From 86cf6c6ccac9c53556cc33a9fc9d9c2b822fa9e3 Mon Sep 17 00:00:00 2001 From: stlc-bot Date: Wed, 3 Jun 2026 19:08:29 +0000 Subject: [PATCH 001/109] Feat(Invoices): Add plan currency to invoice API Stainless-Generated-From: f6479f15dea9c84a5e202791ba6c25ce35dc564c --- src/whop_sdk/types/invoice_create_params.py | 7 +++++++ src/whop_sdk/types/invoice_update_params.py | 4 ++++ tests/api_resources/test_invoices.py | 6 ++++++ 3 files changed, 17 insertions(+) diff --git a/src/whop_sdk/types/invoice_create_params.py b/src/whop_sdk/types/invoice_create_params.py index 8ff9b337..5a55a12b 100644 --- a/src/whop_sdk/types/invoice_create_params.py +++ b/src/whop_sdk/types/invoice_create_params.py @@ -7,6 +7,7 @@ from typing_extensions import Literal, Required, Annotated, TypeAlias, TypedDict from .._utils import PropertyInfo +from .shared.currency import Currency from .shared.plan_type import PlanType from .shared.visibility import Visibility from .tax_identifier_type import TaxIdentifierType @@ -192,6 +193,9 @@ class CreateInvoiceInputWithProductPlan(TypedDict, total=False): billing_period: Optional[int] """The interval in days at which the plan charges (renewal plans).""" + currency: Optional[Currency] + """The available currencies on the platform""" + custom_fields: Optional[Iterable[CreateInvoiceInputWithProductPlanCustomField]] """An array of custom field objects.""" @@ -478,6 +482,9 @@ class CreateInvoiceInputWithProductIDPlan(TypedDict, total=False): billing_period: Optional[int] """The interval in days at which the plan charges (renewal plans).""" + currency: Optional[Currency] + """The available currencies on the platform""" + custom_fields: Optional[Iterable[CreateInvoiceInputWithProductIDPlanCustomField]] """An array of custom field objects.""" diff --git a/src/whop_sdk/types/invoice_update_params.py b/src/whop_sdk/types/invoice_update_params.py index ba46184f..7889a003 100644 --- a/src/whop_sdk/types/invoice_update_params.py +++ b/src/whop_sdk/types/invoice_update_params.py @@ -7,6 +7,7 @@ from typing_extensions import Literal, Required, Annotated, TypedDict from .._utils import PropertyInfo +from .shared.currency import Currency from .shared.plan_type import PlanType from .shared.visibility import Visibility from .tax_identifier_type import TaxIdentifierType @@ -181,6 +182,9 @@ class Plan(TypedDict, total=False): billing_period: Optional[int] """The interval in days at which the plan charges (renewal plans).""" + currency: Optional[Currency] + """The available currencies on the platform""" + custom_fields: Optional[Iterable[PlanCustomField]] """An array of custom field objects.""" diff --git a/tests/api_resources/test_invoices.py b/tests/api_resources/test_invoices.py index 30be40a0..e4e13939 100644 --- a/tests/api_resources/test_invoices.py +++ b/tests/api_resources/test_invoices.py @@ -44,6 +44,7 @@ def test_method_create_with_all_params_overload_1(self, client: Whop) -> None: company_id="biz_xxxxxxxxxxxxxx", plan={ "billing_period": 42, + "currency": "usd", "custom_fields": [ { "field_type": "text", @@ -160,6 +161,7 @@ def test_method_create_with_all_params_overload_2(self, client: Whop) -> None: company_id="biz_xxxxxxxxxxxxxx", plan={ "billing_period": 42, + "currency": "usd", "custom_fields": [ { "field_type": "text", @@ -339,6 +341,7 @@ def test_method_update_with_all_params(self, client: Whop) -> None: payment_method_id="pmt_xxxxxxxxxxxxxx", plan={ "billing_period": 42, + "currency": "usd", "custom_fields": [ { "field_type": "text", @@ -646,6 +649,7 @@ async def test_method_create_with_all_params_overload_1(self, async_client: Asyn company_id="biz_xxxxxxxxxxxxxx", plan={ "billing_period": 42, + "currency": "usd", "custom_fields": [ { "field_type": "text", @@ -762,6 +766,7 @@ async def test_method_create_with_all_params_overload_2(self, async_client: Asyn company_id="biz_xxxxxxxxxxxxxx", plan={ "billing_period": 42, + "currency": "usd", "custom_fields": [ { "field_type": "text", @@ -941,6 +946,7 @@ async def test_method_update_with_all_params(self, async_client: AsyncWhop) -> N payment_method_id="pmt_xxxxxxxxxxxxxx", plan={ "billing_period": 42, + "currency": "usd", "custom_fields": [ { "field_type": "text", From a68e5118385934d9aa1d9a1d474a0ad34ef68945 Mon Sep 17 00:00:00 2001 From: stlc-bot Date: Wed, 3 Jun 2026 22:49:42 +0000 Subject: [PATCH 002/109] fix(backend): update account fields Stainless-Generated-From: e019c8edc43c28f49b69daca853d6c60f5067200 --- src/whop_sdk/resources/accounts.py | 116 +++++++++++++++++++- src/whop_sdk/types/account.py | 44 ++++++++ src/whop_sdk/types/account_update_params.py | 47 ++++++++ tests/api_resources/test_accounts.py | 28 +++++ 4 files changed, 234 insertions(+), 1 deletion(-) diff --git a/src/whop_sdk/resources/accounts.py b/src/whop_sdk/resources/accounts.py index 16264fbd..222afcb7 100644 --- a/src/whop_sdk/resources/accounts.py +++ b/src/whop_sdk/resources/accounts.py @@ -7,7 +7,7 @@ import httpx from ..types import account_list_params, account_create_params, account_update_params -from .._types import Body, Omit, Query, Headers, NotGiven, omit, not_given +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 @@ -132,17 +132,31 @@ def update( affiliate_instructions: Optional[str] | Omit = omit, banner_image: Optional[Dict[str, object]] | Omit = omit, business_type: Optional[str] | Omit = omit, + country: Optional[str] | Omit = omit, description: Optional[str] | Omit = omit, featured_affiliate_product_id: Optional[str] | Omit = omit, + home_preferences: SequenceNotStr[str] | Omit = omit, industry_group: Optional[str] | Omit = omit, industry_type: Optional[str] | Omit = omit, + invoice_prefix: Optional[str] | Omit = omit, logo: Optional[Dict[str, object]] | Omit = omit, metadata: Dict[str, object] | Omit = omit, + onboarding_type: Optional[str] | Omit = omit, + opengraph_image: Optional[Dict[str, object]] | Omit = omit, + opengraph_image_variant: Optional[str] | Omit = omit, + other_business_description: Optional[str] | Omit = omit, + other_industry_description: Optional[str] | Omit = omit, + require_2fa: bool | Omit = omit, route: Optional[str] | Omit = omit, send_customer_emails: bool | Omit = omit, + show_joined_whops: bool | Omit = omit, + show_reviews_dtc: bool | Omit = omit, + show_user_directory: bool | Omit = omit, social_links: Iterable[Dict[str, object]] | Omit = omit, + store_page_config: Optional[Dict[str, object]] | Omit = omit, target_audience: Optional[str] | Omit = omit, title: Optional[str] | Omit = omit, + use_logo_as_opengraph_image_fallback: bool | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, @@ -165,28 +179,57 @@ def update( business_type: The high-level business category for the account. + country: The country the account is located in. + description: A promotional description for the account. featured_affiliate_product_id: The ID of the product to feature for affiliates. Pass null to clear. + home_preferences: Preferences for the public business home page. + industry_group: The industry group the account belongs to. industry_type: The specific industry vertical the account operates in. + invoice_prefix: The prefix to use for account invoices. + logo: Attachment input for the account logo. metadata: Arbitrary key/value metadata to store on the account. + onboarding_type: The type of onboarding the account has completed. + + opengraph_image: Attachment input for the account Open Graph image. + + opengraph_image_variant: The account Open Graph image variant. + + other_business_description: The description of the business type when business_type is other. + + other_industry_description: The description of the industry type when industry_type is other. + + require_2fa: Whether the account requires authorized users to have two-factor authentication + enabled. + route: The unique URL slug for the account. send_customer_emails: Whether Whop sends transactional emails to customers on behalf of this account. + show_joined_whops: Whether the account appears in joined whops on other accounts. + + show_reviews_dtc: Whether reviews are displayed on direct-to-consumer product pages. + + show_user_directory: Whether the account shows users in the user directory. + social_links: The full list of social links to display for the account. + store_page_config: Store page display configuration for the account. + target_audience: The target audience for this account. title: The display name of the account. + use_logo_as_opengraph_image_fallback: Whether the account uses its logo as the fallback Open Graph image. + extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -205,17 +248,31 @@ def update( "affiliate_instructions": affiliate_instructions, "banner_image": banner_image, "business_type": business_type, + "country": country, "description": description, "featured_affiliate_product_id": featured_affiliate_product_id, + "home_preferences": home_preferences, "industry_group": industry_group, "industry_type": industry_type, + "invoice_prefix": invoice_prefix, "logo": logo, "metadata": metadata, + "onboarding_type": onboarding_type, + "opengraph_image": opengraph_image, + "opengraph_image_variant": opengraph_image_variant, + "other_business_description": other_business_description, + "other_industry_description": other_industry_description, + "require_2fa": require_2fa, "route": route, "send_customer_emails": send_customer_emails, + "show_joined_whops": show_joined_whops, + "show_reviews_dtc": show_reviews_dtc, + "show_user_directory": show_user_directory, "social_links": social_links, + "store_page_config": store_page_config, "target_audience": target_audience, "title": title, + "use_logo_as_opengraph_image_fallback": use_logo_as_opengraph_image_fallback, }, account_update_params.AccountUpdateParams, ), @@ -406,17 +463,31 @@ async def update( affiliate_instructions: Optional[str] | Omit = omit, banner_image: Optional[Dict[str, object]] | Omit = omit, business_type: Optional[str] | Omit = omit, + country: Optional[str] | Omit = omit, description: Optional[str] | Omit = omit, featured_affiliate_product_id: Optional[str] | Omit = omit, + home_preferences: SequenceNotStr[str] | Omit = omit, industry_group: Optional[str] | Omit = omit, industry_type: Optional[str] | Omit = omit, + invoice_prefix: Optional[str] | Omit = omit, logo: Optional[Dict[str, object]] | Omit = omit, metadata: Dict[str, object] | Omit = omit, + onboarding_type: Optional[str] | Omit = omit, + opengraph_image: Optional[Dict[str, object]] | Omit = omit, + opengraph_image_variant: Optional[str] | Omit = omit, + other_business_description: Optional[str] | Omit = omit, + other_industry_description: Optional[str] | Omit = omit, + require_2fa: bool | Omit = omit, route: Optional[str] | Omit = omit, send_customer_emails: bool | Omit = omit, + show_joined_whops: bool | Omit = omit, + show_reviews_dtc: bool | Omit = omit, + show_user_directory: bool | Omit = omit, social_links: Iterable[Dict[str, object]] | Omit = omit, + store_page_config: Optional[Dict[str, object]] | Omit = omit, target_audience: Optional[str] | Omit = omit, title: Optional[str] | Omit = omit, + use_logo_as_opengraph_image_fallback: bool | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, @@ -439,28 +510,57 @@ async def update( business_type: The high-level business category for the account. + country: The country the account is located in. + description: A promotional description for the account. featured_affiliate_product_id: The ID of the product to feature for affiliates. Pass null to clear. + home_preferences: Preferences for the public business home page. + industry_group: The industry group the account belongs to. industry_type: The specific industry vertical the account operates in. + invoice_prefix: The prefix to use for account invoices. + logo: Attachment input for the account logo. metadata: Arbitrary key/value metadata to store on the account. + onboarding_type: The type of onboarding the account has completed. + + opengraph_image: Attachment input for the account Open Graph image. + + opengraph_image_variant: The account Open Graph image variant. + + other_business_description: The description of the business type when business_type is other. + + other_industry_description: The description of the industry type when industry_type is other. + + require_2fa: Whether the account requires authorized users to have two-factor authentication + enabled. + route: The unique URL slug for the account. send_customer_emails: Whether Whop sends transactional emails to customers on behalf of this account. + show_joined_whops: Whether the account appears in joined whops on other accounts. + + show_reviews_dtc: Whether reviews are displayed on direct-to-consumer product pages. + + show_user_directory: Whether the account shows users in the user directory. + social_links: The full list of social links to display for the account. + store_page_config: Store page display configuration for the account. + target_audience: The target audience for this account. title: The display name of the account. + use_logo_as_opengraph_image_fallback: Whether the account uses its logo as the fallback Open Graph image. + extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -479,17 +579,31 @@ async def update( "affiliate_instructions": affiliate_instructions, "banner_image": banner_image, "business_type": business_type, + "country": country, "description": description, "featured_affiliate_product_id": featured_affiliate_product_id, + "home_preferences": home_preferences, "industry_group": industry_group, "industry_type": industry_type, + "invoice_prefix": invoice_prefix, "logo": logo, "metadata": metadata, + "onboarding_type": onboarding_type, + "opengraph_image": opengraph_image, + "opengraph_image_variant": opengraph_image_variant, + "other_business_description": other_business_description, + "other_industry_description": other_industry_description, + "require_2fa": require_2fa, "route": route, "send_customer_emails": send_customer_emails, + "show_joined_whops": show_joined_whops, + "show_reviews_dtc": show_reviews_dtc, + "show_user_directory": show_user_directory, "social_links": social_links, + "store_page_config": store_page_config, "target_audience": target_audience, "title": title, + "use_logo_as_opengraph_image_fallback": use_logo_as_opengraph_image_fallback, }, account_update_params.AccountUpdateParams, ), diff --git a/src/whop_sdk/types/account.py b/src/whop_sdk/types/account.py index 6a1d0063..62430ec7 100644 --- a/src/whop_sdk/types/account.py +++ b/src/whop_sdk/types/account.py @@ -19,6 +19,9 @@ class Account(BaseModel): business_type: Optional[str] = None """The high-level business category for the account""" + country: Optional[str] = None + """The country the account is located in""" + created_at: str """When the account was created, as an ISO 8601 timestamp""" @@ -28,34 +31,75 @@ class Account(BaseModel): email: Optional[str] = None """The email address of the account owner""" + home_preferences: List[str] + industry_group: Optional[str] = None """The industry group the account belongs to""" industry_type: Optional[str] = None """The specific industry vertical the account operates in""" + invoice_prefix: Optional[str] = None + """The prefix used for account invoices""" + logo_url: Optional[str] = None """The URL of the account logo image""" metadata: object """Arbitrary key/value metadata supplied when the account was created""" + onboarding_type: Optional[str] = None + """The type of onboarding the account has completed""" + + opengraph_image_url: Optional[str] = None + """The URL of the account Open Graph image""" + + opengraph_image_variant: Optional[str] = None + """The account Open Graph image variant""" + + other_business_description: Optional[str] = None + """The description of the business type when business_type is other""" + + other_industry_description: Optional[str] = None + """The description of the industry type when industry_type is other""" + parent_account_id: Optional[str] = None """The parent account ID for connected accounts""" + require_2fa: bool + """ + Whether the account requires authorized users to have two-factor authentication + enabled + """ + route: str """The account's public route identifier""" send_customer_emails: bool """Whether Whop sends transactional emails to customers on behalf of this account""" + show_joined_whops: bool + """Whether the account appears in joined whops on other accounts""" + + show_reviews_dtc: bool + """Whether reviews are displayed on direct-to-consumer product pages""" + + show_user_directory: bool + """Whether the account shows users in the user directory""" + social_links: List[AccountSocialLink] + store_page_config: object + """Store page display configuration for the account""" + target_audience: Optional[str] = None """The target audience for this account""" title: str """The display name of the account""" + use_logo_as_opengraph_image_fallback: bool + """Whether the account uses its logo as the fallback Open Graph image""" + wallet: Optional[AccountWallet] = None """The account's primary crypto wallet, or null if none has been provisioned""" diff --git a/src/whop_sdk/types/account_update_params.py b/src/whop_sdk/types/account_update_params.py index 1efb814c..f5014c18 100644 --- a/src/whop_sdk/types/account_update_params.py +++ b/src/whop_sdk/types/account_update_params.py @@ -5,6 +5,8 @@ from typing import Dict, Iterable, Optional from typing_extensions import TypedDict +from .._types import SequenceNotStr + __all__ = ["AccountUpdateParams"] @@ -24,35 +26,80 @@ class AccountUpdateParams(TypedDict, total=False): business_type: Optional[str] """The high-level business category for the account.""" + country: Optional[str] + """The country the account is located in.""" + description: Optional[str] """A promotional description for the account.""" featured_affiliate_product_id: Optional[str] """The ID of the product to feature for affiliates. Pass null to clear.""" + home_preferences: SequenceNotStr[str] + """Preferences for the public business home page.""" + industry_group: Optional[str] """The industry group the account belongs to.""" industry_type: Optional[str] """The specific industry vertical the account operates in.""" + invoice_prefix: Optional[str] + """The prefix to use for account invoices.""" + logo: Optional[Dict[str, object]] """Attachment input for the account logo.""" metadata: Dict[str, object] """Arbitrary key/value metadata to store on the account.""" + onboarding_type: Optional[str] + """The type of onboarding the account has completed.""" + + opengraph_image: Optional[Dict[str, object]] + """Attachment input for the account Open Graph image.""" + + opengraph_image_variant: Optional[str] + """The account Open Graph image variant.""" + + other_business_description: Optional[str] + """The description of the business type when business_type is other.""" + + other_industry_description: Optional[str] + """The description of the industry type when industry_type is other.""" + + require_2fa: bool + """ + Whether the account requires authorized users to have two-factor authentication + enabled. + """ + route: Optional[str] """The unique URL slug for the account.""" send_customer_emails: bool """Whether Whop sends transactional emails to customers on behalf of this account.""" + show_joined_whops: bool + """Whether the account appears in joined whops on other accounts.""" + + show_reviews_dtc: bool + """Whether reviews are displayed on direct-to-consumer product pages.""" + + show_user_directory: bool + """Whether the account shows users in the user directory.""" + social_links: Iterable[Dict[str, object]] """The full list of social links to display for the account.""" + store_page_config: Optional[Dict[str, object]] + """Store page display configuration for the account.""" + target_audience: Optional[str] """The target audience for this account.""" title: Optional[str] """The display name of the account.""" + + use_logo_as_opengraph_image_fallback: bool + """Whether the account uses its logo as the fallback Open Graph image.""" diff --git a/tests/api_resources/test_accounts.py b/tests/api_resources/test_accounts.py index 11810569..74855645 100644 --- a/tests/api_resources/test_accounts.py +++ b/tests/api_resources/test_accounts.py @@ -116,17 +116,31 @@ def test_method_update_with_all_params(self, client: Whop) -> None: affiliate_instructions="affiliate_instructions", banner_image={"foo": "bar"}, business_type="business_type", + country="country", description="description", featured_affiliate_product_id="featured_affiliate_product_id", + home_preferences=["string"], industry_group="industry_group", industry_type="industry_type", + invoice_prefix="invoice_prefix", logo={"foo": "bar"}, metadata={"foo": "bar"}, + onboarding_type="onboarding_type", + opengraph_image={"foo": "bar"}, + opengraph_image_variant="opengraph_image_variant", + other_business_description="other_business_description", + other_industry_description="other_industry_description", + require_2fa=True, route="route", send_customer_emails=True, + show_joined_whops=True, + show_reviews_dtc=True, + show_user_directory=True, social_links=[{"foo": "bar"}], + store_page_config={"foo": "bar"}, target_audience="target_audience", title="title", + use_logo_as_opengraph_image_fallback=True, ) assert_matches_type(Account, account, path=["response"]) @@ -331,17 +345,31 @@ async def test_method_update_with_all_params(self, async_client: AsyncWhop) -> N affiliate_instructions="affiliate_instructions", banner_image={"foo": "bar"}, business_type="business_type", + country="country", description="description", featured_affiliate_product_id="featured_affiliate_product_id", + home_preferences=["string"], industry_group="industry_group", industry_type="industry_type", + invoice_prefix="invoice_prefix", logo={"foo": "bar"}, metadata={"foo": "bar"}, + onboarding_type="onboarding_type", + opengraph_image={"foo": "bar"}, + opengraph_image_variant="opengraph_image_variant", + other_business_description="other_business_description", + other_industry_description="other_industry_description", + require_2fa=True, route="route", send_customer_emails=True, + show_joined_whops=True, + show_reviews_dtc=True, + show_user_directory=True, social_links=[{"foo": "bar"}], + store_page_config={"foo": "bar"}, target_audience="target_audience", title="title", + use_logo_as_opengraph_image_fallback=True, ) assert_matches_type(Account, account, path=["response"]) From f2d8a32246a42f3520432d526e5cd714d681621a Mon Sep 17 00:00:00 2001 From: stlc-bot Date: Fri, 5 Jun 2026 00:07:50 +0000 Subject: [PATCH 003/109] Route pixel conversions through worker proxy with edge geo Stainless-Generated-From: 3e19255c99576b6b4a9fce2b3e2638a86b06598a --- src/whop_sdk/resources/conversions.py | 58 +++++++++++++- .../types/conversion_create_params.py | 75 ++++++++++++++++++- tests/api_resources/test_conversions.py | 38 ++++++++++ 3 files changed, 168 insertions(+), 3 deletions(-) diff --git a/src/whop_sdk/resources/conversions.py b/src/whop_sdk/resources/conversions.py index a3dd6259..4c351f91 100644 --- a/src/whop_sdk/resources/conversions.py +++ b/src/whop_sdk/resources/conversions.py @@ -52,7 +52,17 @@ def create( self, *, company_id: str, - event_name: Literal["lead", "submit_application", "contact", "complete_registration", "schedule", "custom"], + event_name: Literal[ + "lead", + "submit_application", + "contact", + "complete_registration", + "schedule", + "custom", + "page", + "leave", + "identify", + ], action_source: Optional[ Literal[ "email", @@ -70,11 +80,15 @@ def create( context: Optional[conversion_create_params.Context] | Omit = omit, currency: Optional[Currency] | Omit = omit, custom_name: Optional[str] | Omit = omit, + duration: Optional[int] | 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, + resumed: Optional[bool] | Omit = omit, + source: Optional[str] | Omit = omit, + title: Optional[str] | Omit = omit, url: Optional[str] | Omit = omit, user: Optional[conversion_create_params.User] | Omit = omit, value: Optional[float] | Omit = omit, @@ -105,6 +119,8 @@ def create( custom_name: Custom event name when event_name is 'custom'. + duration: For 'leave' events: milliseconds the visitor spent on the page. + event_id: Client-provided identifier for deduplication. Generated if omitted. event_time: When the event occurred. Defaults to now. @@ -115,6 +131,13 @@ def create( referrer_url: The referring URL. + resumed: For 'page' events: true when the page was restored from the back/forward cache. + + source: For 'identify' events: where the identity was captured (url, form, manual, + iframe). + + title: For 'page' events: the document title. + url: The URL where the event occurred. user: User identity and profile data. @@ -139,11 +162,15 @@ def create( "context": context, "currency": currency, "custom_name": custom_name, + "duration": duration, "event_id": event_id, "event_time": event_time, "plan_id": plan_id, "product_id": product_id, "referrer_url": referrer_url, + "resumed": resumed, + "source": source, + "title": title, "url": url, "user": user, "value": value, @@ -183,7 +210,17 @@ async def create( self, *, company_id: str, - event_name: Literal["lead", "submit_application", "contact", "complete_registration", "schedule", "custom"], + event_name: Literal[ + "lead", + "submit_application", + "contact", + "complete_registration", + "schedule", + "custom", + "page", + "leave", + "identify", + ], action_source: Optional[ Literal[ "email", @@ -201,11 +238,15 @@ async def create( context: Optional[conversion_create_params.Context] | Omit = omit, currency: Optional[Currency] | Omit = omit, custom_name: Optional[str] | Omit = omit, + duration: Optional[int] | 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, + resumed: Optional[bool] | Omit = omit, + source: Optional[str] | Omit = omit, + title: Optional[str] | Omit = omit, url: Optional[str] | Omit = omit, user: Optional[conversion_create_params.User] | Omit = omit, value: Optional[float] | Omit = omit, @@ -236,6 +277,8 @@ async def create( custom_name: Custom event name when event_name is 'custom'. + duration: For 'leave' events: milliseconds the visitor spent on the page. + event_id: Client-provided identifier for deduplication. Generated if omitted. event_time: When the event occurred. Defaults to now. @@ -246,6 +289,13 @@ async def create( referrer_url: The referring URL. + resumed: For 'page' events: true when the page was restored from the back/forward cache. + + source: For 'identify' events: where the identity was captured (url, form, manual, + iframe). + + title: For 'page' events: the document title. + url: The URL where the event occurred. user: User identity and profile data. @@ -270,11 +320,15 @@ async def create( "context": context, "currency": currency, "custom_name": custom_name, + "duration": duration, "event_id": event_id, "event_time": event_time, "plan_id": plan_id, "product_id": product_id, "referrer_url": referrer_url, + "resumed": resumed, + "source": source, + "title": title, "url": url, "user": user, "value": value, diff --git a/src/whop_sdk/types/conversion_create_params.py b/src/whop_sdk/types/conversion_create_params.py index e833dba0..21b04154 100644 --- a/src/whop_sdk/types/conversion_create_params.py +++ b/src/whop_sdk/types/conversion_create_params.py @@ -17,7 +17,17 @@ class ConversionCreateParams(TypedDict, total=False): """The company to associate with this event.""" event_name: Required[ - Literal["lead", "submit_application", "contact", "complete_registration", "schedule", "custom"] + Literal[ + "lead", + "submit_application", + "contact", + "complete_registration", + "schedule", + "custom", + "page", + "leave", + "identify", + ] ] """The type of event.""" @@ -45,6 +55,9 @@ class ConversionCreateParams(TypedDict, total=False): custom_name: Optional[str] """Custom event name when event_name is 'custom'.""" + duration: Optional[int] + """For 'leave' events: milliseconds the visitor spent on the page.""" + event_id: Optional[str] """Client-provided identifier for deduplication. Generated if omitted.""" @@ -60,6 +73,18 @@ class ConversionCreateParams(TypedDict, total=False): referrer_url: Optional[str] """The referring URL.""" + resumed: Optional[bool] + """For 'page' events: true when the page was restored from the back/forward cache.""" + + source: Optional[str] + """ + For 'identify' events: where the identity was captured (url, form, manual, + iframe). + """ + + title: Optional[str] + """For 'page' events: the document title.""" + url: Optional[str] """The URL where the event occurred.""" @@ -82,15 +107,27 @@ class Context(TypedDict, total=False): ad_set_id: Optional[str] """Ad set ID.""" + fbc: Optional[str] + """Facebook click cookie (\\__fbc, format fb.1.{timestamp}.{fbclid}).""" + fbclid: Optional[str] """Facebook click ID.""" fbp: Optional[str] """Facebook browser pixel ID.""" + fingerprint: Optional[str] + """Client-side device fingerprint.""" + + fingerprint_confidence: Optional[float] + """Confidence score (0-1) for the device fingerprint.""" + ga: Optional[str] """Google Analytics client ID.""" + gbraid: Optional[str] + """Google Ads gbraid click ID (iOS privacy).""" + gclid: Optional[str] """Google click ID.""" @@ -100,12 +137,36 @@ class Context(TypedDict, total=False): ip_address: Optional[str] """IP address.""" + language: Optional[str] + """Browser language (e.g. en-US).""" + + li_fat_id: Optional[str] + """LinkedIn click ID.""" + + msclkid: Optional[str] + """Microsoft Advertising (Bing) click ID.""" + + rdt_cid: Optional[str] + """Reddit click ID.""" + + sccid: Optional[str] + """Snapchat click ID.""" + + screen_resolution: Optional[str] + """Screen resolution (e.g. 1920x1080).""" + + timezone: Optional[str] + """IANA timezone (e.g. America/New_York).""" + ttclid: Optional[str] """TikTok click ID.""" ttp: Optional[str] """TikTok pixel ID.""" + twclid: Optional[str] + """X (Twitter) click ID.""" + user_agent: Optional[str] """Browser user agent string.""" @@ -127,6 +188,9 @@ class Context(TypedDict, total=False): utm_term: Optional[str] """UTM term parameter.""" + wbraid: Optional[str] + """Google Ads wbraid click ID (iOS privacy).""" + class User(TypedDict, total=False): """User identity and profile data.""" @@ -158,6 +222,15 @@ class User(TypedDict, total=False): last_name: Optional[str] """Last name.""" + linked_anonymous_id: Optional[str] + """A second anonymous identifier to link to this user (e.g. + + captured across an iframe boundary). + """ + + linked_wuid: Optional[str] + """A wuid from a linked frame, captured across an iframe boundary.""" + member_id: Optional[str] """The Whop member ID.""" diff --git a/tests/api_resources/test_conversions.py b/tests/api_resources/test_conversions.py index 3b63db11..e0903c3e 100644 --- a/tests/api_resources/test_conversions.py +++ b/tests/api_resources/test_conversions.py @@ -38,14 +38,26 @@ def test_method_create_with_all_params(self, client: Whop) -> None: "ad_campaign_id": "ad_campaign_id", "ad_id": "ad_id", "ad_set_id": "ad_set_id", + "fbc": "fbc", "fbclid": "fbclid", "fbp": "fbp", + "fingerprint": "fingerprint", + "fingerprint_confidence": 6.9, "ga": "ga", + "gbraid": "gbraid", "gclid": "gclid", "ig_sid": "ig_sid", "ip_address": "ip_address", + "language": "language", + "li_fat_id": "li_fat_id", + "msclkid": "msclkid", + "rdt_cid": "rdt_cid", + "sccid": "sccid", + "screen_resolution": "screen_resolution", + "timezone": "timezone", "ttclid": "ttclid", "ttp": "ttp", + "twclid": "twclid", "user_agent": "user_agent", "utm_campaign": "utm_campaign", "utm_content": "utm_content", @@ -53,14 +65,19 @@ def test_method_create_with_all_params(self, client: Whop) -> None: "utm_medium": "utm_medium", "utm_source": "utm_source", "utm_term": "utm_term", + "wbraid": "wbraid", }, currency="usd", custom_name="custom_name", + duration=42, 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", + resumed=True, + source="source", + title="title", url="url", user={ "anonymous_id": "anonymous_id", @@ -72,6 +89,8 @@ def test_method_create_with_all_params(self, client: Whop) -> None: "first_name": "first_name", "gender": "male", "last_name": "last_name", + "linked_anonymous_id": "linked_anonymous_id", + "linked_wuid": "linked_wuid", "member_id": "mber_xxxxxxxxxxxxx", "membership_id": "mem_xxxxxxxxxxxxxx", "name": "name", @@ -139,14 +158,26 @@ async def test_method_create_with_all_params(self, async_client: AsyncWhop) -> N "ad_campaign_id": "ad_campaign_id", "ad_id": "ad_id", "ad_set_id": "ad_set_id", + "fbc": "fbc", "fbclid": "fbclid", "fbp": "fbp", + "fingerprint": "fingerprint", + "fingerprint_confidence": 6.9, "ga": "ga", + "gbraid": "gbraid", "gclid": "gclid", "ig_sid": "ig_sid", "ip_address": "ip_address", + "language": "language", + "li_fat_id": "li_fat_id", + "msclkid": "msclkid", + "rdt_cid": "rdt_cid", + "sccid": "sccid", + "screen_resolution": "screen_resolution", + "timezone": "timezone", "ttclid": "ttclid", "ttp": "ttp", + "twclid": "twclid", "user_agent": "user_agent", "utm_campaign": "utm_campaign", "utm_content": "utm_content", @@ -154,14 +185,19 @@ async def test_method_create_with_all_params(self, async_client: AsyncWhop) -> N "utm_medium": "utm_medium", "utm_source": "utm_source", "utm_term": "utm_term", + "wbraid": "wbraid", }, currency="usd", custom_name="custom_name", + duration=42, 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", + resumed=True, + source="source", + title="title", url="url", user={ "anonymous_id": "anonymous_id", @@ -173,6 +209,8 @@ async def test_method_create_with_all_params(self, async_client: AsyncWhop) -> N "first_name": "first_name", "gender": "male", "last_name": "last_name", + "linked_anonymous_id": "linked_anonymous_id", + "linked_wuid": "linked_wuid", "member_id": "mber_xxxxxxxxxxxxx", "membership_id": "mem_xxxxxxxxxxxxxx", "name": "name", From 76b9af52270b49616464a038d9e20dd95574a0f8 Mon Sep 17 00:00:00 2001 From: stlc-bot Date: Fri, 5 Jun 2026 07:41:55 +0000 Subject: [PATCH 004/109] feat(ads): ad campaigns, ad groups, and ads root tables + public API massive cleanup Stainless-Generated-From: 004144368e3cefd3082d939ad81fa2e8e94c4743 --- api.md | 6 +- src/whop_sdk/resources/ad_campaigns.py | 66 +++++++- src/whop_sdk/resources/ad_groups.py | 104 +++++++++--- src/whop_sdk/resources/ad_reports.py | 88 +++++----- src/whop_sdk/resources/ads.py | 142 ++++++++++++---- src/whop_sdk/types/__init__.py | 3 + src/whop_sdk/types/ad.py | 121 +++++++++++++- src/whop_sdk/types/ad_campaign.py | 152 ++++++++++++------ src/whop_sdk/types/ad_campaign_list_params.py | 14 +- .../types/ad_campaign_list_response.py | 124 +++++++++++++- .../types/ad_campaign_retrieve_params.py | 25 +++ src/whop_sdk/types/ad_group.py | 125 +++++++++++++- src/whop_sdk/types/ad_group_list_params.py | 34 ++-- src/whop_sdk/types/ad_group_list_response.py | 125 +++++++++++++- .../types/ad_group_retrieve_params.py | 25 +++ src/whop_sdk/types/ad_list_params.py | 57 +++++-- src/whop_sdk/types/ad_list_response.py | 121 +++++++++++++- .../types/ad_report_retrieve_params.py | 21 +-- .../types/ad_report_retrieve_response.py | 40 ++--- src/whop_sdk/types/ad_retrieve_params.py | 26 +++ tests/api_resources/test_ad_campaigns.py | 40 ++++- tests/api_resources/test_ad_groups.py | 46 ++++-- tests/api_resources/test_ad_reports.py | 12 +- tests/api_resources/test_ads.py | 48 ++++-- 24 files changed, 1301 insertions(+), 264 deletions(-) create mode 100644 src/whop_sdk/types/ad_campaign_retrieve_params.py create mode 100644 src/whop_sdk/types/ad_group_retrieve_params.py create mode 100644 src/whop_sdk/types/ad_retrieve_params.py diff --git a/api.md b/api.md index 8c7ec82d..1a48a9be 100644 --- a/api.md +++ b/api.md @@ -1073,7 +1073,7 @@ from whop_sdk.types import AdCampaign, AdCampaignPlatform, AdCampaignStatus, AdC Methods: -- client.ad_campaigns.retrieve(id) -> AdCampaign +- client.ad_campaigns.retrieve(id, \*\*params) -> AdCampaign - client.ad_campaigns.update(id, \*\*params) -> AdCampaign - client.ad_campaigns.list(\*\*params) -> SyncCursorPage[AdCampaignListResponse] - client.ad_campaigns.pause(id) -> AdCampaign @@ -1095,7 +1095,7 @@ from whop_sdk.types import ( Methods: -- client.ad_groups.retrieve(id) -> AdGroup +- client.ad_groups.retrieve(id, \*\*params) -> AdGroup - client.ad_groups.update(id, \*\*params) -> AdGroup - client.ad_groups.list(\*\*params) -> SyncCursorPage[AdGroupListResponse] - client.ad_groups.delete(id) -> AdGroupDeleteResponse @@ -1112,7 +1112,7 @@ from whop_sdk.types import Ad, ExternalAdStatus, AdListResponse Methods: -- client.ads.retrieve(id) -> Ad +- client.ads.retrieve(id, \*\*params) -> Ad - client.ads.list(\*\*params) -> SyncCursorPage[AdListResponse] - client.ads.pause(id) -> Ad - client.ads.unpause(id) -> Ad diff --git a/src/whop_sdk/resources/ad_campaigns.py b/src/whop_sdk/resources/ad_campaigns.py index 3cbade58..3d26a1c9 100644 --- a/src/whop_sdk/resources/ad_campaigns.py +++ b/src/whop_sdk/resources/ad_campaigns.py @@ -7,7 +7,7 @@ import httpx -from ..types import AdCampaignStatus, ad_campaign_list_params, ad_campaign_update_params +from ..types import AdCampaignStatus, ad_campaign_list_params, ad_campaign_update_params, ad_campaign_retrieve_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 @@ -53,6 +53,8 @@ def retrieve( self, id: str, *, + stats_from: Union[str, datetime, None] | Omit = omit, + stats_to: 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. extra_headers: Headers | None = None, @@ -68,6 +70,12 @@ def retrieve( - `ad_campaign:basic:read` Args: + stats_from: Inclusive start of the window for the campaign's metric fields (spend, + impressions, …). Omit both statsFrom and statsTo for all-time stats. + + stats_to: Inclusive end of the window for the campaign's metric fields. Omit both + statsFrom and statsTo for all-time stats. + extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -81,7 +89,17 @@ def retrieve( 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 + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "stats_from": stats_from, + "stats_to": stats_to, + }, + ad_campaign_retrieve_params.AdCampaignRetrieveParams, + ), ), cast_to=AdCampaign, ) @@ -139,6 +157,8 @@ def list( first: Optional[int] | Omit = omit, last: Optional[int] | Omit = omit, query: Optional[str] | Omit = omit, + stats_from: Union[str, datetime, None] | Omit = omit, + stats_to: Union[str, datetime, None] | Omit = omit, status: Optional[AdCampaignStatus] | 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. @@ -170,7 +190,13 @@ def list( last: Returns the last _n_ elements from the list. - query: Case-insensitive substring match against the campaign title. + query: Case-insensitive substring match against the campaign title or ID. + + stats_from: Inclusive start of the window for each campaign's metric fields (spend, + impressions, …). Omit both statsFrom and statsTo for all-time stats. + + stats_to: Inclusive end of the window for each campaign's metric fields. Omit both + statsFrom and statsTo for all-time stats. status: The status of an ad campaign. @@ -200,6 +226,8 @@ def list( "first": first, "last": last, "query": query, + "stats_from": stats_from, + "stats_to": stats_to, "status": status, }, ad_campaign_list_params.AdCampaignListParams, @@ -309,6 +337,8 @@ async def retrieve( self, id: str, *, + stats_from: Union[str, datetime, None] | Omit = omit, + stats_to: 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. extra_headers: Headers | None = None, @@ -324,6 +354,12 @@ async def retrieve( - `ad_campaign:basic:read` Args: + stats_from: Inclusive start of the window for the campaign's metric fields (spend, + impressions, …). Omit both statsFrom and statsTo for all-time stats. + + stats_to: Inclusive end of the window for the campaign's metric fields. Omit both + statsFrom and statsTo for all-time stats. + extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -337,7 +373,17 @@ async def retrieve( 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 + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=await async_maybe_transform( + { + "stats_from": stats_from, + "stats_to": stats_to, + }, + ad_campaign_retrieve_params.AdCampaignRetrieveParams, + ), ), cast_to=AdCampaign, ) @@ -395,6 +441,8 @@ def list( first: Optional[int] | Omit = omit, last: Optional[int] | Omit = omit, query: Optional[str] | Omit = omit, + stats_from: Union[str, datetime, None] | Omit = omit, + stats_to: Union[str, datetime, None] | Omit = omit, status: Optional[AdCampaignStatus] | 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. @@ -426,7 +474,13 @@ def list( last: Returns the last _n_ elements from the list. - query: Case-insensitive substring match against the campaign title. + query: Case-insensitive substring match against the campaign title or ID. + + stats_from: Inclusive start of the window for each campaign's metric fields (spend, + impressions, …). Omit both statsFrom and statsTo for all-time stats. + + stats_to: Inclusive end of the window for each campaign's metric fields. Omit both + statsFrom and statsTo for all-time stats. status: The status of an ad campaign. @@ -456,6 +510,8 @@ def list( "first": first, "last": last, "query": query, + "stats_from": stats_from, + "stats_to": stats_to, "status": status, }, ad_campaign_list_params.AdCampaignListParams, diff --git a/src/whop_sdk/resources/ad_groups.py b/src/whop_sdk/resources/ad_groups.py index 25cdded6..bacfb9f4 100644 --- a/src/whop_sdk/resources/ad_groups.py +++ b/src/whop_sdk/resources/ad_groups.py @@ -7,8 +7,8 @@ import httpx -from ..types import AdBudgetType, AdGroupStatus, ad_group_list_params, ad_group_update_params -from .._types import Body, Omit, Query, Headers, NotGiven, omit, not_given +from ..types import AdBudgetType, AdGroupStatus, ad_group_list_params, ad_group_update_params, ad_group_retrieve_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 @@ -55,6 +55,8 @@ def retrieve( self, id: str, *, + stats_from: Union[str, datetime, None] | Omit = omit, + stats_to: 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. extra_headers: Headers | None = None, @@ -70,6 +72,12 @@ def retrieve( - `ad_campaign:basic:read` Args: + stats_from: Inclusive start of the window for the ad group's metric fields (spend, + impressions, …). Omit both statsFrom and statsTo for all-time stats. + + stats_to: Inclusive end of the window for the ad group's metric fields. Omit both + statsFrom and statsTo for all-time stats. + extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -83,7 +91,17 @@ def retrieve( 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 + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "stats_from": stats_from, + "stats_to": stats_to, + }, + ad_group_retrieve_params.AdGroupRetrieveParams, + ), ), cast_to=AdGroup, ) @@ -162,6 +180,8 @@ def update( def list( self, *, + ad_campaign_id: Optional[str] | Omit = omit, + ad_campaign_ids: Optional[SequenceNotStr[str]] | Omit = omit, after: Optional[str] | Omit = omit, before: Optional[str] | Omit = omit, campaign_id: Optional[str] | Omit = omit, @@ -169,9 +189,10 @@ def list( created_after: Union[str, datetime, None] | Omit = omit, created_before: Union[str, datetime, None] | Omit = omit, first: Optional[int] | Omit = omit, - include_paused: Optional[bool] | Omit = omit, last: Optional[int] | Omit = omit, query: Optional[str] | Omit = omit, + stats_from: Union[str, datetime, None] | Omit = omit, + stats_to: Union[str, datetime, None] | Omit = omit, status: Optional[AdGroupStatus] | 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. @@ -189,13 +210,18 @@ def list( - `ad_campaign:basic:read` Args: + ad_campaign_id: Filter by ad campaign. Provide exactly one of ad_campaign_id or company_id. + + ad_campaign_ids: Only return ad groups belonging to these ad campaigns (max 100). Can be combined + with companyId or used on its own. + 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. + campaign_id: Filter by campaign. - company_id: Filter by company. Provide exactly one of campaign_id or company_id. + company_id: Filter by company. Provide companyId or adCampaignIds. created_after: Only return ad groups created after this timestamp. @@ -203,12 +229,15 @@ def list( first: Returns the first _n_ elements from the list. - include_paused: When false, excludes paused ad groups so pagination matches the dashboard's - hide-paused toggle. - last: Returns the last _n_ elements from the list. - query: Case-insensitive substring match against the ad group name. + query: Case-insensitive substring match against the ad group name or ID. + + stats_from: Inclusive start of the window for each ad group's metric fields (spend, + impressions, …). Omit both statsFrom and statsTo for all-time stats. + + stats_to: Inclusive end of the window for each ad group's metric fields. Omit both + statsFrom and statsTo for all-time stats. status: The status of an external ad group. @@ -230,6 +259,8 @@ def list( timeout=timeout, query=maybe_transform( { + "ad_campaign_id": ad_campaign_id, + "ad_campaign_ids": ad_campaign_ids, "after": after, "before": before, "campaign_id": campaign_id, @@ -237,9 +268,10 @@ def list( "created_after": created_after, "created_before": created_before, "first": first, - "include_paused": include_paused, "last": last, "query": query, + "stats_from": stats_from, + "stats_to": stats_to, "status": status, }, ad_group_list_params.AdGroupListParams, @@ -388,6 +420,8 @@ async def retrieve( self, id: str, *, + stats_from: Union[str, datetime, None] | Omit = omit, + stats_to: 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. extra_headers: Headers | None = None, @@ -403,6 +437,12 @@ async def retrieve( - `ad_campaign:basic:read` Args: + stats_from: Inclusive start of the window for the ad group's metric fields (spend, + impressions, …). Omit both statsFrom and statsTo for all-time stats. + + stats_to: Inclusive end of the window for the ad group's metric fields. Omit both + statsFrom and statsTo for all-time stats. + extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -416,7 +456,17 @@ async def retrieve( 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 + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=await async_maybe_transform( + { + "stats_from": stats_from, + "stats_to": stats_to, + }, + ad_group_retrieve_params.AdGroupRetrieveParams, + ), ), cast_to=AdGroup, ) @@ -495,6 +545,8 @@ async def update( def list( self, *, + ad_campaign_id: Optional[str] | Omit = omit, + ad_campaign_ids: Optional[SequenceNotStr[str]] | Omit = omit, after: Optional[str] | Omit = omit, before: Optional[str] | Omit = omit, campaign_id: Optional[str] | Omit = omit, @@ -502,9 +554,10 @@ def list( created_after: Union[str, datetime, None] | Omit = omit, created_before: Union[str, datetime, None] | Omit = omit, first: Optional[int] | Omit = omit, - include_paused: Optional[bool] | Omit = omit, last: Optional[int] | Omit = omit, query: Optional[str] | Omit = omit, + stats_from: Union[str, datetime, None] | Omit = omit, + stats_to: Union[str, datetime, None] | Omit = omit, status: Optional[AdGroupStatus] | 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. @@ -522,13 +575,18 @@ def list( - `ad_campaign:basic:read` Args: + ad_campaign_id: Filter by ad campaign. Provide exactly one of ad_campaign_id or company_id. + + ad_campaign_ids: Only return ad groups belonging to these ad campaigns (max 100). Can be combined + with companyId or used on its own. + 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. + campaign_id: Filter by campaign. - company_id: Filter by company. Provide exactly one of campaign_id or company_id. + company_id: Filter by company. Provide companyId or adCampaignIds. created_after: Only return ad groups created after this timestamp. @@ -536,12 +594,15 @@ def list( first: Returns the first _n_ elements from the list. - include_paused: When false, excludes paused ad groups so pagination matches the dashboard's - hide-paused toggle. - last: Returns the last _n_ elements from the list. - query: Case-insensitive substring match against the ad group name. + query: Case-insensitive substring match against the ad group name or ID. + + stats_from: Inclusive start of the window for each ad group's metric fields (spend, + impressions, …). Omit both statsFrom and statsTo for all-time stats. + + stats_to: Inclusive end of the window for each ad group's metric fields. Omit both + statsFrom and statsTo for all-time stats. status: The status of an external ad group. @@ -563,6 +624,8 @@ def list( timeout=timeout, query=maybe_transform( { + "ad_campaign_id": ad_campaign_id, + "ad_campaign_ids": ad_campaign_ids, "after": after, "before": before, "campaign_id": campaign_id, @@ -570,9 +633,10 @@ def list( "created_after": created_after, "created_before": created_before, "first": first, - "include_paused": include_paused, "last": last, "query": query, + "stats_from": stats_from, + "stats_to": stats_to, "status": status, }, ad_group_list_params.AdGroupListParams, diff --git a/src/whop_sdk/resources/ad_reports.py b/src/whop_sdk/resources/ad_reports.py index 1c8cbd81..22dc0a58 100644 --- a/src/whop_sdk/resources/ad_reports.py +++ b/src/whop_sdk/resources/ad_reports.py @@ -9,7 +9,7 @@ import httpx from ..types import Granularities, ad_report_retrieve_params -from .._types import Body, Omit, Query, Headers, NotGiven, omit, not_given +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 @@ -53,9 +53,9 @@ def retrieve( *, from_: Union[str, datetime], to: Union[str, datetime], - ad_campaign_id: Optional[str] | Omit = omit, - ad_group_id: Optional[str] | Omit = omit, - ad_id: Optional[str] | Omit = omit, + ad_campaign_ids: Optional[SequenceNotStr[str]] | Omit = omit, + ad_group_ids: Optional[SequenceNotStr[str]] | Omit = omit, + ad_ids: Optional[SequenceNotStr[str]] | Omit = omit, breakdown: Optional[Literal["campaign", "ad_group", "ad"]] | Omit = omit, company_id: Optional[str] | Omit = omit, currency: Optional[str] | Omit = omit, @@ -67,13 +67,14 @@ def retrieve( extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> AdReportRetrieveResponse: - """Performance report for a company, ad campaign, ad group, or ad. + """Performance report for a company, ad campaigns, ad groups, or ads. - Always returns - aggregate `summary` totals. Set `granularity` (`daily`/`hourly`) to additionally - get a time series, or set `breakdown` (`campaign`/`ad_group`/`ad`) to - additionally get per-entity rows inside the requested scope. Exactly one of - `companyId`, `adCampaignId`, `adGroupId`, or `adId` must be provided. + Always + returns aggregate `summary` totals summed across the scope. Set `granularity` + (`daily`/`hourly`) to additionally get a time series, or set `breakdown` + (`campaign`/`ad_group`/`ad`) to additionally get per-entity rows inside the + requested scope. Exactly one of `companyId`, `adCampaignIds`, `adGroupIds`, or + `adIds` must be provided. Required permissions: @@ -84,20 +85,20 @@ def retrieve( to: Inclusive end of the reporting window. - ad_campaign_id: The unique identifier of an ad campaign. Mutually exclusive with `companyId`, - `adGroupId`, and `adId`. + ad_campaign_ids: Scope the report to these ad campaigns (max 100); stats are summed across them. + Mutually exclusive with `companyId`, `adGroupIds`, and `adIds`. - ad_group_id: The unique identifier of an ad group. Mutually exclusive with `companyId`, - `adCampaignId`, and `adId`. + ad_group_ids: Scope the report to these ad groups (max 100); stats are summed across them. + Mutually exclusive with `companyId`, `adCampaignIds`, and `adIds`. - ad_id: The unique identifier of an ad. Mutually exclusive with `companyId`, - `adCampaignId`, and `adGroupId`. + ad_ids: Scope the report to these ads (max 100); stats are summed across them. Mutually + exclusive with `companyId`, `adCampaignIds`, and `adGroupIds`. breakdown: Entity level to group an ad report by. - company_id: The unique identifier of a company. Mutually exclusive with `adCampaignId`, - `adGroupId`, and `adId`. Use with `breakdown` to fan out across every campaign, - ad group, or ad in the company without paging. + company_id: The unique identifier of a company. Mutually exclusive with `adCampaignIds`, + `adGroupIds`, and `adIds`. Use with `breakdown` to fan out across every + campaign, ad group, or ad in the company without paging. currency: ISO 4217 currency code to report `spend` in. Defaults to the company's ads reporting currency. @@ -123,9 +124,9 @@ def retrieve( { "from_": from_, "to": to, - "ad_campaign_id": ad_campaign_id, - "ad_group_id": ad_group_id, - "ad_id": ad_id, + "ad_campaign_ids": ad_campaign_ids, + "ad_group_ids": ad_group_ids, + "ad_ids": ad_ids, "breakdown": breakdown, "company_id": company_id, "currency": currency, @@ -165,9 +166,9 @@ async def retrieve( *, from_: Union[str, datetime], to: Union[str, datetime], - ad_campaign_id: Optional[str] | Omit = omit, - ad_group_id: Optional[str] | Omit = omit, - ad_id: Optional[str] | Omit = omit, + ad_campaign_ids: Optional[SequenceNotStr[str]] | Omit = omit, + ad_group_ids: Optional[SequenceNotStr[str]] | Omit = omit, + ad_ids: Optional[SequenceNotStr[str]] | Omit = omit, breakdown: Optional[Literal["campaign", "ad_group", "ad"]] | Omit = omit, company_id: Optional[str] | Omit = omit, currency: Optional[str] | Omit = omit, @@ -179,13 +180,14 @@ async def retrieve( extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> AdReportRetrieveResponse: - """Performance report for a company, ad campaign, ad group, or ad. + """Performance report for a company, ad campaigns, ad groups, or ads. - Always returns - aggregate `summary` totals. Set `granularity` (`daily`/`hourly`) to additionally - get a time series, or set `breakdown` (`campaign`/`ad_group`/`ad`) to - additionally get per-entity rows inside the requested scope. Exactly one of - `companyId`, `adCampaignId`, `adGroupId`, or `adId` must be provided. + Always + returns aggregate `summary` totals summed across the scope. Set `granularity` + (`daily`/`hourly`) to additionally get a time series, or set `breakdown` + (`campaign`/`ad_group`/`ad`) to additionally get per-entity rows inside the + requested scope. Exactly one of `companyId`, `adCampaignIds`, `adGroupIds`, or + `adIds` must be provided. Required permissions: @@ -196,20 +198,20 @@ async def retrieve( to: Inclusive end of the reporting window. - ad_campaign_id: The unique identifier of an ad campaign. Mutually exclusive with `companyId`, - `adGroupId`, and `adId`. + ad_campaign_ids: Scope the report to these ad campaigns (max 100); stats are summed across them. + Mutually exclusive with `companyId`, `adGroupIds`, and `adIds`. - ad_group_id: The unique identifier of an ad group. Mutually exclusive with `companyId`, - `adCampaignId`, and `adId`. + ad_group_ids: Scope the report to these ad groups (max 100); stats are summed across them. + Mutually exclusive with `companyId`, `adCampaignIds`, and `adIds`. - ad_id: The unique identifier of an ad. Mutually exclusive with `companyId`, - `adCampaignId`, and `adGroupId`. + ad_ids: Scope the report to these ads (max 100); stats are summed across them. Mutually + exclusive with `companyId`, `adCampaignIds`, and `adGroupIds`. breakdown: Entity level to group an ad report by. - company_id: The unique identifier of a company. Mutually exclusive with `adCampaignId`, - `adGroupId`, and `adId`. Use with `breakdown` to fan out across every campaign, - ad group, or ad in the company without paging. + company_id: The unique identifier of a company. Mutually exclusive with `adCampaignIds`, + `adGroupIds`, and `adIds`. Use with `breakdown` to fan out across every + campaign, ad group, or ad in the company without paging. currency: ISO 4217 currency code to report `spend` in. Defaults to the company's ads reporting currency. @@ -235,9 +237,9 @@ async def retrieve( { "from_": from_, "to": to, - "ad_campaign_id": ad_campaign_id, - "ad_group_id": ad_group_id, - "ad_id": ad_id, + "ad_campaign_ids": ad_campaign_ids, + "ad_group_ids": ad_group_ids, + "ad_ids": ad_ids, "breakdown": breakdown, "company_id": company_id, "currency": currency, diff --git a/src/whop_sdk/resources/ads.py b/src/whop_sdk/resources/ads.py index fc6351c6..97617892 100644 --- a/src/whop_sdk/resources/ads.py +++ b/src/whop_sdk/resources/ads.py @@ -8,9 +8,9 @@ import httpx -from ..types import ExternalAdStatus, ad_list_params -from .._types import Body, Omit, Query, Headers, NotGiven, omit, not_given -from .._utils import path_template, maybe_transform +from ..types import ExternalAdStatus, ad_list_params, ad_retrieve_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 ..types.ad import Ad from .._resource import SyncAPIResource, AsyncAPIResource @@ -55,6 +55,8 @@ def retrieve( self, id: str, *, + stats_from: Union[str, datetime, None] | Omit = omit, + stats_to: 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. extra_headers: Headers | None = None, @@ -70,6 +72,12 @@ def retrieve( - `ad_campaign:basic:read` Args: + stats_from: Inclusive start of the window for the ad's metric fields (spend, impressions, + …). Omit both statsFrom and statsTo for all-time stats. + + stats_to: Inclusive end of the window for the ad's metric fields. Omit both statsFrom and + statsTo for all-time stats. + extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -83,7 +91,17 @@ def retrieve( 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 + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "stats_from": stats_from, + "stats_to": stats_to, + }, + ad_retrieve_params.AdRetrieveParams, + ), ), cast_to=Ad, ) @@ -91,17 +109,21 @@ def retrieve( def list( self, *, + ad_campaign_id: Optional[str] | Omit = omit, + ad_campaign_ids: Optional[SequenceNotStr[str]] | Omit = omit, ad_group_id: Optional[str] | Omit = omit, + ad_group_ids: Optional[SequenceNotStr[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, + direction: Optional[Direction] | Omit = omit, first: Optional[int] | Omit = omit, - include_paused: Optional[bool] | Omit = omit, last: Optional[int] | Omit = omit, - order_by: Optional[Literal["spend", "roas"]] | Omit = omit, + order: Optional[Literal["created_at", "spend", "return_on_ad_spend"]] | Omit = omit, + order_by: Optional[Literal["spend", "return_on_ad_spend", "roas"]] | Omit = omit, order_direction: Optional[Direction] | Omit = omit, query: Optional[str] | Omit = omit, stats_from: Union[str, datetime, None] | Omit = omit, @@ -122,39 +144,51 @@ def list( - `ad_campaign:basic:read` Args: - ad_group_id: Filter by ad group. Provide exactly one of ad_group_id, campaign_id, or + ad_campaign_id: Filter by ad campaign. Provide exactly one of ad_group_id, ad_campaign_id, or + company_id. + + ad_campaign_ids: Only return ads belonging to these ad campaigns (max 100). Can be combined with + companyId or used on its own. + + ad_group_id: Filter by ad group. Provide exactly one of ad_group_id, ad_campaign_id, or company_id. + ad_group_ids: Only return ads belonging to these ad groups (max 100). Can be combined with + companyId or used on its own. + 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. + campaign_id: Filter by campaign. - company_id: Filter by company. Provide exactly one of ad_group_id, campaign_id, or + company_id: Filter by company. Provide exactly one of ad_group_id, ad_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. + direction: The direction of the sort. - include_paused: When false, excludes paused ads so pagination matches the dashboard's - hide-paused toggle. + first: Returns the first _n_ elements from the list. last: Returns the last _n_ elements from the list. - order_by: Columns that the listAds query can sort by. + order: The fields ad resources can be ordered by. + + order_by: Columns that the listAds query can sort by. Deprecated — use AdOrder. order_direction: The direction of the sort. - query: Case-insensitive substring match against the ad title or tag. + query: Case-insensitive substring match against the ad title or ID. - stats_from: Start of the stats date range used when order_by is a stats column. + stats_from: Inclusive start of the window for each ad's metric fields (spend, impressions, + …) and for stats-column sorting. Omit both statsFrom and statsTo for all-time + stats. - stats_to: End of the stats date range used when order_by is a stats column. + stats_to: Inclusive end of the window for each ad's metric fields and for stats-column + sorting. Omit both statsFrom and statsTo for all-time stats. status: The status of an external ad. @@ -176,16 +210,20 @@ def list( timeout=timeout, query=maybe_transform( { + "ad_campaign_id": ad_campaign_id, + "ad_campaign_ids": ad_campaign_ids, "ad_group_id": ad_group_id, + "ad_group_ids": ad_group_ids, "after": after, "before": before, "campaign_id": campaign_id, "company_id": company_id, "created_after": created_after, "created_before": created_before, + "direction": direction, "first": first, - "include_paused": include_paused, "last": last, + "order": order, "order_by": order_by, "order_direction": order_direction, "query": query, @@ -302,6 +340,8 @@ async def retrieve( self, id: str, *, + stats_from: Union[str, datetime, None] | Omit = omit, + stats_to: 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. extra_headers: Headers | None = None, @@ -317,6 +357,12 @@ async def retrieve( - `ad_campaign:basic:read` Args: + stats_from: Inclusive start of the window for the ad's metric fields (spend, impressions, + …). Omit both statsFrom and statsTo for all-time stats. + + stats_to: Inclusive end of the window for the ad's metric fields. Omit both statsFrom and + statsTo for all-time stats. + extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -330,7 +376,17 @@ async def retrieve( 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 + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=await async_maybe_transform( + { + "stats_from": stats_from, + "stats_to": stats_to, + }, + ad_retrieve_params.AdRetrieveParams, + ), ), cast_to=Ad, ) @@ -338,17 +394,21 @@ async def retrieve( def list( self, *, + ad_campaign_id: Optional[str] | Omit = omit, + ad_campaign_ids: Optional[SequenceNotStr[str]] | Omit = omit, ad_group_id: Optional[str] | Omit = omit, + ad_group_ids: Optional[SequenceNotStr[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, + direction: Optional[Direction] | Omit = omit, first: Optional[int] | Omit = omit, - include_paused: Optional[bool] | Omit = omit, last: Optional[int] | Omit = omit, - order_by: Optional[Literal["spend", "roas"]] | Omit = omit, + order: Optional[Literal["created_at", "spend", "return_on_ad_spend"]] | Omit = omit, + order_by: Optional[Literal["spend", "return_on_ad_spend", "roas"]] | Omit = omit, order_direction: Optional[Direction] | Omit = omit, query: Optional[str] | Omit = omit, stats_from: Union[str, datetime, None] | Omit = omit, @@ -369,39 +429,51 @@ def list( - `ad_campaign:basic:read` Args: - ad_group_id: Filter by ad group. Provide exactly one of ad_group_id, campaign_id, or + ad_campaign_id: Filter by ad campaign. Provide exactly one of ad_group_id, ad_campaign_id, or + company_id. + + ad_campaign_ids: Only return ads belonging to these ad campaigns (max 100). Can be combined with + companyId or used on its own. + + ad_group_id: Filter by ad group. Provide exactly one of ad_group_id, ad_campaign_id, or company_id. + ad_group_ids: Only return ads belonging to these ad groups (max 100). Can be combined with + companyId or used on its own. + 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. + campaign_id: Filter by campaign. - company_id: Filter by company. Provide exactly one of ad_group_id, campaign_id, or + company_id: Filter by company. Provide exactly one of ad_group_id, ad_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. + direction: The direction of the sort. - include_paused: When false, excludes paused ads so pagination matches the dashboard's - hide-paused toggle. + first: Returns the first _n_ elements from the list. last: Returns the last _n_ elements from the list. - order_by: Columns that the listAds query can sort by. + order: The fields ad resources can be ordered by. + + order_by: Columns that the listAds query can sort by. Deprecated — use AdOrder. order_direction: The direction of the sort. - query: Case-insensitive substring match against the ad title or tag. + query: Case-insensitive substring match against the ad title or ID. - stats_from: Start of the stats date range used when order_by is a stats column. + stats_from: Inclusive start of the window for each ad's metric fields (spend, impressions, + …) and for stats-column sorting. Omit both statsFrom and statsTo for all-time + stats. - stats_to: End of the stats date range used when order_by is a stats column. + stats_to: Inclusive end of the window for each ad's metric fields and for stats-column + sorting. Omit both statsFrom and statsTo for all-time stats. status: The status of an external ad. @@ -423,16 +495,20 @@ def list( timeout=timeout, query=maybe_transform( { + "ad_campaign_id": ad_campaign_id, + "ad_campaign_ids": ad_campaign_ids, "ad_group_id": ad_group_id, + "ad_group_ids": ad_group_ids, "after": after, "before": before, "campaign_id": campaign_id, "company_id": company_id, "created_after": created_after, "created_before": created_before, + "direction": direction, "first": first, - "include_paused": include_paused, "last": last, + "order": order, "order_by": order_by, "order_direction": order_direction, "query": query, diff --git a/src/whop_sdk/types/__init__.py b/src/whop_sdk/types/__init__.py index c41c9b9b..8e3b5927 100644 --- a/src/whop_sdk/types/__init__.py +++ b/src/whop_sdk/types/__init__.py @@ -122,6 +122,7 @@ from .withdrawal_speeds import WithdrawalSpeeds as WithdrawalSpeeds from .withdrawal_status import WithdrawalStatus as WithdrawalStatus from .ad_campaign_status import AdCampaignStatus as AdCampaignStatus +from .ad_retrieve_params import AdRetrieveParams as AdRetrieveParams from .bounty_list_params import BountyListParams as BountyListParams from .course_list_params import CourseListParams as CourseListParams from .dispute_alert_type import DisputeAlertType as DisputeAlertType @@ -252,6 +253,7 @@ from .webhook_create_response import WebhookCreateResponse as WebhookCreateResponse from .webhook_delete_response import WebhookDeleteResponse as WebhookDeleteResponse from .ad_group_delete_response import AdGroupDeleteResponse as AdGroupDeleteResponse +from .ad_group_retrieve_params import AdGroupRetrieveParams as AdGroupRetrieveParams 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 @@ -317,6 +319,7 @@ from .swap_create_quote_response import SwapCreateQuoteResponse as SwapCreateQuoteResponse from .user_check_access_response import UserCheckAccessResponse as UserCheckAccessResponse from .verification_list_response import VerificationListResponse as VerificationListResponse +from .ad_campaign_retrieve_params import AdCampaignRetrieveParams as AdCampaignRetrieveParams from .ad_report_retrieve_response import AdReportRetrieveResponse as AdReportRetrieveResponse from .authorized_user_list_params import AuthorizedUserListParams as AuthorizedUserListParams from .course_lesson_create_params import CourseLessonCreateParams as CourseLessonCreateParams diff --git a/src/whop_sdk/types/ad.py b/src/whop_sdk/types/ad.py index e2a1a0bd..f88c0755 100644 --- a/src/whop_sdk/types/ad.py +++ b/src/whop_sdk/types/ad.py @@ -1,13 +1,15 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -from typing import Optional +from typing import List, Optional from datetime import datetime +from typing_extensions import Literal from .._models import BaseModel +from .shared.currency import Currency from .external_ad_status import ExternalAdStatus from .ad_campaign_platform import AdCampaignPlatform -__all__ = ["Ad", "AdCampaign", "AdGroup"] +__all__ = ["Ad", "AdCampaign", "AdGroup", "Issue"] class AdCampaign(BaseModel): @@ -24,6 +26,37 @@ class AdGroup(BaseModel): """The unique identifier for this ad group.""" +class Issue(BaseModel): + """A platform-reported issue on an ad object (rejection, policy flag, etc.).""" + + created_at: datetime + """When the issue was first reported.""" + + error_code: Optional[str] = None + """Platform-specific error code.""" + + error_message: Optional[str] = None + """Full error detail from the platform.""" + + error_summary: str + """Short description of the issue.""" + + resolution_status: Literal["open", "resolved", "acknowledged"] + """Current resolution status.""" + + resource_id: Optional[str] = None + """The Whop ID of the ad object this issue is on (the ad, ad group, or campaign). + + Null when the issue isn't tied to a local object. + """ + + resource_type: str + """The kind of ad object this issue is on: `ad`, `ad_group`, or `ad_campaign`. + + Pairs with `resourceId`. + """ + + class Ad(BaseModel): """An ad belonging to an ad group.""" @@ -36,17 +69,101 @@ class Ad(BaseModel): ad_group: AdGroup """The parent ad group this ad belongs to.""" + click_through_rate: float + """Click-through rate as a fraction of impressions (clicks / impressions, 0–1).""" + + clicks: int + """Total clicks on this ad in the stats window.""" + + cost_per_click: float + """Cost per click in dollars (spend / clicks). 0 when there are no clicks.""" + + cost_per_lead: Optional[float] = None + """Cost in dollars per Whop pixel-attributed lead (spend / leads). + + 0 when leads are tracked but none happened yet; null when leads are not a goal + and none were attributed. + """ + + cost_per_mille: float + """Cost per 1,000 impressions in dollars (spend / impressions × 1000). + + 0 when there are no impressions. + """ + + cost_per_purchase: Optional[float] = None + """Cost in dollars per Whop pixel-attributed purchase (spend / purchases). + + 0 when purchases are tracked but none happened yet; null when purchases are not + a goal and none were attributed. + """ + + cost_per_result: Optional[float] = None + """Cost in dollars per optimization result (spend / results). + + 0 when a result is being optimized for but none happened yet; null when nothing + is being optimized for. + """ + created_at: datetime """When the ad was created.""" + frequency: Optional[float] = None + """ + Average number of times each person saw an ad (impressions / reach), as reported + by the platform. + """ + + impressions: int + """Total impressions (views) on this ad in the stats window.""" + + issues: List[Issue] + """Open platform issues affecting this ad, deduplicated per object. + + Empty when there are none. + """ + + leads: int + """Number of Whop pixel-attributed leads (last-click) in the stats window.""" + platform: AdCampaignPlatform """The external ad platform this ad is running on (e.g., meta, tiktok).""" + purchase_value: float + """Total USD value of Whop pixel-attributed purchases in the stats window.""" + + purchases: int + """Number of Whop pixel-attributed purchases (last-click) in the stats window.""" + + reach: int + """Unique users reached in the stats window (deduplicated by the platform).""" + + return_on_ad_spend: float + """ + Return on ad spend as a ratio (purchaseValue / spend) — 2.5 means $2.50 of + attributed purchase value per $1 spent. 0 when there is no spend. + """ + + spend: float + """Amount charged in dollars in the stats window.""" + + spend_currency: Optional[Currency] = None + """The available currencies on the platform""" + status: ExternalAdStatus """Current delivery status of the ad.""" title: Optional[str] = None """The display title of the ad. Falls back to the creative set caption when unset.""" + unique_click_through_rate: Optional[float] = None + """ + Unique click-through rate as a fraction of impressions (unique clicks / + impressions, 0–1). + """ + + unique_clicks: int + """Unique clicks (deduplicated by the platform) in the stats window.""" + updated_at: datetime """When the ad was last updated.""" diff --git a/src/whop_sdk/types/ad_campaign.py b/src/whop_sdk/types/ad_campaign.py index 6d201157..2d43d055 100644 --- a/src/whop_sdk/types/ad_campaign.py +++ b/src/whop_sdk/types/ad_campaign.py @@ -6,67 +6,43 @@ from .._models import BaseModel from .ad_budget_type import AdBudgetType +from .shared.currency import Currency from .ad_campaign_status import AdCampaignStatus from .ad_campaign_platform import AdCampaignPlatform -__all__ = ["AdCampaign", "CreatedByUser", "MetaConfig"] +__all__ = ["AdCampaign", "Issue"] -class CreatedByUser(BaseModel): - """The user who created this ad campaign.""" +class Issue(BaseModel): + """A platform-reported issue on an ad object (rejection, policy flag, etc.).""" - 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.""" + created_at: datetime + """When the issue was first reported.""" + error_code: Optional[str] = None + """Platform-specific error code.""" -class MetaConfig(BaseModel): - """Meta-specific campaign configuration (objective, budget mode, etc.). + error_message: Optional[str] = None + """Full error detail from the platform.""" - Null for non-Meta campaigns. - """ + error_summary: str + """Short description of the issue.""" - bid_amount: Optional[int] = None - """Bid cap amount in cents. Only used when bid_strategy is bid_cap.""" + resolution_status: Literal["open", "resolved", "acknowledged"] + """Current resolution status.""" - bid_strategy: Optional[Literal["lowest_cost", "bid_cap", "cost_cap"]] = None - """The bidding strategy used to optimize spend for this campaign.""" + resource_id: Optional[str] = None + """The Whop ID of the ad object this issue is on (the ad, ad group, or campaign). - budget_optimization: Optional[bool] = None - """ - Whether campaign budget optimization (CBO) is enabled, allowing the platform to - distribute budget across ad groups. + Null when the issue isn't tied to a local object. """ - effective_status: Optional[Literal["active", "paused", "deleted", "in_review", "rejected", "with_issues"]] = None - """ - The actual delivery status, accounting for platform overrides (e.g., in_review, - rejected). - """ - - end_time: Optional[str] = None - """The scheduled end time of the campaign (ISO8601).""" + resource_type: str + """The kind of ad object this issue is on: `ad`, `ad_group`, or `ad_campaign`. - 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). + Pairs with `resourceId`. """ - 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 AdCampaign(BaseModel): """An advertising campaign running on an external platform or within Whop.""" @@ -80,29 +56,101 @@ class AdCampaign(BaseModel): budget_type: Optional[AdBudgetType] = None """The budget type for an ad campaign or ad group.""" + click_through_rate: float + """Click-through rate as a fraction of impressions (clicks / impressions, 0–1).""" + + clicks: int + """Total clicks on the campaign's ads in the stats window.""" + + cost_per_click: float + """Cost per click in dollars (spend / clicks). 0 when there are no clicks.""" + + cost_per_lead: Optional[float] = None + """Cost in dollars per Whop pixel-attributed lead (spend / leads). + + 0 when leads are tracked but none happened yet; null when leads are not a goal + and none were attributed. + """ + + cost_per_mille: float + """Cost per 1,000 impressions in dollars (spend / impressions × 1000). + + 0 when there are no impressions. + """ + + cost_per_purchase: Optional[float] = None + """Cost in dollars per Whop pixel-attributed purchase (spend / purchases). + + 0 when purchases are tracked but none happened yet; null when purchases are not + a goal and none were attributed. + """ + + cost_per_result: Optional[float] = None + """Cost in dollars per optimization result (spend / results). + + 0 when a result is being optimized for but none happened yet; null when nothing + is being optimized for. + """ + created_at: datetime """When the ad campaign was created.""" - created_by_user: CreatedByUser - """The user who created this ad campaign.""" + frequency: Optional[float] = None + """ + Average number of times each person saw an ad (impressions / reach), as reported + by the platform. + """ - meta_config: Optional[MetaConfig] = None - """Meta-specific campaign configuration (objective, budget mode, etc.). + impressions: int + """Total impressions (views) on the campaign's ads in the stats window.""" - Null for non-Meta campaigns. + issues: List[Issue] + """ + Open platform issues affecting this campaign and its descendant ad groups and + ads, deduplicated per object. Empty when there are none. """ + leads: int + """Number of Whop pixel-attributed leads (last-click) in the stats window.""" + platform: AdCampaignPlatform """The external ad platform this campaign is running on (e.g., meta, tiktok).""" + purchase_value: float + """Total USD value of Whop pixel-attributed purchases in the stats window.""" + + purchases: int + """Number of Whop pixel-attributed purchases (last-click) in the stats window.""" + + reach: int + """Unique users reached in the stats window (deduplicated by the platform).""" + + return_on_ad_spend: float + """ + Return on ad spend as a ratio (purchaseValue / spend) — 2.5 means $2.50 of + attributed purchase value per $1 spent. 0 when there is no spend. + """ + + spend: float + """Amount charged in dollars in the stats window.""" + + spend_currency: Optional[Currency] = None + """The available currencies on the platform""" + status: AdCampaignStatus - """Current status of the campaign (active, paused, or inactive).""" + """Current status of the campaign.""" title: str """The campaign name shown in the Whop dashboard.""" - total_spend: float - """Total amount spent in dollars.""" + unique_click_through_rate: Optional[float] = None + """ + Unique click-through rate as a fraction of impressions (unique clicks / + impressions, 0–1). + """ + + unique_clicks: int + """Unique clicks (deduplicated by the platform) in the stats window.""" updated_at: datetime """When 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 index ab696839..5c3dce5c 100644 --- a/src/whop_sdk/types/ad_campaign_list_params.py +++ b/src/whop_sdk/types/ad_campaign_list_params.py @@ -35,7 +35,19 @@ class AdCampaignListParams(TypedDict, total=False): """Returns the last _n_ elements from the list.""" query: Optional[str] - """Case-insensitive substring match against the campaign title.""" + """Case-insensitive substring match against the campaign title or ID.""" + + stats_from: Annotated[Union[str, datetime, None], PropertyInfo(format="iso8601")] + """ + Inclusive start of the window for each campaign's metric fields (spend, + impressions, …). Omit both statsFrom and statsTo for all-time stats. + """ + + stats_to: Annotated[Union[str, datetime, None], PropertyInfo(format="iso8601")] + """Inclusive end of the window for each campaign's metric fields. + + Omit both statsFrom and statsTo for all-time stats. + """ status: Optional[AdCampaignStatus] """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 index 7123408d..01227bbe 100644 --- a/src/whop_sdk/types/ad_campaign_list_response.py +++ b/src/whop_sdk/types/ad_campaign_list_response.py @@ -1,14 +1,47 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -from typing import Optional +from typing import List, Optional from datetime import datetime +from typing_extensions import Literal from .._models import BaseModel from .ad_budget_type import AdBudgetType +from .shared.currency import Currency from .ad_campaign_status import AdCampaignStatus from .ad_campaign_platform import AdCampaignPlatform -__all__ = ["AdCampaignListResponse"] +__all__ = ["AdCampaignListResponse", "Issue"] + + +class Issue(BaseModel): + """A platform-reported issue on an ad object (rejection, policy flag, etc.).""" + + created_at: datetime + """When the issue was first reported.""" + + error_code: Optional[str] = None + """Platform-specific error code.""" + + error_message: Optional[str] = None + """Full error detail from the platform.""" + + error_summary: str + """Short description of the issue.""" + + resolution_status: Literal["open", "resolved", "acknowledged"] + """Current resolution status.""" + + resource_id: Optional[str] = None + """The Whop ID of the ad object this issue is on (the ad, ad group, or campaign). + + Null when the issue isn't tied to a local object. + """ + + resource_type: str + """The kind of ad object this issue is on: `ad`, `ad_group`, or `ad_campaign`. + + Pairs with `resourceId`. + """ class AdCampaignListResponse(BaseModel): @@ -23,20 +56,101 @@ class AdCampaignListResponse(BaseModel): budget_type: Optional[AdBudgetType] = None """The budget type for an ad campaign or ad group.""" + click_through_rate: float + """Click-through rate as a fraction of impressions (clicks / impressions, 0–1).""" + + clicks: int + """Total clicks on the campaign's ads in the stats window.""" + + cost_per_click: float + """Cost per click in dollars (spend / clicks). 0 when there are no clicks.""" + + cost_per_lead: Optional[float] = None + """Cost in dollars per Whop pixel-attributed lead (spend / leads). + + 0 when leads are tracked but none happened yet; null when leads are not a goal + and none were attributed. + """ + + cost_per_mille: float + """Cost per 1,000 impressions in dollars (spend / impressions × 1000). + + 0 when there are no impressions. + """ + + cost_per_purchase: Optional[float] = None + """Cost in dollars per Whop pixel-attributed purchase (spend / purchases). + + 0 when purchases are tracked but none happened yet; null when purchases are not + a goal and none were attributed. + """ + + cost_per_result: Optional[float] = None + """Cost in dollars per optimization result (spend / results). + + 0 when a result is being optimized for but none happened yet; null when nothing + is being optimized for. + """ + created_at: datetime """When the ad campaign was created.""" + frequency: Optional[float] = None + """ + Average number of times each person saw an ad (impressions / reach), as reported + by the platform. + """ + + impressions: int + """Total impressions (views) on the campaign's ads in the stats window.""" + + issues: List[Issue] + """ + Open platform issues affecting this campaign and its descendant ad groups and + ads, deduplicated per object. Empty when there are none. + """ + + leads: int + """Number of Whop pixel-attributed leads (last-click) in the stats window.""" + platform: AdCampaignPlatform """The external ad platform this campaign is running on (e.g., meta, tiktok).""" + purchase_value: float + """Total USD value of Whop pixel-attributed purchases in the stats window.""" + + purchases: int + """Number of Whop pixel-attributed purchases (last-click) in the stats window.""" + + reach: int + """Unique users reached in the stats window (deduplicated by the platform).""" + + return_on_ad_spend: float + """ + Return on ad spend as a ratio (purchaseValue / spend) — 2.5 means $2.50 of + attributed purchase value per $1 spent. 0 when there is no spend. + """ + + spend: float + """Amount charged in dollars in the stats window.""" + + spend_currency: Optional[Currency] = None + """The available currencies on the platform""" + status: AdCampaignStatus - """Current status of the campaign (active, paused, or inactive).""" + """Current status of the campaign.""" title: str """The campaign name shown in the Whop dashboard.""" - total_spend: float - """Total amount spent in dollars.""" + unique_click_through_rate: Optional[float] = None + """ + Unique click-through rate as a fraction of impressions (unique clicks / + impressions, 0–1). + """ + + unique_clicks: int + """Unique clicks (deduplicated by the platform) in the stats window.""" updated_at: datetime """When the ad campaign was last updated.""" diff --git a/src/whop_sdk/types/ad_campaign_retrieve_params.py b/src/whop_sdk/types/ad_campaign_retrieve_params.py new file mode 100644 index 00000000..4bd49c3b --- /dev/null +++ b/src/whop_sdk/types/ad_campaign_retrieve_params.py @@ -0,0 +1,25 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Union +from datetime import datetime +from typing_extensions import Annotated, TypedDict + +from .._utils import PropertyInfo + +__all__ = ["AdCampaignRetrieveParams"] + + +class AdCampaignRetrieveParams(TypedDict, total=False): + stats_from: Annotated[Union[str, datetime, None], PropertyInfo(format="iso8601")] + """ + Inclusive start of the window for the campaign's metric fields (spend, + impressions, …). Omit both statsFrom and statsTo for all-time stats. + """ + + stats_to: Annotated[Union[str, datetime, None], PropertyInfo(format="iso8601")] + """Inclusive end of the window for the campaign's metric fields. + + Omit both statsFrom and statsTo for all-time stats. + """ diff --git a/src/whop_sdk/types/ad_group.py b/src/whop_sdk/types/ad_group.py index 384dbf5f..34f18caa 100644 --- a/src/whop_sdk/types/ad_group.py +++ b/src/whop_sdk/types/ad_group.py @@ -1,14 +1,16 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -from typing import Optional +from typing import List, Optional from datetime import datetime +from typing_extensions import Literal from .._models import BaseModel from .ad_budget_type import AdBudgetType from .ad_group_status import AdGroupStatus +from .shared.currency import Currency from .ad_campaign_platform import AdCampaignPlatform -__all__ = ["AdGroup", "AdCampaign"] +__all__ = ["AdGroup", "AdCampaign", "Issue"] class AdCampaign(BaseModel): @@ -18,8 +20,39 @@ class AdCampaign(BaseModel): """The unique identifier for this ad campaign.""" +class Issue(BaseModel): + """A platform-reported issue on an ad object (rejection, policy flag, etc.).""" + + created_at: datetime + """When the issue was first reported.""" + + error_code: Optional[str] = None + """Platform-specific error code.""" + + error_message: Optional[str] = None + """Full error detail from the platform.""" + + error_summary: str + """Short description of the issue.""" + + resolution_status: Literal["open", "resolved", "acknowledged"] + """Current resolution status.""" + + resource_id: Optional[str] = None + """The Whop ID of the ad object this issue is on (the ad, ad group, or campaign). + + Null when the issue isn't tied to a local object. + """ + + resource_type: str + """The kind of ad object this issue is on: `ad`, `ad_group`, or `ad_campaign`. + + Pairs with `resourceId`. + """ + + class AdGroup(BaseModel): - """An ad group (ad set) belonging to an ad campaign.""" + """An ad group belonging to an ad campaign.""" id: str """The unique identifier for this ad group.""" @@ -33,17 +66,101 @@ class AdGroup(BaseModel): budget_type: Optional[AdBudgetType] = None """The budget type for an ad campaign or ad group.""" + click_through_rate: float + """Click-through rate as a fraction of impressions (clicks / impressions, 0–1).""" + + clicks: int + """Total clicks on this ad group's ads in the stats window.""" + + cost_per_click: float + """Cost per click in dollars (spend / clicks). 0 when there are no clicks.""" + + cost_per_lead: Optional[float] = None + """Cost in dollars per Whop pixel-attributed lead (spend / leads). + + 0 when leads are tracked but none happened yet; null when leads are not a goal + and none were attributed. + """ + + cost_per_mille: float + """Cost per 1,000 impressions in dollars (spend / impressions × 1000). + + 0 when there are no impressions. + """ + + cost_per_purchase: Optional[float] = None + """Cost in dollars per Whop pixel-attributed purchase (spend / purchases). + + 0 when purchases are tracked but none happened yet; null when purchases are not + a goal and none were attributed. + """ + + cost_per_result: Optional[float] = None + """Cost in dollars per optimization result (spend / results). + + 0 when a result is being optimized for but none happened yet; null when nothing + is being optimized for. + """ + created_at: datetime """When the ad group was created.""" + frequency: Optional[float] = None + """ + Average number of times each person saw an ad (impressions / reach), as reported + by the platform. + """ + + impressions: int + """Total impressions (views) on this ad group's ads in the stats window.""" + + issues: List[Issue] + """ + Open platform issues affecting this ad group and its descendant ads, + deduplicated per object. Empty when there are none. + """ + + leads: int + """Number of Whop pixel-attributed leads (last-click) in the stats window.""" + platform: AdCampaignPlatform """The external ad platform this ad group is running on (e.g., meta, tiktok).""" + purchase_value: float + """Total USD value of Whop pixel-attributed purchases in the stats window.""" + + purchases: int + """Number of Whop pixel-attributed purchases (last-click) in the stats window.""" + + reach: int + """Unique users reached in the stats window (deduplicated by the platform).""" + + return_on_ad_spend: float + """ + Return on ad spend as a ratio (purchaseValue / spend) — 2.5 means $2.50 of + attributed purchase value per $1 spent. 0 when there is no spend. + """ + + spend: float + """Amount charged in dollars in the stats window.""" + + spend_currency: Optional[Currency] = None + """The available currencies on the platform""" + status: AdGroupStatus """Current operational status of the ad group.""" title: Optional[str] = None - """Human-readable name shown on the external platform.""" + """The ad group name shown in the Whop dashboard.""" + + unique_click_through_rate: Optional[float] = None + """ + Unique click-through rate as a fraction of impressions (unique clicks / + impressions, 0–1). + """ + + unique_clicks: int + """Unique clicks (deduplicated by the platform) in the stats window.""" updated_at: datetime """When the ad group was last updated.""" diff --git a/src/whop_sdk/types/ad_group_list_params.py b/src/whop_sdk/types/ad_group_list_params.py index 9bda52fb..5e92b127 100644 --- a/src/whop_sdk/types/ad_group_list_params.py +++ b/src/whop_sdk/types/ad_group_list_params.py @@ -6,6 +6,7 @@ from datetime import datetime from typing_extensions import Annotated, TypedDict +from .._types import SequenceNotStr from .._utils import PropertyInfo from .ad_group_status import AdGroupStatus @@ -13,6 +14,15 @@ class AdGroupListParams(TypedDict, total=False): + ad_campaign_id: Optional[str] + """Filter by ad campaign. Provide exactly one of ad_campaign_id or company_id.""" + + ad_campaign_ids: Optional[SequenceNotStr[str]] + """Only return ad groups belonging to these ad campaigns (max 100). + + Can be combined with companyId or used on its own. + """ + after: Optional[str] """Returns the elements in the list that come after the specified cursor.""" @@ -20,10 +30,10 @@ class AdGroupListParams(TypedDict, total=False): """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.""" + """Filter by campaign.""" company_id: Optional[str] - """Filter by company. Provide exactly one of campaign_id or company_id.""" + """Filter by company. Provide companyId or adCampaignIds.""" created_after: Annotated[Union[str, datetime, None], PropertyInfo(format="iso8601")] """Only return ad groups created after this timestamp.""" @@ -34,17 +44,23 @@ class AdGroupListParams(TypedDict, total=False): first: Optional[int] """Returns the first _n_ elements from the list.""" - include_paused: Optional[bool] - """ - When false, excludes paused ad groups so pagination matches the dashboard's - hide-paused toggle. - """ - last: Optional[int] """Returns the last _n_ elements from the list.""" query: Optional[str] - """Case-insensitive substring match against the ad group name.""" + """Case-insensitive substring match against the ad group name or ID.""" + + stats_from: Annotated[Union[str, datetime, None], PropertyInfo(format="iso8601")] + """ + Inclusive start of the window for each ad group's metric fields (spend, + impressions, …). Omit both statsFrom and statsTo for all-time stats. + """ + + stats_to: Annotated[Union[str, datetime, None], PropertyInfo(format="iso8601")] + """Inclusive end of the window for each ad group's metric fields. + + Omit both statsFrom and statsTo for all-time stats. + """ status: Optional[AdGroupStatus] """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 index dac01854..35deef5d 100644 --- a/src/whop_sdk/types/ad_group_list_response.py +++ b/src/whop_sdk/types/ad_group_list_response.py @@ -1,14 +1,16 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -from typing import Optional +from typing import List, Optional from datetime import datetime +from typing_extensions import Literal from .._models import BaseModel from .ad_budget_type import AdBudgetType from .ad_group_status import AdGroupStatus +from .shared.currency import Currency from .ad_campaign_platform import AdCampaignPlatform -__all__ = ["AdGroupListResponse", "AdCampaign"] +__all__ = ["AdGroupListResponse", "AdCampaign", "Issue"] class AdCampaign(BaseModel): @@ -18,8 +20,39 @@ class AdCampaign(BaseModel): """The unique identifier for this ad campaign.""" +class Issue(BaseModel): + """A platform-reported issue on an ad object (rejection, policy flag, etc.).""" + + created_at: datetime + """When the issue was first reported.""" + + error_code: Optional[str] = None + """Platform-specific error code.""" + + error_message: Optional[str] = None + """Full error detail from the platform.""" + + error_summary: str + """Short description of the issue.""" + + resolution_status: Literal["open", "resolved", "acknowledged"] + """Current resolution status.""" + + resource_id: Optional[str] = None + """The Whop ID of the ad object this issue is on (the ad, ad group, or campaign). + + Null when the issue isn't tied to a local object. + """ + + resource_type: str + """The kind of ad object this issue is on: `ad`, `ad_group`, or `ad_campaign`. + + Pairs with `resourceId`. + """ + + class AdGroupListResponse(BaseModel): - """An ad group (ad set) belonging to an ad campaign.""" + """An ad group belonging to an ad campaign.""" id: str """The unique identifier for this ad group.""" @@ -33,17 +66,101 @@ class AdGroupListResponse(BaseModel): budget_type: Optional[AdBudgetType] = None """The budget type for an ad campaign or ad group.""" + click_through_rate: float + """Click-through rate as a fraction of impressions (clicks / impressions, 0–1).""" + + clicks: int + """Total clicks on this ad group's ads in the stats window.""" + + cost_per_click: float + """Cost per click in dollars (spend / clicks). 0 when there are no clicks.""" + + cost_per_lead: Optional[float] = None + """Cost in dollars per Whop pixel-attributed lead (spend / leads). + + 0 when leads are tracked but none happened yet; null when leads are not a goal + and none were attributed. + """ + + cost_per_mille: float + """Cost per 1,000 impressions in dollars (spend / impressions × 1000). + + 0 when there are no impressions. + """ + + cost_per_purchase: Optional[float] = None + """Cost in dollars per Whop pixel-attributed purchase (spend / purchases). + + 0 when purchases are tracked but none happened yet; null when purchases are not + a goal and none were attributed. + """ + + cost_per_result: Optional[float] = None + """Cost in dollars per optimization result (spend / results). + + 0 when a result is being optimized for but none happened yet; null when nothing + is being optimized for. + """ + created_at: datetime """When the ad group was created.""" + frequency: Optional[float] = None + """ + Average number of times each person saw an ad (impressions / reach), as reported + by the platform. + """ + + impressions: int + """Total impressions (views) on this ad group's ads in the stats window.""" + + issues: List[Issue] + """ + Open platform issues affecting this ad group and its descendant ads, + deduplicated per object. Empty when there are none. + """ + + leads: int + """Number of Whop pixel-attributed leads (last-click) in the stats window.""" + platform: AdCampaignPlatform """The external ad platform this ad group is running on (e.g., meta, tiktok).""" + purchase_value: float + """Total USD value of Whop pixel-attributed purchases in the stats window.""" + + purchases: int + """Number of Whop pixel-attributed purchases (last-click) in the stats window.""" + + reach: int + """Unique users reached in the stats window (deduplicated by the platform).""" + + return_on_ad_spend: float + """ + Return on ad spend as a ratio (purchaseValue / spend) — 2.5 means $2.50 of + attributed purchase value per $1 spent. 0 when there is no spend. + """ + + spend: float + """Amount charged in dollars in the stats window.""" + + spend_currency: Optional[Currency] = None + """The available currencies on the platform""" + status: AdGroupStatus """Current operational status of the ad group.""" title: Optional[str] = None - """Human-readable name shown on the external platform.""" + """The ad group name shown in the Whop dashboard.""" + + unique_click_through_rate: Optional[float] = None + """ + Unique click-through rate as a fraction of impressions (unique clicks / + impressions, 0–1). + """ + + unique_clicks: int + """Unique clicks (deduplicated by the platform) in the stats window.""" updated_at: datetime """When the ad group was last updated.""" diff --git a/src/whop_sdk/types/ad_group_retrieve_params.py b/src/whop_sdk/types/ad_group_retrieve_params.py new file mode 100644 index 00000000..54c57f2c --- /dev/null +++ b/src/whop_sdk/types/ad_group_retrieve_params.py @@ -0,0 +1,25 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Union +from datetime import datetime +from typing_extensions import Annotated, TypedDict + +from .._utils import PropertyInfo + +__all__ = ["AdGroupRetrieveParams"] + + +class AdGroupRetrieveParams(TypedDict, total=False): + stats_from: Annotated[Union[str, datetime, None], PropertyInfo(format="iso8601")] + """ + Inclusive start of the window for the ad group's metric fields (spend, + impressions, …). Omit both statsFrom and statsTo for all-time stats. + """ + + stats_to: Annotated[Union[str, datetime, None], PropertyInfo(format="iso8601")] + """Inclusive end of the window for the ad group's metric fields. + + Omit both statsFrom and statsTo for all-time stats. + """ diff --git a/src/whop_sdk/types/ad_list_params.py b/src/whop_sdk/types/ad_list_params.py index f89c77d2..f436a972 100644 --- a/src/whop_sdk/types/ad_list_params.py +++ b/src/whop_sdk/types/ad_list_params.py @@ -6,6 +6,7 @@ from datetime import datetime from typing_extensions import Literal, Annotated, TypedDict +from .._types import SequenceNotStr from .._utils import PropertyInfo from .shared.direction import Direction from .external_ad_status import ExternalAdStatus @@ -14,10 +15,28 @@ class AdListParams(TypedDict, total=False): + ad_campaign_id: Optional[str] + """Filter by ad campaign. + + Provide exactly one of ad_group_id, ad_campaign_id, or company_id. + """ + + ad_campaign_ids: Optional[SequenceNotStr[str]] + """Only return ads belonging to these ad campaigns (max 100). + + Can be combined with companyId or used on its own. + """ + ad_group_id: Optional[str] """Filter by ad group. - Provide exactly one of ad_group_id, campaign_id, or company_id. + Provide exactly one of ad_group_id, ad_campaign_id, or company_id. + """ + + ad_group_ids: Optional[SequenceNotStr[str]] + """Only return ads belonging to these ad groups (max 100). + + Can be combined with companyId or used on its own. """ after: Optional[str] @@ -27,15 +46,12 @@ class AdListParams(TypedDict, total=False): """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. - """ + """Filter by campaign.""" company_id: Optional[str] """Filter by company. - Provide exactly one of ad_group_id, campaign_id, or company_id. + Provide exactly one of ad_group_id, ad_campaign_id, or company_id. """ created_after: Annotated[Union[str, datetime, None], PropertyInfo(format="iso8601")] @@ -44,32 +60,39 @@ class AdListParams(TypedDict, total=False): created_before: Annotated[Union[str, datetime, None], PropertyInfo(format="iso8601")] """Only return ads created before this timestamp.""" + direction: Optional[Direction] + """The direction of the sort.""" + first: Optional[int] """Returns the first _n_ elements from the list.""" - include_paused: Optional[bool] - """ - When false, excludes paused ads so pagination matches the dashboard's - hide-paused toggle. - """ - last: Optional[int] """Returns the last _n_ elements from the list.""" - order_by: Optional[Literal["spend", "roas"]] - """Columns that the listAds query can sort by.""" + order: Optional[Literal["created_at", "spend", "return_on_ad_spend"]] + """The fields ad resources can be ordered by.""" + + order_by: Optional[Literal["spend", "return_on_ad_spend", "roas"]] + """Columns that the listAds query can sort by. Deprecated — use AdOrder.""" order_direction: Optional[Direction] """The direction of the sort.""" query: Optional[str] - """Case-insensitive substring match against the ad title or tag.""" + """Case-insensitive substring match against the ad title or ID.""" stats_from: Annotated[Union[str, datetime, None], PropertyInfo(format="iso8601")] - """Start of the stats date range used when order_by is a stats column.""" + """ + Inclusive start of the window for each ad's metric fields (spend, impressions, + …) and for stats-column sorting. Omit both statsFrom and statsTo for all-time + stats. + """ stats_to: Annotated[Union[str, datetime, None], PropertyInfo(format="iso8601")] - """End of the stats date range used when order_by is a stats column.""" + """ + Inclusive end of the window for each ad's metric fields and for stats-column + sorting. Omit both statsFrom and statsTo for all-time stats. + """ status: Optional[ExternalAdStatus] """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 index f6a13bac..7c956fb3 100644 --- a/src/whop_sdk/types/ad_list_response.py +++ b/src/whop_sdk/types/ad_list_response.py @@ -1,13 +1,15 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -from typing import Optional +from typing import List, Optional from datetime import datetime +from typing_extensions import Literal from .._models import BaseModel +from .shared.currency import Currency from .external_ad_status import ExternalAdStatus from .ad_campaign_platform import AdCampaignPlatform -__all__ = ["AdListResponse", "AdCampaign", "AdGroup"] +__all__ = ["AdListResponse", "AdCampaign", "AdGroup", "Issue"] class AdCampaign(BaseModel): @@ -24,6 +26,37 @@ class AdGroup(BaseModel): """The unique identifier for this ad group.""" +class Issue(BaseModel): + """A platform-reported issue on an ad object (rejection, policy flag, etc.).""" + + created_at: datetime + """When the issue was first reported.""" + + error_code: Optional[str] = None + """Platform-specific error code.""" + + error_message: Optional[str] = None + """Full error detail from the platform.""" + + error_summary: str + """Short description of the issue.""" + + resolution_status: Literal["open", "resolved", "acknowledged"] + """Current resolution status.""" + + resource_id: Optional[str] = None + """The Whop ID of the ad object this issue is on (the ad, ad group, or campaign). + + Null when the issue isn't tied to a local object. + """ + + resource_type: str + """The kind of ad object this issue is on: `ad`, `ad_group`, or `ad_campaign`. + + Pairs with `resourceId`. + """ + + class AdListResponse(BaseModel): """An ad belonging to an ad group.""" @@ -36,17 +69,101 @@ class AdListResponse(BaseModel): ad_group: AdGroup """The parent ad group this ad belongs to.""" + click_through_rate: float + """Click-through rate as a fraction of impressions (clicks / impressions, 0–1).""" + + clicks: int + """Total clicks on this ad in the stats window.""" + + cost_per_click: float + """Cost per click in dollars (spend / clicks). 0 when there are no clicks.""" + + cost_per_lead: Optional[float] = None + """Cost in dollars per Whop pixel-attributed lead (spend / leads). + + 0 when leads are tracked but none happened yet; null when leads are not a goal + and none were attributed. + """ + + cost_per_mille: float + """Cost per 1,000 impressions in dollars (spend / impressions × 1000). + + 0 when there are no impressions. + """ + + cost_per_purchase: Optional[float] = None + """Cost in dollars per Whop pixel-attributed purchase (spend / purchases). + + 0 when purchases are tracked but none happened yet; null when purchases are not + a goal and none were attributed. + """ + + cost_per_result: Optional[float] = None + """Cost in dollars per optimization result (spend / results). + + 0 when a result is being optimized for but none happened yet; null when nothing + is being optimized for. + """ + created_at: datetime """When the ad was created.""" + frequency: Optional[float] = None + """ + Average number of times each person saw an ad (impressions / reach), as reported + by the platform. + """ + + impressions: int + """Total impressions (views) on this ad in the stats window.""" + + issues: List[Issue] + """Open platform issues affecting this ad, deduplicated per object. + + Empty when there are none. + """ + + leads: int + """Number of Whop pixel-attributed leads (last-click) in the stats window.""" + platform: AdCampaignPlatform """The external ad platform this ad is running on (e.g., meta, tiktok).""" + purchase_value: float + """Total USD value of Whop pixel-attributed purchases in the stats window.""" + + purchases: int + """Number of Whop pixel-attributed purchases (last-click) in the stats window.""" + + reach: int + """Unique users reached in the stats window (deduplicated by the platform).""" + + return_on_ad_spend: float + """ + Return on ad spend as a ratio (purchaseValue / spend) — 2.5 means $2.50 of + attributed purchase value per $1 spent. 0 when there is no spend. + """ + + spend: float + """Amount charged in dollars in the stats window.""" + + spend_currency: Optional[Currency] = None + """The available currencies on the platform""" + status: ExternalAdStatus """Current delivery status of the ad.""" title: Optional[str] = None """The display title of the ad. Falls back to the creative set caption when unset.""" + unique_click_through_rate: Optional[float] = None + """ + Unique click-through rate as a fraction of impressions (unique clicks / + impressions, 0–1). + """ + + unique_clicks: int + """Unique clicks (deduplicated by the platform) in the stats window.""" + updated_at: datetime """When the ad was last updated.""" diff --git a/src/whop_sdk/types/ad_report_retrieve_params.py b/src/whop_sdk/types/ad_report_retrieve_params.py index 9a779523..2b66d934 100644 --- a/src/whop_sdk/types/ad_report_retrieve_params.py +++ b/src/whop_sdk/types/ad_report_retrieve_params.py @@ -6,6 +6,7 @@ from datetime import datetime from typing_extensions import Literal, Required, Annotated, TypedDict +from .._types import SequenceNotStr from .._utils import PropertyInfo from .granularities import Granularities @@ -19,22 +20,22 @@ class AdReportRetrieveParams(TypedDict, total=False): to: Required[Annotated[Union[str, datetime], PropertyInfo(format="iso8601")]] """Inclusive end of the reporting window.""" - ad_campaign_id: Optional[str] - """The unique identifier of an ad campaign. + ad_campaign_ids: Optional[SequenceNotStr[str]] + """Scope the report to these ad campaigns (max 100); stats are summed across them. - Mutually exclusive with `companyId`, `adGroupId`, and `adId`. + Mutually exclusive with `companyId`, `adGroupIds`, and `adIds`. """ - ad_group_id: Optional[str] - """The unique identifier of an ad group. + ad_group_ids: Optional[SequenceNotStr[str]] + """Scope the report to these ad groups (max 100); stats are summed across them. - Mutually exclusive with `companyId`, `adCampaignId`, and `adId`. + Mutually exclusive with `companyId`, `adCampaignIds`, and `adIds`. """ - ad_id: Optional[str] - """The unique identifier of an ad. + ad_ids: Optional[SequenceNotStr[str]] + """Scope the report to these ads (max 100); stats are summed across them. - Mutually exclusive with `companyId`, `adCampaignId`, and `adGroupId`. + Mutually exclusive with `companyId`, `adCampaignIds`, and `adGroupIds`. """ breakdown: Optional[Literal["campaign", "ad_group", "ad"]] @@ -43,7 +44,7 @@ class AdReportRetrieveParams(TypedDict, total=False): company_id: Optional[str] """The unique identifier of a company. - Mutually exclusive with `adCampaignId`, `adGroupId`, and `adId`. Use with + Mutually exclusive with `adCampaignIds`, `adGroupIds`, and `adIds`. Use with `breakdown` to fan out across every campaign, ad group, or ad in the company without paging. """ diff --git a/src/whop_sdk/types/ad_report_retrieve_response.py b/src/whop_sdk/types/ad_report_retrieve_response.py index 56bf2ef4..7d27474e 100644 --- a/src/whop_sdk/types/ad_report_retrieve_response.py +++ b/src/whop_sdk/types/ad_report_retrieve_response.py @@ -81,20 +81,20 @@ class BreakdownGranularity(BaseModel): class BreakdownSummary(BaseModel): """Aggregate totals and rates for this entity over the date range.""" + click_through_rate: float + """Click-through rate (clicks / impressions).""" + clicks: int """Total clicks over the date range.""" - cost_per_result: Optional[float] = None - """Spend divided by `resultCount`. Null when there are no results.""" - - cpc: float + cost_per_click: float """Cost per click in the requested reporting currency.""" - cpm: Optional[float] = None + cost_per_mille: Optional[float] = None """Cost per thousand impressions in the requested reporting currency.""" - ctr: float - """Click-through rate (clicks / impressions).""" + cost_per_result: Optional[float] = None + """Spend divided by `resultCount`. Null when there are no results.""" frequency: Optional[float] = None """Average number of times each reached user saw an ad.""" @@ -117,10 +117,10 @@ class BreakdownSummary(BaseModel): result_label_override: Optional[str] = None """Advertiser-defined label for the result when `resultLabelKey` is `custom`.""" - roas: Optional[float] = None + return_on_ad_spend: Optional[float] = None """ - Alias for `purchaseRoas` — return on ad spend for purchases, as reported by the - external ad platform. + Alias for `purchaseReturnOnAdSpend` — return on ad spend for purchases, as + reported by the external ad platform. """ spend: float @@ -218,20 +218,20 @@ class Granularity(BaseModel): class Summary(BaseModel): """Aggregate totals and rates over the date range.""" + click_through_rate: float + """Click-through rate (clicks / impressions).""" + clicks: int """Total clicks over the date range.""" - cost_per_result: Optional[float] = None - """Spend divided by `resultCount`. Null when there are no results.""" - - cpc: float + cost_per_click: float """Cost per click in the requested reporting currency.""" - cpm: Optional[float] = None + cost_per_mille: Optional[float] = None """Cost per thousand impressions in the requested reporting currency.""" - ctr: float - """Click-through rate (clicks / impressions).""" + cost_per_result: Optional[float] = None + """Spend divided by `resultCount`. Null when there are no results.""" frequency: Optional[float] = None """Average number of times each reached user saw an ad.""" @@ -254,10 +254,10 @@ class Summary(BaseModel): result_label_override: Optional[str] = None """Advertiser-defined label for the result when `resultLabelKey` is `custom`.""" - roas: Optional[float] = None + return_on_ad_spend: Optional[float] = None """ - Alias for `purchaseRoas` — return on ad spend for purchases, as reported by the - external ad platform. + Alias for `purchaseReturnOnAdSpend` — return on ad spend for purchases, as + reported by the external ad platform. """ spend: float diff --git a/src/whop_sdk/types/ad_retrieve_params.py b/src/whop_sdk/types/ad_retrieve_params.py new file mode 100644 index 00000000..fc735f99 --- /dev/null +++ b/src/whop_sdk/types/ad_retrieve_params.py @@ -0,0 +1,26 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Union +from datetime import datetime +from typing_extensions import Annotated, TypedDict + +from .._utils import PropertyInfo + +__all__ = ["AdRetrieveParams"] + + +class AdRetrieveParams(TypedDict, total=False): + stats_from: Annotated[Union[str, datetime, None], PropertyInfo(format="iso8601")] + """Inclusive start of the window for the ad's metric fields (spend, impressions, + …). + + Omit both statsFrom and statsTo for all-time stats. + """ + + stats_to: Annotated[Union[str, datetime, None], PropertyInfo(format="iso8601")] + """Inclusive end of the window for the ad's metric fields. + + Omit both statsFrom and statsTo for all-time stats. + """ diff --git a/tests/api_resources/test_ad_campaigns.py b/tests/api_resources/test_ad_campaigns.py index 4c818bf8..655a33ef 100644 --- a/tests/api_resources/test_ad_campaigns.py +++ b/tests/api_resources/test_ad_campaigns.py @@ -26,7 +26,17 @@ class TestAdCampaigns: @parametrize def test_method_retrieve(self, client: Whop) -> None: ad_campaign = client.ad_campaigns.retrieve( - "adcamp_xxxxxxxxxxx", + id="adcamp_xxxxxxxxxxx", + ) + assert_matches_type(AdCampaign, ad_campaign, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_retrieve_with_all_params(self, client: Whop) -> None: + ad_campaign = client.ad_campaigns.retrieve( + id="adcamp_xxxxxxxxxxx", + stats_from=parse_datetime("2023-12-01T05:00:00.401Z"), + stats_to=parse_datetime("2023-12-01T05:00:00.401Z"), ) assert_matches_type(AdCampaign, ad_campaign, path=["response"]) @@ -34,7 +44,7 @@ def test_method_retrieve(self, client: Whop) -> None: @parametrize def test_raw_response_retrieve(self, client: Whop) -> None: response = client.ad_campaigns.with_raw_response.retrieve( - "adcamp_xxxxxxxxxxx", + id="adcamp_xxxxxxxxxxx", ) assert response.is_closed is True @@ -46,7 +56,7 @@ def test_raw_response_retrieve(self, client: Whop) -> None: @parametrize def test_streaming_response_retrieve(self, client: Whop) -> None: with client.ad_campaigns.with_streaming_response.retrieve( - "adcamp_xxxxxxxxxxx", + id="adcamp_xxxxxxxxxxx", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -61,7 +71,7 @@ def test_streaming_response_retrieve(self, client: Whop) -> None: 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( - "", + id="", ) @pytest.mark.skip(reason="Mock server tests are disabled") @@ -133,6 +143,8 @@ def test_method_list_with_all_params(self, client: Whop) -> None: first=42, last=42, query="query", + stats_from=parse_datetime("2023-12-01T05:00:00.401Z"), + stats_to=parse_datetime("2023-12-01T05:00:00.401Z"), status="active", ) assert_matches_type(SyncCursorPage[AdCampaignListResponse], ad_campaign, path=["response"]) @@ -253,7 +265,17 @@ class TestAsyncAdCampaigns: @parametrize async def test_method_retrieve(self, async_client: AsyncWhop) -> None: ad_campaign = await async_client.ad_campaigns.retrieve( - "adcamp_xxxxxxxxxxx", + id="adcamp_xxxxxxxxxxx", + ) + assert_matches_type(AdCampaign, ad_campaign, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_retrieve_with_all_params(self, async_client: AsyncWhop) -> None: + ad_campaign = await async_client.ad_campaigns.retrieve( + id="adcamp_xxxxxxxxxxx", + stats_from=parse_datetime("2023-12-01T05:00:00.401Z"), + stats_to=parse_datetime("2023-12-01T05:00:00.401Z"), ) assert_matches_type(AdCampaign, ad_campaign, path=["response"]) @@ -261,7 +283,7 @@ async def test_method_retrieve(self, async_client: AsyncWhop) -> None: @parametrize async def test_raw_response_retrieve(self, async_client: AsyncWhop) -> None: response = await async_client.ad_campaigns.with_raw_response.retrieve( - "adcamp_xxxxxxxxxxx", + id="adcamp_xxxxxxxxxxx", ) assert response.is_closed is True @@ -273,7 +295,7 @@ async def test_raw_response_retrieve(self, async_client: AsyncWhop) -> None: @parametrize async def test_streaming_response_retrieve(self, async_client: AsyncWhop) -> None: async with async_client.ad_campaigns.with_streaming_response.retrieve( - "adcamp_xxxxxxxxxxx", + id="adcamp_xxxxxxxxxxx", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -288,7 +310,7 @@ async def test_streaming_response_retrieve(self, async_client: AsyncWhop) -> Non 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( - "", + id="", ) @pytest.mark.skip(reason="Mock server tests are disabled") @@ -360,6 +382,8 @@ async def test_method_list_with_all_params(self, async_client: AsyncWhop) -> Non first=42, last=42, query="query", + stats_from=parse_datetime("2023-12-01T05:00:00.401Z"), + stats_to=parse_datetime("2023-12-01T05:00:00.401Z"), status="active", ) assert_matches_type(AsyncCursorPage[AdCampaignListResponse], ad_campaign, path=["response"]) diff --git a/tests/api_resources/test_ad_groups.py b/tests/api_resources/test_ad_groups.py index ebbda82c..6f7d99fc 100644 --- a/tests/api_resources/test_ad_groups.py +++ b/tests/api_resources/test_ad_groups.py @@ -27,7 +27,17 @@ class TestAdGroups: @parametrize def test_method_retrieve(self, client: Whop) -> None: ad_group = client.ad_groups.retrieve( - "adgrp_xxxxxxxxxxxx", + id="adgrp_xxxxxxxxxxxx", + ) + assert_matches_type(AdGroup, ad_group, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_retrieve_with_all_params(self, client: Whop) -> None: + ad_group = client.ad_groups.retrieve( + id="adgrp_xxxxxxxxxxxx", + stats_from=parse_datetime("2023-12-01T05:00:00.401Z"), + stats_to=parse_datetime("2023-12-01T05:00:00.401Z"), ) assert_matches_type(AdGroup, ad_group, path=["response"]) @@ -35,7 +45,7 @@ def test_method_retrieve(self, client: Whop) -> None: @parametrize def test_raw_response_retrieve(self, client: Whop) -> None: response = client.ad_groups.with_raw_response.retrieve( - "adgrp_xxxxxxxxxxxx", + id="adgrp_xxxxxxxxxxxx", ) assert response.is_closed is True @@ -47,7 +57,7 @@ def test_raw_response_retrieve(self, client: Whop) -> None: @parametrize def test_streaming_response_retrieve(self, client: Whop) -> None: with client.ad_groups.with_streaming_response.retrieve( - "adgrp_xxxxxxxxxxxx", + id="adgrp_xxxxxxxxxxxx", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -62,7 +72,7 @@ def test_streaming_response_retrieve(self, client: Whop) -> None: 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( - "", + id="", ) @pytest.mark.skip(reason="Mock server tests are disabled") @@ -466,6 +476,8 @@ def test_method_list(self, client: Whop) -> None: @parametrize def test_method_list_with_all_params(self, client: Whop) -> None: ad_group = client.ad_groups.list( + ad_campaign_id="ad_campaign_id", + ad_campaign_ids=["string"], after="after", before="before", campaign_id="campaign_id", @@ -473,9 +485,10 @@ def test_method_list_with_all_params(self, client: Whop) -> None: created_after=parse_datetime("2023-12-01T05:00:00.401Z"), created_before=parse_datetime("2023-12-01T05:00:00.401Z"), first=42, - include_paused=True, last=42, query="query", + stats_from=parse_datetime("2023-12-01T05:00:00.401Z"), + stats_to=parse_datetime("2023-12-01T05:00:00.401Z"), status="active", ) assert_matches_type(SyncCursorPage[AdGroupListResponse], ad_group, path=["response"]) @@ -638,7 +651,17 @@ class TestAsyncAdGroups: @parametrize async def test_method_retrieve(self, async_client: AsyncWhop) -> None: ad_group = await async_client.ad_groups.retrieve( - "adgrp_xxxxxxxxxxxx", + id="adgrp_xxxxxxxxxxxx", + ) + assert_matches_type(AdGroup, ad_group, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_retrieve_with_all_params(self, async_client: AsyncWhop) -> None: + ad_group = await async_client.ad_groups.retrieve( + id="adgrp_xxxxxxxxxxxx", + stats_from=parse_datetime("2023-12-01T05:00:00.401Z"), + stats_to=parse_datetime("2023-12-01T05:00:00.401Z"), ) assert_matches_type(AdGroup, ad_group, path=["response"]) @@ -646,7 +669,7 @@ async def test_method_retrieve(self, async_client: AsyncWhop) -> None: @parametrize async def test_raw_response_retrieve(self, async_client: AsyncWhop) -> None: response = await async_client.ad_groups.with_raw_response.retrieve( - "adgrp_xxxxxxxxxxxx", + id="adgrp_xxxxxxxxxxxx", ) assert response.is_closed is True @@ -658,7 +681,7 @@ async def test_raw_response_retrieve(self, async_client: AsyncWhop) -> None: @parametrize async def test_streaming_response_retrieve(self, async_client: AsyncWhop) -> None: async with async_client.ad_groups.with_streaming_response.retrieve( - "adgrp_xxxxxxxxxxxx", + id="adgrp_xxxxxxxxxxxx", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -673,7 +696,7 @@ async def test_streaming_response_retrieve(self, async_client: AsyncWhop) -> Non 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( - "", + id="", ) @pytest.mark.skip(reason="Mock server tests are disabled") @@ -1077,6 +1100,8 @@ async def test_method_list(self, async_client: AsyncWhop) -> None: @parametrize async def test_method_list_with_all_params(self, async_client: AsyncWhop) -> None: ad_group = await async_client.ad_groups.list( + ad_campaign_id="ad_campaign_id", + ad_campaign_ids=["string"], after="after", before="before", campaign_id="campaign_id", @@ -1084,9 +1109,10 @@ async def test_method_list_with_all_params(self, async_client: AsyncWhop) -> Non created_after=parse_datetime("2023-12-01T05:00:00.401Z"), created_before=parse_datetime("2023-12-01T05:00:00.401Z"), first=42, - include_paused=True, last=42, query="query", + stats_from=parse_datetime("2023-12-01T05:00:00.401Z"), + stats_to=parse_datetime("2023-12-01T05:00:00.401Z"), status="active", ) assert_matches_type(AsyncCursorPage[AdGroupListResponse], ad_group, path=["response"]) diff --git a/tests/api_resources/test_ad_reports.py b/tests/api_resources/test_ad_reports.py index a5b76624..da7dc2be 100644 --- a/tests/api_resources/test_ad_reports.py +++ b/tests/api_resources/test_ad_reports.py @@ -33,9 +33,9 @@ def test_method_retrieve_with_all_params(self, client: Whop) -> None: ad_report = client.ad_reports.retrieve( from_=parse_datetime("2023-12-01T05:00:00.401Z"), to=parse_datetime("2023-12-01T05:00:00.401Z"), - ad_campaign_id="ad_campaign_id", - ad_group_id="ad_group_id", - ad_id="ad_id", + ad_campaign_ids=["string"], + ad_group_ids=["string"], + ad_ids=["string"], breakdown="campaign", company_id="biz_xxxxxxxxxxxxxx", currency="currency", @@ -92,9 +92,9 @@ async def test_method_retrieve_with_all_params(self, async_client: AsyncWhop) -> ad_report = await async_client.ad_reports.retrieve( from_=parse_datetime("2023-12-01T05:00:00.401Z"), to=parse_datetime("2023-12-01T05:00:00.401Z"), - ad_campaign_id="ad_campaign_id", - ad_group_id="ad_group_id", - ad_id="ad_id", + ad_campaign_ids=["string"], + ad_group_ids=["string"], + ad_ids=["string"], breakdown="campaign", company_id="biz_xxxxxxxxxxxxxx", currency="currency", diff --git a/tests/api_resources/test_ads.py b/tests/api_resources/test_ads.py index 0b0f229e..4fc25ed4 100644 --- a/tests/api_resources/test_ads.py +++ b/tests/api_resources/test_ads.py @@ -23,7 +23,17 @@ class TestAds: @parametrize def test_method_retrieve(self, client: Whop) -> None: ad = client.ads.retrieve( - "ad_xxxxxxxxxxxxxxx", + id="ad_xxxxxxxxxxxxxxx", + ) + assert_matches_type(Ad, ad, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_retrieve_with_all_params(self, client: Whop) -> None: + ad = client.ads.retrieve( + id="ad_xxxxxxxxxxxxxxx", + stats_from=parse_datetime("2023-12-01T05:00:00.401Z"), + stats_to=parse_datetime("2023-12-01T05:00:00.401Z"), ) assert_matches_type(Ad, ad, path=["response"]) @@ -31,7 +41,7 @@ def test_method_retrieve(self, client: Whop) -> None: @parametrize def test_raw_response_retrieve(self, client: Whop) -> None: response = client.ads.with_raw_response.retrieve( - "ad_xxxxxxxxxxxxxxx", + id="ad_xxxxxxxxxxxxxxx", ) assert response.is_closed is True @@ -43,7 +53,7 @@ def test_raw_response_retrieve(self, client: Whop) -> None: @parametrize def test_streaming_response_retrieve(self, client: Whop) -> None: with client.ads.with_streaming_response.retrieve( - "ad_xxxxxxxxxxxxxxx", + id="ad_xxxxxxxxxxxxxxx", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -58,7 +68,7 @@ def test_streaming_response_retrieve(self, client: Whop) -> None: 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( - "", + id="", ) @pytest.mark.skip(reason="Mock server tests are disabled") @@ -71,16 +81,20 @@ def test_method_list(self, client: Whop) -> None: @parametrize def test_method_list_with_all_params(self, client: Whop) -> None: ad = client.ads.list( + ad_campaign_id="ad_campaign_id", + ad_campaign_ids=["string"], ad_group_id="ad_group_id", + ad_group_ids=["string"], 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"), + direction="asc", first=42, - include_paused=True, last=42, + order="created_at", order_by="spend", order_direction="asc", query="query", @@ -206,7 +220,17 @@ class TestAsyncAds: @parametrize async def test_method_retrieve(self, async_client: AsyncWhop) -> None: ad = await async_client.ads.retrieve( - "ad_xxxxxxxxxxxxxxx", + id="ad_xxxxxxxxxxxxxxx", + ) + assert_matches_type(Ad, ad, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_retrieve_with_all_params(self, async_client: AsyncWhop) -> None: + ad = await async_client.ads.retrieve( + id="ad_xxxxxxxxxxxxxxx", + stats_from=parse_datetime("2023-12-01T05:00:00.401Z"), + stats_to=parse_datetime("2023-12-01T05:00:00.401Z"), ) assert_matches_type(Ad, ad, path=["response"]) @@ -214,7 +238,7 @@ async def test_method_retrieve(self, async_client: AsyncWhop) -> None: @parametrize async def test_raw_response_retrieve(self, async_client: AsyncWhop) -> None: response = await async_client.ads.with_raw_response.retrieve( - "ad_xxxxxxxxxxxxxxx", + id="ad_xxxxxxxxxxxxxxx", ) assert response.is_closed is True @@ -226,7 +250,7 @@ async def test_raw_response_retrieve(self, async_client: AsyncWhop) -> None: @parametrize async def test_streaming_response_retrieve(self, async_client: AsyncWhop) -> None: async with async_client.ads.with_streaming_response.retrieve( - "ad_xxxxxxxxxxxxxxx", + id="ad_xxxxxxxxxxxxxxx", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -241,7 +265,7 @@ async def test_streaming_response_retrieve(self, async_client: AsyncWhop) -> Non 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( - "", + id="", ) @pytest.mark.skip(reason="Mock server tests are disabled") @@ -254,16 +278,20 @@ async def test_method_list(self, async_client: AsyncWhop) -> None: @parametrize async def test_method_list_with_all_params(self, async_client: AsyncWhop) -> None: ad = await async_client.ads.list( + ad_campaign_id="ad_campaign_id", + ad_campaign_ids=["string"], ad_group_id="ad_group_id", + ad_group_ids=["string"], 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"), + direction="asc", first=42, - include_paused=True, last=42, + order="created_at", order_by="spend", order_direction="asc", query="query", From bb2079ed5b4c9533208ce40c18bbdd7f09782698 Mon Sep 17 00:00:00 2001 From: stlc-bot Date: Fri, 5 Jun 2026 08:58:22 +0000 Subject: [PATCH 005/109] Document swap execute/status endpoints in OpenAPI + SDK Stainless-Generated-From: 304bb3a2d4637856fc0b74be08b2ce2944848004 --- .stats.yml | 2 +- api.md | 4 +- src/whop_sdk/resources/swaps.py | 214 ++++++++++++++++++- src/whop_sdk/types/__init__.py | 3 + src/whop_sdk/types/swap_create_params.py | 28 +++ src/whop_sdk/types/swap_create_response.py | 24 +++ src/whop_sdk/types/swap_retrieve_response.py | 20 ++ tests/api_resources/test_swaps.py | 204 +++++++++++++++++- 8 files changed, 494 insertions(+), 5 deletions(-) create mode 100644 src/whop_sdk/types/swap_create_params.py create mode 100644 src/whop_sdk/types/swap_create_response.py create mode 100644 src/whop_sdk/types/swap_retrieve_response.py diff --git a/.stats.yml b/.stats.yml index a5722ed0..a9737a87 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1 +1 @@ -configured_endpoints: 226 +configured_endpoints: 228 diff --git a/api.md b/api.md index 1a48a9be..ad1d5f2d 100644 --- a/api.md +++ b/api.md @@ -743,11 +743,13 @@ Methods: Types: ```python -from whop_sdk.types import SwapCreateQuoteResponse +from whop_sdk.types import SwapCreateResponse, SwapRetrieveResponse, SwapCreateQuoteResponse ``` Methods: +- client.swaps.create(\*\*params) -> SwapCreateResponse +- client.swaps.retrieve(account_id) -> SwapRetrieveResponse - client.swaps.create_quote(\*\*params) -> SwapCreateQuoteResponse # Deposits diff --git a/src/whop_sdk/resources/swaps.py b/src/whop_sdk/resources/swaps.py index a79eabbe..e95d744d 100644 --- a/src/whop_sdk/resources/swaps.py +++ b/src/whop_sdk/resources/swaps.py @@ -6,9 +6,9 @@ import httpx -from ..types import swap_create_quote_params +from ..types import swap_create_params, swap_create_quote_params from .._types import Body, Omit, Query, Headers, NotGiven, omit, not_given -from .._utils import maybe_transform, async_maybe_transform +from .._utils import path_template, maybe_transform, async_maybe_transform from .._compat import cached_property from .._resource import SyncAPIResource, AsyncAPIResource from .._response import ( @@ -18,6 +18,8 @@ async_to_streamed_response_wrapper, ) from .._base_client import make_request_options +from ..types.swap_create_response import SwapCreateResponse +from ..types.swap_retrieve_response import SwapRetrieveResponse from ..types.swap_create_quote_response import SwapCreateQuoteResponse __all__ = ["SwapsResource", "AsyncSwapsResource"] @@ -43,6 +45,98 @@ def with_streaming_response(self) -> SwapsResourceWithStreamingResponse: """ return SwapsResourceWithStreamingResponse(self) + def create( + self, + *, + account_id: str, + amount: str, + from_token: str, + to_token: str, + from_chain: Union[str, int, None] | Omit = omit, + slippage_bps: Optional[int] | Omit = omit, + to_chain: Union[str, int, 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. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> SwapCreateResponse: + """Executes a swap from the account's wallet. + + Runs asynchronously — poll GET + /swaps/{account_id} for status. + + Args: + account_id: Business or user account ID (biz*\\** / user*\\**). + + amount: Input token amount. + + from_token: Source token contract address. + + to_token: Destination token contract address. + + 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( + "/swaps", + body=maybe_transform( + { + "account_id": account_id, + "amount": amount, + "from_token": from_token, + "to_token": to_token, + "from_chain": from_chain, + "slippage_bps": slippage_bps, + "to_chain": to_chain, + }, + swap_create_params.SwapCreateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=SwapCreateResponse, + ) + + def retrieve( + self, + account_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, + ) -> SwapRetrieveResponse: + """ + Returns the status of the account's in-flight or most recent swap. + + 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 account_id: + raise ValueError(f"Expected a non-empty value for `account_id` but received {account_id!r}") + return self._get( + path_template("/swaps/{account_id}", account_id=account_id), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=SwapRetrieveResponse, + ) + def create_quote( self, *, @@ -124,6 +218,98 @@ def with_streaming_response(self) -> AsyncSwapsResourceWithStreamingResponse: """ return AsyncSwapsResourceWithStreamingResponse(self) + async def create( + self, + *, + account_id: str, + amount: str, + from_token: str, + to_token: str, + from_chain: Union[str, int, None] | Omit = omit, + slippage_bps: Optional[int] | Omit = omit, + to_chain: Union[str, int, 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. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> SwapCreateResponse: + """Executes a swap from the account's wallet. + + Runs asynchronously — poll GET + /swaps/{account_id} for status. + + Args: + account_id: Business or user account ID (biz*\\** / user*\\**). + + amount: Input token amount. + + from_token: Source token contract address. + + to_token: Destination token contract address. + + 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( + "/swaps", + body=await async_maybe_transform( + { + "account_id": account_id, + "amount": amount, + "from_token": from_token, + "to_token": to_token, + "from_chain": from_chain, + "slippage_bps": slippage_bps, + "to_chain": to_chain, + }, + swap_create_params.SwapCreateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=SwapCreateResponse, + ) + + async def retrieve( + self, + account_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, + ) -> SwapRetrieveResponse: + """ + Returns the status of the account's in-flight or most recent swap. + + 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 account_id: + raise ValueError(f"Expected a non-empty value for `account_id` but received {account_id!r}") + return await self._get( + path_template("/swaps/{account_id}", account_id=account_id), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=SwapRetrieveResponse, + ) + async def create_quote( self, *, @@ -189,6 +375,12 @@ class SwapsResourceWithRawResponse: def __init__(self, swaps: SwapsResource) -> None: self._swaps = swaps + self.create = to_raw_response_wrapper( + swaps.create, + ) + self.retrieve = to_raw_response_wrapper( + swaps.retrieve, + ) self.create_quote = to_raw_response_wrapper( swaps.create_quote, ) @@ -198,6 +390,12 @@ class AsyncSwapsResourceWithRawResponse: def __init__(self, swaps: AsyncSwapsResource) -> None: self._swaps = swaps + self.create = async_to_raw_response_wrapper( + swaps.create, + ) + self.retrieve = async_to_raw_response_wrapper( + swaps.retrieve, + ) self.create_quote = async_to_raw_response_wrapper( swaps.create_quote, ) @@ -207,6 +405,12 @@ class SwapsResourceWithStreamingResponse: def __init__(self, swaps: SwapsResource) -> None: self._swaps = swaps + self.create = to_streamed_response_wrapper( + swaps.create, + ) + self.retrieve = to_streamed_response_wrapper( + swaps.retrieve, + ) self.create_quote = to_streamed_response_wrapper( swaps.create_quote, ) @@ -216,6 +420,12 @@ class AsyncSwapsResourceWithStreamingResponse: def __init__(self, swaps: AsyncSwapsResource) -> None: self._swaps = swaps + self.create = async_to_streamed_response_wrapper( + swaps.create, + ) + self.retrieve = async_to_streamed_response_wrapper( + swaps.retrieve, + ) self.create_quote = async_to_streamed_response_wrapper( swaps.create_quote, ) diff --git a/src/whop_sdk/types/__init__.py b/src/whop_sdk/types/__init__.py index 8e3b5927..766e6f08 100644 --- a/src/whop_sdk/types/__init__.py +++ b/src/whop_sdk/types/__init__.py @@ -137,6 +137,7 @@ from .plan_update_params import PlanUpdateParams as PlanUpdateParams from .refund_list_params import RefundListParams as RefundListParams from .review_list_params import ReviewListParams as ReviewListParams +from .swap_create_params import SwapCreateParams as SwapCreateParams from .user_list_response import UserListResponse as UserListResponse from .user_update_params import UserUpdateParams as UserUpdateParams from .wallet_send_params import WalletSendParams as WalletSendParams @@ -176,6 +177,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 .swap_create_response import SwapCreateResponse as SwapCreateResponse from .transfer_list_params import TransferListParams as TransferListParams from .unwrap_webhook_event import UnwrapWebhookEvent as UnwrapWebhookEvent from .user_retrieve_params import UserRetrieveParams as UserRetrieveParams @@ -229,6 +231,7 @@ 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 .swap_retrieve_response import SwapRetrieveResponse as SwapRetrieveResponse from .transfer_create_params import TransferCreateParams as TransferCreateParams from .transfer_list_response import TransferListResponse as TransferListResponse from .withdrawal_list_params import WithdrawalListParams as WithdrawalListParams diff --git a/src/whop_sdk/types/swap_create_params.py b/src/whop_sdk/types/swap_create_params.py new file mode 100644 index 00000000..ab28ef99 --- /dev/null +++ b/src/whop_sdk/types/swap_create_params.py @@ -0,0 +1,28 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Union, Optional +from typing_extensions import Required, TypedDict + +__all__ = ["SwapCreateParams"] + + +class SwapCreateParams(TypedDict, total=False): + account_id: Required[str] + """Business or user account ID (biz*\\** / user*\\**).""" + + amount: Required[str] + """Input token amount.""" + + from_token: Required[str] + """Source token contract address.""" + + to_token: Required[str] + """Destination token contract address.""" + + from_chain: Union[str, int, None] + + slippage_bps: Optional[int] + + to_chain: Union[str, int, None] diff --git a/src/whop_sdk/types/swap_create_response.py b/src/whop_sdk/types/swap_create_response.py new file mode 100644 index 00000000..8829f7b9 --- /dev/null +++ b/src/whop_sdk/types/swap_create_response.py @@ -0,0 +1,24 @@ +# 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 + +__all__ = ["SwapCreateResponse"] + + +class SwapCreateResponse(BaseModel): + account_id: str + + object: Literal["swap"] + + status: str + + amount_out_expected: Optional[str] = None + + amount_out_min: Optional[str] = None + + rate: Optional[str] = None + + to_chain: Optional[str] = None diff --git a/src/whop_sdk/types/swap_retrieve_response.py b/src/whop_sdk/types/swap_retrieve_response.py new file mode 100644 index 00000000..ca351983 --- /dev/null +++ b/src/whop_sdk/types/swap_retrieve_response.py @@ -0,0 +1,20 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Optional +from typing_extensions import Literal + +from .._models import BaseModel + +__all__ = ["SwapRetrieveResponse"] + + +class SwapRetrieveResponse(BaseModel): + account_id: str + + object: Literal["swap"] + + status: str + + tx_hashes: List[str] + + error: Optional[str] = None diff --git a/tests/api_resources/test_swaps.py b/tests/api_resources/test_swaps.py index 26dee794..de7e8803 100644 --- a/tests/api_resources/test_swaps.py +++ b/tests/api_resources/test_swaps.py @@ -9,7 +9,11 @@ from whop_sdk import Whop, AsyncWhop from tests.utils import assert_matches_type -from whop_sdk.types import SwapCreateQuoteResponse +from whop_sdk.types import ( + SwapCreateResponse, + SwapRetrieveResponse, + SwapCreateQuoteResponse, +) base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") @@ -17,6 +21,105 @@ class TestSwaps: 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: + swap = client.swaps.create( + account_id="account_id", + amount="amount", + from_token="from_token", + to_token="to_token", + ) + assert_matches_type(SwapCreateResponse, swap, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_create_with_all_params(self, client: Whop) -> None: + swap = client.swaps.create( + account_id="account_id", + amount="amount", + from_token="from_token", + to_token="to_token", + from_chain="string", + slippage_bps=0, + to_chain="string", + ) + assert_matches_type(SwapCreateResponse, swap, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_raw_response_create(self, client: Whop) -> None: + response = client.swaps.with_raw_response.create( + account_id="account_id", + amount="amount", + from_token="from_token", + to_token="to_token", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + swap = response.parse() + assert_matches_type(SwapCreateResponse, swap, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_streaming_response_create(self, client: Whop) -> None: + with client.swaps.with_streaming_response.create( + account_id="account_id", + amount="amount", + from_token="from_token", + to_token="to_token", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + swap = response.parse() + assert_matches_type(SwapCreateResponse, swap, 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: + swap = client.swaps.retrieve( + "account_id", + ) + assert_matches_type(SwapRetrieveResponse, swap, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_raw_response_retrieve(self, client: Whop) -> None: + response = client.swaps.with_raw_response.retrieve( + "account_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + swap = response.parse() + assert_matches_type(SwapRetrieveResponse, swap, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_streaming_response_retrieve(self, client: Whop) -> None: + with client.swaps.with_streaming_response.retrieve( + "account_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + swap = response.parse() + assert_matches_type(SwapRetrieveResponse, swap, 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 `account_id` but received ''"): + client.swaps.with_raw_response.retrieve( + "", + ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_method_create_quote(self, client: Whop) -> None: @@ -79,6 +182,105 @@ class TestAsyncSwaps: "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: + swap = await async_client.swaps.create( + account_id="account_id", + amount="amount", + from_token="from_token", + to_token="to_token", + ) + assert_matches_type(SwapCreateResponse, swap, 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: + swap = await async_client.swaps.create( + account_id="account_id", + amount="amount", + from_token="from_token", + to_token="to_token", + from_chain="string", + slippage_bps=0, + to_chain="string", + ) + assert_matches_type(SwapCreateResponse, swap, 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.swaps.with_raw_response.create( + account_id="account_id", + amount="amount", + from_token="from_token", + to_token="to_token", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + swap = await response.parse() + assert_matches_type(SwapCreateResponse, swap, 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.swaps.with_streaming_response.create( + account_id="account_id", + amount="amount", + from_token="from_token", + to_token="to_token", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + swap = await response.parse() + assert_matches_type(SwapCreateResponse, swap, 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: + swap = await async_client.swaps.retrieve( + "account_id", + ) + assert_matches_type(SwapRetrieveResponse, swap, 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.swaps.with_raw_response.retrieve( + "account_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + swap = await response.parse() + assert_matches_type(SwapRetrieveResponse, swap, 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.swaps.with_streaming_response.retrieve( + "account_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + swap = await response.parse() + assert_matches_type(SwapRetrieveResponse, swap, 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 `account_id` but received ''"): + await async_client.swaps.with_raw_response.retrieve( + "", + ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_method_create_quote(self, async_client: AsyncWhop) -> None: From a374b1987a9ce34a998e6db81d9f854f065ea06b Mon Sep 17 00:00:00 2001 From: stlc-bot Date: Mon, 8 Jun 2026 05:50:04 +0000 Subject: [PATCH 006/109] Add GET /api/v1/financial-activity endpoint Stainless-Generated-From: 658ded3dac4fec0bafd3a910fdadd81d52b30e8b --- .stats.yml | 2 +- api.md | 12 + src/whop_sdk/_client.py | 38 +++ src/whop_sdk/resources/__init__.py | 14 + src/whop_sdk/resources/financial_activity.py | 250 ++++++++++++++++++ src/whop_sdk/types/__init__.py | 2 + .../types/financial_activity_list_params.py | 38 +++ .../types/financial_activity_list_response.py | 212 +++++++++++++++ .../api_resources/test_financial_activity.py | 111 ++++++++ 9 files changed, 678 insertions(+), 1 deletion(-) create mode 100644 src/whop_sdk/resources/financial_activity.py create mode 100644 src/whop_sdk/types/financial_activity_list_params.py create mode 100644 src/whop_sdk/types/financial_activity_list_response.py create mode 100644 tests/api_resources/test_financial_activity.py diff --git a/.stats.yml b/.stats.yml index a9737a87..f1fe8e08 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1 +1 @@ -configured_endpoints: 228 +configured_endpoints: 229 diff --git a/api.md b/api.md index ad1d5f2d..abd862a0 100644 --- a/api.md +++ b/api.md @@ -738,6 +738,18 @@ Methods: - client.wallets.balance(account_id) -> WalletBalanceResponse - client.wallets.send(account_id, \*\*params) -> WalletSendResponse +# FinancialActivity + +Types: + +```python +from whop_sdk.types import FinancialActivityListResponse +``` + +Methods: + +- client.financial_activity.list(\*\*params) -> FinancialActivityListResponse + # Swaps Types: diff --git a/src/whop_sdk/_client.py b/src/whop_sdk/_client.py index 81df0bfb..4455e1e1 100644 --- a/src/whop_sdk/_client.py +++ b/src/whop_sdk/_client.py @@ -95,6 +95,7 @@ payout_accounts, authorized_users, support_channels, + financial_activity, checkout_configurations, resolution_center_cases, company_token_transactions, @@ -158,6 +159,7 @@ from .resources.payout_accounts import PayoutAccountsResource, AsyncPayoutAccountsResource from .resources.authorized_users import AuthorizedUsersResource, AsyncAuthorizedUsersResource from .resources.support_channels import SupportChannelsResource, AsyncSupportChannelsResource + from .resources.financial_activity import FinancialActivityResource, AsyncFinancialActivityResource from .resources.affiliates.affiliates import AffiliatesResource, AsyncAffiliatesResource from .resources.checkout_configurations import CheckoutConfigurationsResource, AsyncCheckoutConfigurationsResource from .resources.resolution_center_cases import ResolutionCenterCasesResource, AsyncResolutionCenterCasesResource @@ -523,6 +525,12 @@ def wallets(self) -> WalletsResource: return WalletsResource(self) + @cached_property + def financial_activity(self) -> FinancialActivityResource: + from .resources.financial_activity import FinancialActivityResource + + return FinancialActivityResource(self) + @cached_property def swaps(self) -> SwapsResource: from .resources.swaps import SwapsResource @@ -1157,6 +1165,12 @@ def wallets(self) -> AsyncWalletsResource: return AsyncWalletsResource(self) + @cached_property + def financial_activity(self) -> AsyncFinancialActivityResource: + from .resources.financial_activity import AsyncFinancialActivityResource + + return AsyncFinancialActivityResource(self) + @cached_property def swaps(self) -> AsyncSwapsResource: from .resources.swaps import AsyncSwapsResource @@ -1718,6 +1732,12 @@ def wallets(self) -> wallets.WalletsResourceWithRawResponse: return WalletsResourceWithRawResponse(self._client.wallets) + @cached_property + def financial_activity(self) -> financial_activity.FinancialActivityResourceWithRawResponse: + from .resources.financial_activity import FinancialActivityResourceWithRawResponse + + return FinancialActivityResourceWithRawResponse(self._client.financial_activity) + @cached_property def swaps(self) -> swaps.SwapsResourceWithRawResponse: from .resources.swaps import SwapsResourceWithRawResponse @@ -2164,6 +2184,12 @@ def wallets(self) -> wallets.AsyncWalletsResourceWithRawResponse: return AsyncWalletsResourceWithRawResponse(self._client.wallets) + @cached_property + def financial_activity(self) -> financial_activity.AsyncFinancialActivityResourceWithRawResponse: + from .resources.financial_activity import AsyncFinancialActivityResourceWithRawResponse + + return AsyncFinancialActivityResourceWithRawResponse(self._client.financial_activity) + @cached_property def swaps(self) -> swaps.AsyncSwapsResourceWithRawResponse: from .resources.swaps import AsyncSwapsResourceWithRawResponse @@ -2612,6 +2638,12 @@ def wallets(self) -> wallets.WalletsResourceWithStreamingResponse: return WalletsResourceWithStreamingResponse(self._client.wallets) + @cached_property + def financial_activity(self) -> financial_activity.FinancialActivityResourceWithStreamingResponse: + from .resources.financial_activity import FinancialActivityResourceWithStreamingResponse + + return FinancialActivityResourceWithStreamingResponse(self._client.financial_activity) + @cached_property def swaps(self) -> swaps.SwapsResourceWithStreamingResponse: from .resources.swaps import SwapsResourceWithStreamingResponse @@ -3062,6 +3094,12 @@ def wallets(self) -> wallets.AsyncWalletsResourceWithStreamingResponse: return AsyncWalletsResourceWithStreamingResponse(self._client.wallets) + @cached_property + def financial_activity(self) -> financial_activity.AsyncFinancialActivityResourceWithStreamingResponse: + from .resources.financial_activity import AsyncFinancialActivityResourceWithStreamingResponse + + return AsyncFinancialActivityResourceWithStreamingResponse(self._client.financial_activity) + @cached_property def swaps(self) -> swaps.AsyncSwapsResourceWithStreamingResponse: from .resources.swaps import AsyncSwapsResourceWithStreamingResponse diff --git a/src/whop_sdk/resources/__init__.py b/src/whop_sdk/resources/__init__.py index 6cd6f052..222ce2ef 100644 --- a/src/whop_sdk/resources/__init__.py +++ b/src/whop_sdk/resources/__init__.py @@ -472,6 +472,14 @@ SupportChannelsResourceWithStreamingResponse, AsyncSupportChannelsResourceWithStreamingResponse, ) +from .financial_activity import ( + FinancialActivityResource, + AsyncFinancialActivityResource, + FinancialActivityResourceWithRawResponse, + AsyncFinancialActivityResourceWithRawResponse, + FinancialActivityResourceWithStreamingResponse, + AsyncFinancialActivityResourceWithStreamingResponse, +) from .checkout_configurations import ( CheckoutConfigurationsResource, AsyncCheckoutConfigurationsResource, @@ -740,6 +748,12 @@ "AsyncWalletsResourceWithRawResponse", "WalletsResourceWithStreamingResponse", "AsyncWalletsResourceWithStreamingResponse", + "FinancialActivityResource", + "AsyncFinancialActivityResource", + "FinancialActivityResourceWithRawResponse", + "AsyncFinancialActivityResourceWithRawResponse", + "FinancialActivityResourceWithStreamingResponse", + "AsyncFinancialActivityResourceWithStreamingResponse", "SwapsResource", "AsyncSwapsResource", "SwapsResourceWithRawResponse", diff --git a/src/whop_sdk/resources/financial_activity.py b/src/whop_sdk/resources/financial_activity.py new file mode 100644 index 00000000..e866edc6 --- /dev/null +++ b/src/whop_sdk/resources/financial_activity.py @@ -0,0 +1,250 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Union +from datetime import datetime + +import httpx + +from ..types import financial_activity_list_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.financial_activity_list_response import FinancialActivityListResponse + +__all__ = ["FinancialActivityResource", "AsyncFinancialActivityResource"] + + +class FinancialActivityResource(SyncAPIResource): + @cached_property + def with_raw_response(self) -> FinancialActivityResourceWithRawResponse: + """ + 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 FinancialActivityResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> FinancialActivityResourceWithStreamingResponse: + """ + 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 FinancialActivityResourceWithStreamingResponse(self) + + def list( + self, + *, + account_id: str | Omit = omit, + currency: str | Omit = omit, + cursor: str | Omit = omit, + limit: int | Omit = omit, + line_types: SequenceNotStr[str] | Omit = omit, + posted_after: Union[str, datetime] | Omit = omit, + posted_before: Union[str, datetime] | Omit = omit, + user_id: 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, + ) -> FinancialActivityListResponse: + """Lists financial activity rows for a ledger account. + + Rows are derived from ledger + lines and include typed resource and source objects that clients can use for + presentation and navigation. The ledger's owner is passed as exactly one of + account*id (a biz* identifier) or user*id (a user* identifier). + + Args: + account_id: The owning account ID (a biz\\__ identifier). Provide this or user_id. + + currency: Optional currency code filter, for example usd. + + cursor: Cursor returned by the previous page. + + limit: Maximum number of rows to return. + + line_types: Optional ledger line categories to include. + + posted_after: Only include rows posted after this ISO 8601 timestamp. + + posted_before: Only include rows posted before this ISO 8601 timestamp. + + user_id: The owning user ID (a user\\__ identifier). Provide this or account_id. + + 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( + "/financial-activity", + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "account_id": account_id, + "currency": currency, + "cursor": cursor, + "limit": limit, + "line_types": line_types, + "posted_after": posted_after, + "posted_before": posted_before, + "user_id": user_id, + }, + financial_activity_list_params.FinancialActivityListParams, + ), + ), + cast_to=FinancialActivityListResponse, + ) + + +class AsyncFinancialActivityResource(AsyncAPIResource): + @cached_property + def with_raw_response(self) -> AsyncFinancialActivityResourceWithRawResponse: + """ + 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 AsyncFinancialActivityResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncFinancialActivityResourceWithStreamingResponse: + """ + 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 AsyncFinancialActivityResourceWithStreamingResponse(self) + + async def list( + self, + *, + account_id: str | Omit = omit, + currency: str | Omit = omit, + cursor: str | Omit = omit, + limit: int | Omit = omit, + line_types: SequenceNotStr[str] | Omit = omit, + posted_after: Union[str, datetime] | Omit = omit, + posted_before: Union[str, datetime] | Omit = omit, + user_id: 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, + ) -> FinancialActivityListResponse: + """Lists financial activity rows for a ledger account. + + Rows are derived from ledger + lines and include typed resource and source objects that clients can use for + presentation and navigation. The ledger's owner is passed as exactly one of + account*id (a biz* identifier) or user*id (a user* identifier). + + Args: + account_id: The owning account ID (a biz\\__ identifier). Provide this or user_id. + + currency: Optional currency code filter, for example usd. + + cursor: Cursor returned by the previous page. + + limit: Maximum number of rows to return. + + line_types: Optional ledger line categories to include. + + posted_after: Only include rows posted after this ISO 8601 timestamp. + + posted_before: Only include rows posted before this ISO 8601 timestamp. + + user_id: The owning user ID (a user\\__ identifier). Provide this or account_id. + + 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( + "/financial-activity", + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=await async_maybe_transform( + { + "account_id": account_id, + "currency": currency, + "cursor": cursor, + "limit": limit, + "line_types": line_types, + "posted_after": posted_after, + "posted_before": posted_before, + "user_id": user_id, + }, + financial_activity_list_params.FinancialActivityListParams, + ), + ), + cast_to=FinancialActivityListResponse, + ) + + +class FinancialActivityResourceWithRawResponse: + def __init__(self, financial_activity: FinancialActivityResource) -> None: + self._financial_activity = financial_activity + + self.list = to_raw_response_wrapper( + financial_activity.list, + ) + + +class AsyncFinancialActivityResourceWithRawResponse: + def __init__(self, financial_activity: AsyncFinancialActivityResource) -> None: + self._financial_activity = financial_activity + + self.list = async_to_raw_response_wrapper( + financial_activity.list, + ) + + +class FinancialActivityResourceWithStreamingResponse: + def __init__(self, financial_activity: FinancialActivityResource) -> None: + self._financial_activity = financial_activity + + self.list = to_streamed_response_wrapper( + financial_activity.list, + ) + + +class AsyncFinancialActivityResourceWithStreamingResponse: + def __init__(self, financial_activity: AsyncFinancialActivityResource) -> None: + self._financial_activity = financial_activity + + self.list = async_to_streamed_response_wrapper( + financial_activity.list, + ) diff --git a/src/whop_sdk/types/__init__.py b/src/whop_sdk/types/__init__.py index 766e6f08..a0326517 100644 --- a/src/whop_sdk/types/__init__.py +++ b/src/whop_sdk/types/__init__.py @@ -366,6 +366,7 @@ from .company_token_transaction_type import CompanyTokenTransactionType as CompanyTokenTransactionType from .course_chapter_delete_response import CourseChapterDeleteResponse as CourseChapterDeleteResponse from .dispute_update_evidence_params import DisputeUpdateEvidenceParams as DisputeUpdateEvidenceParams +from .financial_activity_list_params import FinancialActivityListParams as FinancialActivityListParams from .invoice_past_due_webhook_event import InvoicePastDueWebhookEvent as InvoicePastDueWebhookEvent from .payment_method_retrieve_params import PaymentMethodRetrieveParams as PaymentMethodRetrieveParams from .verification_retrieve_response import VerificationRetrieveResponse as VerificationRetrieveResponse @@ -376,6 +377,7 @@ from .payment_succeeded_webhook_event import PaymentSucceededWebhookEvent as PaymentSucceededWebhookEvent from .payout_method_retrieve_response import PayoutMethodRetrieveResponse as PayoutMethodRetrieveResponse from .course_student_retrieve_response import CourseStudentRetrieveResponse as CourseStudentRetrieveResponse +from .financial_activity_list_response import FinancialActivityListResponse as FinancialActivityListResponse from .ledger_account_retrieve_response import LedgerAccountRetrieveResponse as LedgerAccountRetrieveResponse from .payment_method_retrieve_response import PaymentMethodRetrieveResponse as PaymentMethodRetrieveResponse from .payout_account_retrieve_response import PayoutAccountRetrieveResponse as PayoutAccountRetrieveResponse diff --git a/src/whop_sdk/types/financial_activity_list_params.py b/src/whop_sdk/types/financial_activity_list_params.py new file mode 100644 index 00000000..352c468d --- /dev/null +++ b/src/whop_sdk/types/financial_activity_list_params.py @@ -0,0 +1,38 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Union +from datetime import datetime +from typing_extensions import Annotated, TypedDict + +from .._types import SequenceNotStr +from .._utils import PropertyInfo + +__all__ = ["FinancialActivityListParams"] + + +class FinancialActivityListParams(TypedDict, total=False): + account_id: str + """The owning account ID (a biz\\__ identifier). Provide this or user_id.""" + + currency: str + """Optional currency code filter, for example usd.""" + + cursor: str + """Cursor returned by the previous page.""" + + limit: int + """Maximum number of rows to return.""" + + line_types: SequenceNotStr[str] + """Optional ledger line categories to include.""" + + posted_after: Annotated[Union[str, datetime], PropertyInfo(format="iso8601")] + """Only include rows posted after this ISO 8601 timestamp.""" + + posted_before: Annotated[Union[str, datetime], PropertyInfo(format="iso8601")] + """Only include rows posted before this ISO 8601 timestamp.""" + + user_id: str + """The owning user ID (a user\\__ identifier). Provide this or account_id.""" diff --git a/src/whop_sdk/types/financial_activity_list_response.py b/src/whop_sdk/types/financial_activity_list_response.py new file mode 100644 index 00000000..1d684205 --- /dev/null +++ b/src/whop_sdk/types/financial_activity_list_response.py @@ -0,0 +1,212 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import builtins +from typing import TYPE_CHECKING, Dict, List, Union, Optional +from datetime import datetime +from typing_extensions import Literal, TypeAlias + +from pydantic import Field as FieldInfo + +from .._models import BaseModel + +__all__ = [ + "FinancialActivityListResponse", + "Data", + "DataCurrency", + "DataResource", + "DataResourceUnionMember0", + "DataResourceUnionMember1", + "DataResourceUnionMember2", + "DataResourceUnionMember2Owner", + "DataResourceUnionMember2OwnerUnionMember0", + "DataResourceUnionMember2OwnerUnionMember1", + "DataResourceUnionMember3", + "DataResourceUnionMember3Bank", + "DataResourceUnionMember3Card", + "DataResourceUnionMember4", + "DataSource", + "PageInfo", +] + + +class DataCurrency(BaseModel): + code: str + + precision: str + """Precision factor for the currency, for example 100000000 for USD.""" + + +class DataResourceUnionMember0(BaseModel): + id: str + + logo_url: Optional[str] = None + + object: Literal["account"] + + route: Optional[str] = None + + title: Optional[str] = None + + +class DataResourceUnionMember1(BaseModel): + id: str + + name: Optional[str] = None + + object: Literal["user"] + + profile_picture_url: Optional[str] = None + + username: Optional[str] = None + + +class DataResourceUnionMember2OwnerUnionMember0(BaseModel): + id: str + + logo_url: Optional[str] = None + + object: Literal["account"] + + route: Optional[str] = None + + title: Optional[str] = None + + +class DataResourceUnionMember2OwnerUnionMember1(BaseModel): + id: str + + name: Optional[str] = None + + object: Literal["user"] + + profile_picture_url: Optional[str] = None + + username: Optional[str] = None + + +DataResourceUnionMember2Owner: TypeAlias = Union[ + DataResourceUnionMember2OwnerUnionMember0, DataResourceUnionMember2OwnerUnionMember1, None +] + + +class DataResourceUnionMember2(BaseModel): + id: str + + object: Literal["ledger_account"] + + owner: Optional[DataResourceUnionMember2Owner] = None + + +class DataResourceUnionMember3Bank(BaseModel): + account_name: Optional[str] = None + + account_type: Optional[str] = None + + bank_name: Optional[str] = None + + last4: Optional[str] = None + + +class DataResourceUnionMember3Card(BaseModel): + brand: Optional[str] = None + + exp_month: Optional[int] = None + + exp_year: Optional[int] = None + + last4: Optional[str] = None + + +class DataResourceUnionMember3(BaseModel): + id: str + + bank: Optional[DataResourceUnionMember3Bank] = None + + card: Optional[DataResourceUnionMember3Card] = None + + email_identifier: Optional[str] = None + + gateway_type: Optional[str] = None + + object: Literal["payment_method"] + + payment_method_type: Optional[str] = None + + +class DataResourceUnionMember4(BaseModel): + id: str + + account_reference: Optional[str] = None + + destination_currency_code: Optional[str] = None + + institution_name: Optional[str] = None + + nickname: Optional[str] = None + + object: Literal["payout_method"] + + provider: Optional[str] = None + + +DataResource: TypeAlias = Union[ + DataResourceUnionMember0, + DataResourceUnionMember1, + DataResourceUnionMember2, + DataResourceUnionMember3, + DataResourceUnionMember4, + None, +] + + +class DataSource(BaseModel): + id: str + + object: str + + if TYPE_CHECKING: + # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a + # value to this field, so for compatibility we avoid doing it at runtime. + __pydantic_extra__: Dict[str, builtins.object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride] + + # Stub to indicate that arbitrary properties are accepted. + # To access properties that are not valid identifiers you can use `getattr`, e.g. + # `getattr(obj, '$type')` + def __getattr__(self, attr: str) -> builtins.object: ... + else: + __pydantic_extra__: Dict[str, builtins.object] + + +class Data(BaseModel): + id: str + + amount: str + """Signed amount in the currency's smallest precision units.""" + + currency: DataCurrency + + line_type: str + + object: Literal["ledger_activity"] + + posted_at: datetime + + resource: Optional[DataResource] = None + + source: Optional[DataSource] = None + + +class PageInfo(BaseModel): + end_cursor: Optional[str] = None + + has_next_page: bool + + has_previous_page: bool + + start_cursor: Optional[str] = None + + +class FinancialActivityListResponse(BaseModel): + data: List[Data] + + page_info: PageInfo diff --git a/tests/api_resources/test_financial_activity.py b/tests/api_resources/test_financial_activity.py new file mode 100644 index 00000000..957a46d6 --- /dev/null +++ b/tests/api_resources/test_financial_activity.py @@ -0,0 +1,111 @@ +# 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 FinancialActivityListResponse +from whop_sdk._utils import parse_datetime + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestFinancialActivity: + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_list(self, client: Whop) -> None: + financial_activity = client.financial_activity.list() + assert_matches_type(FinancialActivityListResponse, financial_activity, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_list_with_all_params(self, client: Whop) -> None: + financial_activity = client.financial_activity.list( + account_id="account_id", + currency="currency", + cursor="cursor", + limit=100, + line_types=["string"], + posted_after=parse_datetime("2019-12-27T18:11:19.117Z"), + posted_before=parse_datetime("2019-12-27T18:11:19.117Z"), + user_id="user_id", + ) + assert_matches_type(FinancialActivityListResponse, financial_activity, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_raw_response_list(self, client: Whop) -> None: + response = client.financial_activity.with_raw_response.list() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + financial_activity = response.parse() + assert_matches_type(FinancialActivityListResponse, financial_activity, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_streaming_response_list(self, client: Whop) -> None: + with client.financial_activity.with_streaming_response.list() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + financial_activity = response.parse() + assert_matches_type(FinancialActivityListResponse, financial_activity, path=["response"]) + + assert cast(Any, response.is_closed) is True + + +class TestAsyncFinancialActivity: + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_list(self, async_client: AsyncWhop) -> None: + financial_activity = await async_client.financial_activity.list() + assert_matches_type(FinancialActivityListResponse, financial_activity, 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: + financial_activity = await async_client.financial_activity.list( + account_id="account_id", + currency="currency", + cursor="cursor", + limit=100, + line_types=["string"], + posted_after=parse_datetime("2019-12-27T18:11:19.117Z"), + posted_before=parse_datetime("2019-12-27T18:11:19.117Z"), + user_id="user_id", + ) + assert_matches_type(FinancialActivityListResponse, financial_activity, 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.financial_activity.with_raw_response.list() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + financial_activity = await response.parse() + assert_matches_type(FinancialActivityListResponse, financial_activity, 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.financial_activity.with_streaming_response.list() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + financial_activity = await response.parse() + assert_matches_type(FinancialActivityListResponse, financial_activity, path=["response"]) + + assert cast(Any, response.is_closed) is True From a8c8a33d3ac46e35362678593b0f917d9bf0f6ad Mon Sep 17 00:00:00 2001 From: stlc-bot Date: Mon, 8 Jun 2026 21:52:03 +0000 Subject: [PATCH 007/109] fix(ads): move ad group titles to `title` column and add indexes Stainless-Generated-From: f905ffc8bc262038609d3196827212f1ac2716c2 --- api.md | 6 +- src/whop_sdk/resources/ad_groups.py | 8 +++ src/whop_sdk/resources/swaps.py | 36 +++++++----- src/whop_sdk/resources/wallets.py | 62 +++++++++++++------- src/whop_sdk/types/__init__.py | 2 + src/whop_sdk/types/ad_group_update_params.py | 3 + src/whop_sdk/types/swap_retrieve_params.py | 12 ++++ src/whop_sdk/types/wallet_balance_params.py | 12 ++++ src/whop_sdk/types/wallet_send_params.py | 3 + tests/api_resources/test_ad_groups.py | 2 + tests/api_resources/test_swaps.py | 28 ++------- tests/api_resources/test_wallets.py | 54 ++++------------- 12 files changed, 124 insertions(+), 104 deletions(-) create mode 100644 src/whop_sdk/types/swap_retrieve_params.py create mode 100644 src/whop_sdk/types/wallet_balance_params.py diff --git a/api.md b/api.md index abd862a0..8f4e6e82 100644 --- a/api.md +++ b/api.md @@ -735,8 +735,8 @@ from whop_sdk.types import ( Methods: - client.wallets.list() -> WalletListResponse -- client.wallets.balance(account_id) -> WalletBalanceResponse -- client.wallets.send(account_id, \*\*params) -> WalletSendResponse +- client.wallets.balance(\*\*params) -> WalletBalanceResponse +- client.wallets.send(\*\*params) -> WalletSendResponse # FinancialActivity @@ -761,7 +761,7 @@ from whop_sdk.types import SwapCreateResponse, SwapRetrieveResponse, SwapCreateQ Methods: - client.swaps.create(\*\*params) -> SwapCreateResponse -- client.swaps.retrieve(account_id) -> SwapRetrieveResponse +- client.swaps.retrieve(\*\*params) -> SwapRetrieveResponse - client.swaps.create_quote(\*\*params) -> SwapCreateQuoteResponse # Deposits diff --git a/src/whop_sdk/resources/ad_groups.py b/src/whop_sdk/resources/ad_groups.py index bacfb9f4..ce4459d5 100644 --- a/src/whop_sdk/resources/ad_groups.py +++ b/src/whop_sdk/resources/ad_groups.py @@ -117,6 +117,7 @@ def update( name: Optional[str] | Omit = omit, platform_config: Optional[ad_group_update_params.PlatformConfig] | Omit = omit, status: Optional[AdGroupStatus] | 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, @@ -147,6 +148,8 @@ def update( status: The status of an external ad group. + title: Human-readable ad group title. + extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -168,6 +171,7 @@ def update( "name": name, "platform_config": platform_config, "status": status, + "title": title, }, ad_group_update_params.AdGroupUpdateParams, ), @@ -482,6 +486,7 @@ async def update( name: Optional[str] | Omit = omit, platform_config: Optional[ad_group_update_params.PlatformConfig] | Omit = omit, status: Optional[AdGroupStatus] | 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, @@ -512,6 +517,8 @@ async def update( status: The status of an external ad group. + title: Human-readable ad group title. + extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -533,6 +540,7 @@ async def update( "name": name, "platform_config": platform_config, "status": status, + "title": title, }, ad_group_update_params.AdGroupUpdateParams, ), diff --git a/src/whop_sdk/resources/swaps.py b/src/whop_sdk/resources/swaps.py index e95d744d..771bf068 100644 --- a/src/whop_sdk/resources/swaps.py +++ b/src/whop_sdk/resources/swaps.py @@ -6,9 +6,9 @@ import httpx -from ..types import swap_create_params, swap_create_quote_params +from ..types import swap_create_params, swap_retrieve_params, swap_create_quote_params from .._types import Body, Omit, Query, Headers, NotGiven, omit, not_given -from .._utils import path_template, maybe_transform, async_maybe_transform +from .._utils import maybe_transform, async_maybe_transform from .._compat import cached_property from .._resource import SyncAPIResource, AsyncAPIResource from .._response import ( @@ -65,7 +65,7 @@ def create( """Executes a swap from the account's wallet. Runs asynchronously — poll GET - /swaps/{account_id} for status. + /swaps?account_id=... for status. Args: account_id: Business or user account ID (biz*\\** / user*\\**). @@ -106,8 +106,8 @@ def create( def retrieve( self, - account_id: str, *, + account_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, @@ -119,6 +119,8 @@ def retrieve( Returns the status of the account's in-flight or most recent swap. Args: + account_id: Business or user account ID (biz*\\** / user*\\**). + extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -127,12 +129,14 @@ def retrieve( timeout: Override the client-level default timeout for this request, in seconds """ - if not account_id: - raise ValueError(f"Expected a non-empty value for `account_id` but received {account_id!r}") return self._get( - path_template("/swaps/{account_id}", account_id=account_id), + "/swaps", options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform({"account_id": account_id}, swap_retrieve_params.SwapRetrieveParams), ), cast_to=SwapRetrieveResponse, ) @@ -238,7 +242,7 @@ async def create( """Executes a swap from the account's wallet. Runs asynchronously — poll GET - /swaps/{account_id} for status. + /swaps?account_id=... for status. Args: account_id: Business or user account ID (biz*\\** / user*\\**). @@ -279,8 +283,8 @@ async def create( async def retrieve( self, - account_id: str, *, + account_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, @@ -292,6 +296,8 @@ async def retrieve( Returns the status of the account's in-flight or most recent swap. Args: + account_id: Business or user account ID (biz*\\** / user*\\**). + extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -300,12 +306,14 @@ async def retrieve( timeout: Override the client-level default timeout for this request, in seconds """ - if not account_id: - raise ValueError(f"Expected a non-empty value for `account_id` but received {account_id!r}") return await self._get( - path_template("/swaps/{account_id}", account_id=account_id), + "/swaps", options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=await async_maybe_transform({"account_id": account_id}, swap_retrieve_params.SwapRetrieveParams), ), cast_to=SwapRetrieveResponse, ) diff --git a/src/whop_sdk/resources/wallets.py b/src/whop_sdk/resources/wallets.py index 42e8018d..3bc4c9d4 100644 --- a/src/whop_sdk/resources/wallets.py +++ b/src/whop_sdk/resources/wallets.py @@ -4,9 +4,9 @@ import httpx -from ..types import wallet_send_params +from ..types import wallet_send_params, wallet_balance_params from .._types import Body, Query, Headers, NotGiven, not_given -from .._utils import path_template, maybe_transform, async_maybe_transform +from .._utils import maybe_transform, async_maybe_transform from .._compat import cached_property from .._resource import SyncAPIResource, AsyncAPIResource from .._response import ( @@ -64,8 +64,8 @@ def list( def balance( self, - account_id: str, *, + account_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, @@ -77,6 +77,8 @@ def balance( Returns per-token balances held in an account's wallet. Args: + account_id: The business or user account ID whose wallet balance should be returned. + extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -85,20 +87,22 @@ def balance( timeout: Override the client-level default timeout for this request, in seconds """ - if not account_id: - raise ValueError(f"Expected a non-empty value for `account_id` but received {account_id!r}") return self._get( - path_template("/wallets/{account_id}/balance", account_id=account_id), + "/wallets/balance", options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform({"account_id": account_id}, wallet_balance_params.WalletBalanceParams), ), cast_to=WalletBalanceResponse, ) def send( self, - account_id: str, *, + account_id: str, amount: str, to: str, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. @@ -112,6 +116,8 @@ def send( Sends USDT from an account's wallet to another Whop user or business. Args: + account_id: The sending account ID. + amount: USDT amount to send. to: Recipient user ID, business account ID, ledger account ID, or email. @@ -124,10 +130,8 @@ def send( timeout: Override the client-level default timeout for this request, in seconds """ - if not account_id: - raise ValueError(f"Expected a non-empty value for `account_id` but received {account_id!r}") return self._post( - path_template("/wallets/{account_id}/sends", account_id=account_id), + "/wallets/send", body=maybe_transform( { "amount": amount, @@ -136,7 +140,11 @@ def send( wallet_send_params.WalletSendParams, ), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform({"account_id": account_id}, wallet_send_params.WalletSendParams), ), cast_to=WalletSendResponse, ) @@ -183,8 +191,8 @@ async def list( async def balance( self, - account_id: str, *, + account_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, @@ -196,6 +204,8 @@ async def balance( Returns per-token balances held in an account's wallet. Args: + account_id: The business or user account ID whose wallet balance should be returned. + extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -204,20 +214,24 @@ async def balance( timeout: Override the client-level default timeout for this request, in seconds """ - if not account_id: - raise ValueError(f"Expected a non-empty value for `account_id` but received {account_id!r}") return await self._get( - path_template("/wallets/{account_id}/balance", account_id=account_id), + "/wallets/balance", options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=await async_maybe_transform( + {"account_id": account_id}, wallet_balance_params.WalletBalanceParams + ), ), cast_to=WalletBalanceResponse, ) async def send( self, - account_id: str, *, + account_id: str, amount: str, to: str, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. @@ -231,6 +245,8 @@ async def send( Sends USDT from an account's wallet to another Whop user or business. Args: + account_id: The sending account ID. + amount: USDT amount to send. to: Recipient user ID, business account ID, ledger account ID, or email. @@ -243,10 +259,8 @@ async def send( timeout: Override the client-level default timeout for this request, in seconds """ - if not account_id: - raise ValueError(f"Expected a non-empty value for `account_id` but received {account_id!r}") return await self._post( - path_template("/wallets/{account_id}/sends", account_id=account_id), + "/wallets/send", body=await async_maybe_transform( { "amount": amount, @@ -255,7 +269,11 @@ async def send( wallet_send_params.WalletSendParams, ), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=await async_maybe_transform({"account_id": account_id}, wallet_send_params.WalletSendParams), ), cast_to=WalletSendResponse, ) diff --git a/src/whop_sdk/types/__init__.py b/src/whop_sdk/types/__init__.py index a0326517..23a936a3 100644 --- a/src/whop_sdk/types/__init__.py +++ b/src/whop_sdk/types/__init__.py @@ -178,6 +178,7 @@ from .shipment_list_params import ShipmentListParams as ShipmentListParams from .social_link_websites import SocialLinkWebsites as SocialLinkWebsites from .swap_create_response import SwapCreateResponse as SwapCreateResponse +from .swap_retrieve_params import SwapRetrieveParams as SwapRetrieveParams from .transfer_list_params import TransferListParams as TransferListParams from .unwrap_webhook_event import UnwrapWebhookEvent as UnwrapWebhookEvent from .user_retrieve_params import UserRetrieveParams as UserRetrieveParams @@ -211,6 +212,7 @@ from .product_update_params import ProductUpdateParams as ProductUpdateParams from .refund_reference_type import RefundReferenceType as RefundReferenceType from .topup_create_response import TopupCreateResponse as TopupCreateResponse +from .wallet_balance_params import WalletBalanceParams as WalletBalanceParams from .webhook_create_params import WebhookCreateParams as WebhookCreateParams from .webhook_list_response import WebhookListResponse as WebhookListResponse from .webhook_update_params import WebhookUpdateParams as WebhookUpdateParams diff --git a/src/whop_sdk/types/ad_group_update_params.py b/src/whop_sdk/types/ad_group_update_params.py index 97f6403e..8b5b49ab 100644 --- a/src/whop_sdk/types/ad_group_update_params.py +++ b/src/whop_sdk/types/ad_group_update_params.py @@ -66,6 +66,9 @@ class AdGroupUpdateParams(TypedDict, total=False): status: Optional[AdGroupStatus] """The status of an external ad group.""" + title: Optional[str] + """Human-readable ad group title.""" + class ConfigTargeting(TypedDict, total=False): """Audience targeting settings (demographics, geo, interests, audiences, devices).""" diff --git a/src/whop_sdk/types/swap_retrieve_params.py b/src/whop_sdk/types/swap_retrieve_params.py new file mode 100644 index 00000000..8607fab8 --- /dev/null +++ b/src/whop_sdk/types/swap_retrieve_params.py @@ -0,0 +1,12 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Required, TypedDict + +__all__ = ["SwapRetrieveParams"] + + +class SwapRetrieveParams(TypedDict, total=False): + account_id: Required[str] + """Business or user account ID (biz*\\** / user*\\**).""" diff --git a/src/whop_sdk/types/wallet_balance_params.py b/src/whop_sdk/types/wallet_balance_params.py new file mode 100644 index 00000000..0c92e9fa --- /dev/null +++ b/src/whop_sdk/types/wallet_balance_params.py @@ -0,0 +1,12 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Required, TypedDict + +__all__ = ["WalletBalanceParams"] + + +class WalletBalanceParams(TypedDict, total=False): + account_id: Required[str] + """The business or user account ID whose wallet balance should be returned.""" diff --git a/src/whop_sdk/types/wallet_send_params.py b/src/whop_sdk/types/wallet_send_params.py index 03889c27..79978b9e 100644 --- a/src/whop_sdk/types/wallet_send_params.py +++ b/src/whop_sdk/types/wallet_send_params.py @@ -8,6 +8,9 @@ class WalletSendParams(TypedDict, total=False): + account_id: Required[str] + """The sending account ID.""" + amount: Required[str] """USDT amount to send.""" diff --git a/tests/api_resources/test_ad_groups.py b/tests/api_resources/test_ad_groups.py index 6f7d99fc..8f7fd044 100644 --- a/tests/api_resources/test_ad_groups.py +++ b/tests/api_resources/test_ad_groups.py @@ -429,6 +429,7 @@ def test_method_update_with_all_params(self, client: Whop) -> None: }, }, status="active", + title="title", ) assert_matches_type(AdGroup, ad_group, path=["response"]) @@ -1053,6 +1054,7 @@ async def test_method_update_with_all_params(self, async_client: AsyncWhop) -> N }, }, status="active", + title="title", ) assert_matches_type(AdGroup, ad_group, path=["response"]) diff --git a/tests/api_resources/test_swaps.py b/tests/api_resources/test_swaps.py index de7e8803..1814d7b7 100644 --- a/tests/api_resources/test_swaps.py +++ b/tests/api_resources/test_swaps.py @@ -82,7 +82,7 @@ def test_streaming_response_create(self, client: Whop) -> None: @parametrize def test_method_retrieve(self, client: Whop) -> None: swap = client.swaps.retrieve( - "account_id", + account_id="account_id", ) assert_matches_type(SwapRetrieveResponse, swap, path=["response"]) @@ -90,7 +90,7 @@ def test_method_retrieve(self, client: Whop) -> None: @parametrize def test_raw_response_retrieve(self, client: Whop) -> None: response = client.swaps.with_raw_response.retrieve( - "account_id", + account_id="account_id", ) assert response.is_closed is True @@ -102,7 +102,7 @@ def test_raw_response_retrieve(self, client: Whop) -> None: @parametrize def test_streaming_response_retrieve(self, client: Whop) -> None: with client.swaps.with_streaming_response.retrieve( - "account_id", + account_id="account_id", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -112,14 +112,6 @@ def test_streaming_response_retrieve(self, client: Whop) -> None: 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 `account_id` but received ''"): - client.swaps.with_raw_response.retrieve( - "", - ) - @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_method_create_quote(self, client: Whop) -> None: @@ -243,7 +235,7 @@ async def test_streaming_response_create(self, async_client: AsyncWhop) -> None: @parametrize async def test_method_retrieve(self, async_client: AsyncWhop) -> None: swap = await async_client.swaps.retrieve( - "account_id", + account_id="account_id", ) assert_matches_type(SwapRetrieveResponse, swap, path=["response"]) @@ -251,7 +243,7 @@ async def test_method_retrieve(self, async_client: AsyncWhop) -> None: @parametrize async def test_raw_response_retrieve(self, async_client: AsyncWhop) -> None: response = await async_client.swaps.with_raw_response.retrieve( - "account_id", + account_id="account_id", ) assert response.is_closed is True @@ -263,7 +255,7 @@ async def test_raw_response_retrieve(self, async_client: AsyncWhop) -> None: @parametrize async def test_streaming_response_retrieve(self, async_client: AsyncWhop) -> None: async with async_client.swaps.with_streaming_response.retrieve( - "account_id", + account_id="account_id", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -273,14 +265,6 @@ async def test_streaming_response_retrieve(self, async_client: AsyncWhop) -> Non 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 `account_id` but received ''"): - await async_client.swaps.with_raw_response.retrieve( - "", - ) - @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_method_create_quote(self, async_client: AsyncWhop) -> None: diff --git a/tests/api_resources/test_wallets.py b/tests/api_resources/test_wallets.py index abc5ad15..9627b766 100644 --- a/tests/api_resources/test_wallets.py +++ b/tests/api_resources/test_wallets.py @@ -9,7 +9,11 @@ from whop_sdk import Whop, AsyncWhop from tests.utils import assert_matches_type -from whop_sdk.types import WalletListResponse, WalletSendResponse, WalletBalanceResponse +from whop_sdk.types import ( + WalletListResponse, + WalletSendResponse, + WalletBalanceResponse, +) base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") @@ -49,7 +53,7 @@ def test_streaming_response_list(self, client: Whop) -> None: @parametrize def test_method_balance(self, client: Whop) -> None: wallet = client.wallets.balance( - "account_id", + account_id="account_id", ) assert_matches_type(WalletBalanceResponse, wallet, path=["response"]) @@ -57,7 +61,7 @@ def test_method_balance(self, client: Whop) -> None: @parametrize def test_raw_response_balance(self, client: Whop) -> None: response = client.wallets.with_raw_response.balance( - "account_id", + account_id="account_id", ) assert response.is_closed is True @@ -69,7 +73,7 @@ def test_raw_response_balance(self, client: Whop) -> None: @parametrize def test_streaming_response_balance(self, client: Whop) -> None: with client.wallets.with_streaming_response.balance( - "account_id", + account_id="account_id", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -79,14 +83,6 @@ def test_streaming_response_balance(self, client: Whop) -> None: assert cast(Any, response.is_closed) is True - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - def test_path_params_balance(self, client: Whop) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `account_id` but received ''"): - client.wallets.with_raw_response.balance( - "", - ) - @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_method_send(self, client: Whop) -> None: @@ -127,16 +123,6 @@ def test_streaming_response_send(self, client: Whop) -> None: assert cast(Any, response.is_closed) is True - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - def test_path_params_send(self, client: Whop) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `account_id` but received ''"): - client.wallets.with_raw_response.send( - account_id="", - amount="amount", - to="to", - ) - class TestAsyncWallets: parametrize = pytest.mark.parametrize( @@ -175,7 +161,7 @@ async def test_streaming_response_list(self, async_client: AsyncWhop) -> None: @parametrize async def test_method_balance(self, async_client: AsyncWhop) -> None: wallet = await async_client.wallets.balance( - "account_id", + account_id="account_id", ) assert_matches_type(WalletBalanceResponse, wallet, path=["response"]) @@ -183,7 +169,7 @@ async def test_method_balance(self, async_client: AsyncWhop) -> None: @parametrize async def test_raw_response_balance(self, async_client: AsyncWhop) -> None: response = await async_client.wallets.with_raw_response.balance( - "account_id", + account_id="account_id", ) assert response.is_closed is True @@ -195,7 +181,7 @@ async def test_raw_response_balance(self, async_client: AsyncWhop) -> None: @parametrize async def test_streaming_response_balance(self, async_client: AsyncWhop) -> None: async with async_client.wallets.with_streaming_response.balance( - "account_id", + account_id="account_id", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -205,14 +191,6 @@ async def test_streaming_response_balance(self, async_client: AsyncWhop) -> None assert cast(Any, response.is_closed) is True - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - async def test_path_params_balance(self, async_client: AsyncWhop) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `account_id` but received ''"): - await async_client.wallets.with_raw_response.balance( - "", - ) - @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_method_send(self, async_client: AsyncWhop) -> None: @@ -252,13 +230,3 @@ async def test_streaming_response_send(self, async_client: AsyncWhop) -> None: assert_matches_type(WalletSendResponse, wallet, 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_send(self, async_client: AsyncWhop) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `account_id` but received ''"): - await async_client.wallets.with_raw_response.send( - account_id="", - amount="amount", - to="to", - ) From cb2032dc5d75601bede8da80f37156088a91b127 Mon Sep 17 00:00:00 2001 From: stlc-bot Date: Mon, 8 Jun 2026 23:35:18 +0000 Subject: [PATCH 008/109] feat(ads): forward custom pixel events to Meta CAPI (ENG-23505) Stainless-Generated-From: 07a1dd4f4696f56e9ce05df5e8dd259e0f99b8c2 --- src/whop_sdk/resources/conversions.py | 4 ++-- src/whop_sdk/types/conversion_create_params.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/whop_sdk/resources/conversions.py b/src/whop_sdk/resources/conversions.py index 4c351f91..8f5cf755 100644 --- a/src/whop_sdk/resources/conversions.py +++ b/src/whop_sdk/resources/conversions.py @@ -117,7 +117,7 @@ def create( currency: The available currencies on the platform - custom_name: Custom event name when event_name is 'custom'. + custom_name: Custom event name when event_name is 'custom'. Maximum 35 chars for this value. duration: For 'leave' events: milliseconds the visitor spent on the page. @@ -275,7 +275,7 @@ async def create( currency: The available currencies on the platform - custom_name: Custom event name when event_name is 'custom'. + custom_name: Custom event name when event_name is 'custom'. Maximum 35 chars for this value. duration: For 'leave' events: milliseconds the visitor spent on the page. diff --git a/src/whop_sdk/types/conversion_create_params.py b/src/whop_sdk/types/conversion_create_params.py index 21b04154..eb09b960 100644 --- a/src/whop_sdk/types/conversion_create_params.py +++ b/src/whop_sdk/types/conversion_create_params.py @@ -53,7 +53,7 @@ class ConversionCreateParams(TypedDict, total=False): """The available currencies on the platform""" custom_name: Optional[str] - """Custom event name when event_name is 'custom'.""" + """Custom event name when event_name is 'custom'. Maximum 35 chars for this value.""" duration: Optional[int] """For 'leave' events: milliseconds the visitor spent on the page.""" From e741c763ca70d51b6806688a258c9ada5d7c3959 Mon Sep 17 00:00:00 2001 From: danielschwartz4 <52050264+danielschwartz4@users.noreply.github.com> Date: Mon, 8 Jun 2026 17:53:55 -0700 Subject: [PATCH 009/109] Build SDK Stainless-Generated-From: 74ef5645fb3bfc37bd8ce125f2bcb72e42f6ddfd --- .stats.yml | 2 +- README.md | 4 +- api.md | 5 +- src/whop_sdk/_client.py | 26 +- src/whop_sdk/resources/conversions.py | 4 +- src/whop_sdk/resources/users.py | 271 +++++++++++------- src/whop_sdk/types/__init__.py | 2 +- .../types/conversion_create_params.py | 2 +- src/whop_sdk/types/user.py | 40 +-- .../types/user_check_access_response.py | 9 +- src/whop_sdk/types/user_list_params.py | 21 +- src/whop_sdk/types/user_list_response.py | 49 ---- src/whop_sdk/types/user_retrieve_params.py | 7 +- src/whop_sdk/types/user_update_me_params.py | 23 ++ src/whop_sdk/types/user_update_params.py | 35 +-- tests/api_resources/test_users.py | 169 ++++++++--- 16 files changed, 377 insertions(+), 292 deletions(-) delete mode 100644 src/whop_sdk/types/user_list_response.py create mode 100644 src/whop_sdk/types/user_update_me_params.py diff --git a/.stats.yml b/.stats.yml index f1fe8e08..cd5b9cf9 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1 +1 @@ -configured_endpoints: 229 +configured_endpoints: 230 diff --git a/README.md b/README.md index f695ea0b..f4049761 100644 --- a/README.md +++ b/README.md @@ -13,8 +13,8 @@ It is generated with [Stainless](https://www.stainless.com/). Use the Whop MCP Server to enable AI assistants to interact with this API, allowing them to explore endpoints, make test requests, and use documentation to help integrate this SDK into your application. -[![Add to Cursor](https://cursor.com/deeplink/mcp-install-dark.svg)](https://cursor.com/en-US/install-mcp?name=%40whop%2Fmcp&config=eyJjb21tYW5kIjoibnB4IiwiYXJncyI6WyIteSIsIkB3aG9wL21jcCJdLCJlbnYiOnsiV0hPUF9BUElfS0VZIjoiTXkgQVBJIEtleSIsIldIT1BfV0VCSE9PS19TRUNSRVQiOiJNeSBXZWJob29rIEtleSIsIldIT1BfQVBQX0lEIjoiYXBwX3h4eHh4eHh4eHh4eHh4In19) -[![Install in VS Code](https://img.shields.io/badge/_-Add_to_VS_Code-blue?style=for-the-badge&logo=data:image/svg%2bxml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGZpbGw9Im5vbmUiIHZpZXdCb3g9IjAgMCA0MCA0MCI+PHBhdGggZmlsbD0iI0VFRSIgZmlsbC1ydWxlPSJldmVub2RkIiBkPSJNMzAuMjM1IDM5Ljg4NGEyLjQ5MSAyLjQ5MSAwIDAgMS0xLjc4MS0uNzNMMTIuNyAyNC43OGwtMy40NiAyLjYyNC0zLjQwNiAyLjU4MmExLjY2NSAxLjY2NSAwIDAgMS0xLjA4Mi4zMzggMS42NjQgMS42NjQgMCAwIDEtMS4wNDYtLjQzMWwtMi4yLTJhMS42NjYgMS42NjYgMCAwIDEgMC0yLjQ2M0w3LjQ1OCAyMCA0LjY3IDE3LjQ1MyAxLjUwNyAxNC41N2ExLjY2NSAxLjY2NSAwIDAgMSAwLTIuNDYzbDIuMi0yYTEuNjY1IDEuNjY1IDAgMCAxIDIuMTMtLjA5N2w2Ljg2MyA1LjIwOUwyOC40NTIuODQ0YTIuNDg4IDIuNDg4IDAgMCAxIDEuODQxLS43MjljLjM1MS4wMDkuNjk5LjA5MSAxLjAxOS4yNDVsOC4yMzYgMy45NjFhMi41IDIuNSAwIDAgMSAxLjQxNSAyLjI1M3YuMDk5LS4wNDVWMzMuMzd2LS4wNDUuMDk1YTIuNTAxIDIuNTAxIDAgMCAxLTEuNDE2IDIuMjU3bC04LjIzNSAzLjk2MWEyLjQ5MiAyLjQ5MiAwIDAgMS0xLjA3Ny4yNDZabS43MTYtMjguOTQ3LTExLjk0OCA5LjA2MiAxMS45NTIgOS4wNjUtLjAwNC0xOC4xMjdaIi8+PC9zdmc+)](https://vscode.stainless.com/mcp/%7B%22name%22%3A%22%40whop%2Fmcp%22%2C%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22-y%22%2C%22%40whop%2Fmcp%22%5D%2C%22env%22%3A%7B%22WHOP_API_KEY%22%3A%22My%20API%20Key%22%2C%22WHOP_WEBHOOK_SECRET%22%3A%22My%20Webhook%20Key%22%2C%22WHOP_APP_ID%22%3A%22app_xxxxxxxxxxxxxx%22%7D%7D) +[![Add to Cursor](https://cursor.com/deeplink/mcp-install-dark.svg)](https://cursor.com/en-US/install-mcp?name=%40whop%2Fmcp&config=eyJjb21tYW5kIjoibnB4IiwiYXJncyI6WyIteSIsIkB3aG9wL21jcCJdLCJlbnYiOnsiV0hPUF9BUElfS0VZIjoiTXkgQVBJIEtleSIsIldIT1BfV0VCSE9PS19TRUNSRVQiOiJNeSBXZWJob29rIEtleSIsIldIT1BfQVBQX0lEIjoiYXBwX3h4eHh4eHh4eHh4eHh4IiwiV0hPUF9BUElfVkVSU0lPTiI6IjIwMjYtMDYtMDgifX0) +[![Install in VS Code](https://img.shields.io/badge/_-Add_to_VS_Code-blue?style=for-the-badge&logo=data:image/svg%2bxml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGZpbGw9Im5vbmUiIHZpZXdCb3g9IjAgMCA0MCA0MCI+PHBhdGggZmlsbD0iI0VFRSIgZmlsbC1ydWxlPSJldmVub2RkIiBkPSJNMzAuMjM1IDM5Ljg4NGEyLjQ5MSAyLjQ5MSAwIDAgMS0xLjc4MS0uNzNMMTIuNyAyNC43OGwtMy40NiAyLjYyNC0zLjQwNiAyLjU4MmExLjY2NSAxLjY2NSAwIDAgMS0xLjA4Mi4zMzggMS42NjQgMS42NjQgMCAwIDEtMS4wNDYtLjQzMWwtMi4yLTJhMS42NjYgMS42NjYgMCAwIDEgMC0yLjQ2M0w3LjQ1OCAyMCA0LjY3IDE3LjQ1MyAxLjUwNyAxNC41N2ExLjY2NSAxLjY2NSAwIDAgMSAwLTIuNDYzbDIuMi0yYTEuNjY1IDEuNjY1IDAgMCAxIDIuMTMtLjA5N2w2Ljg2MyA1LjIwOUwyOC40NTIuODQ0YTIuNDg4IDIuNDg4IDAgMCAxIDEuODQxLS43MjljLjM1MS4wMDkuNjk5LjA5MSAxLjAxOS4yNDVsOC4yMzYgMy45NjFhMi41IDIuNSAwIDAgMSAxLjQxNSAyLjI1M3YuMDk5LS4wNDVWMzMuMzd2LS4wNDUuMDk1YTIuNTAxIDIuNTAxIDAgMCAxLTEuNDE2IDIuMjU3bC04LjIzNSAzLjk2MWEyLjQ5MiAyLjQ5MiAwIDAgMS0xLjA3Ny4yNDZabS43MTYtMjguOTQ3LTExLjk0OCA5LjA2MiAxMS45NTIgOS4wNjUtLjAwNC0xOC4xMjdaIi8+PC9zdmc+)](https://vscode.stainless.com/mcp/%7B%22name%22%3A%22%40whop%2Fmcp%22%2C%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22-y%22%2C%22%40whop%2Fmcp%22%5D%2C%22env%22%3A%7B%22WHOP_API_KEY%22%3A%22My%20API%20Key%22%2C%22WHOP_WEBHOOK_SECRET%22%3A%22My%20Webhook%20Key%22%2C%22WHOP_APP_ID%22%3A%22app_xxxxxxxxxxxxxx%22%2C%22WHOP_API_VERSION%22%3A%222026-06-08%22%7D%7D) > Note: You may need to set environment variables in your MCP client. diff --git a/api.md b/api.md index 8f4e6e82..401d5f4f 100644 --- a/api.md +++ b/api.md @@ -387,15 +387,16 @@ Methods: Types: ```python -from whop_sdk.types import User, UserListResponse, UserCheckAccessResponse +from whop_sdk.types import User, UserCheckAccessResponse ``` Methods: - client.users.retrieve(id, \*\*params) -> User - client.users.update(id, \*\*params) -> User -- client.users.list(\*\*params) -> SyncCursorPage[UserListResponse] +- client.users.list(\*\*params) -> SyncCursorPage[User] - client.users.check_access(resource_id, \*, id) -> UserCheckAccessResponse +- client.users.update_me(\*\*params) -> User # Payments diff --git a/src/whop_sdk/_client.py b/src/whop_sdk/_client.py index 4455e1e1..1259e198 100644 --- a/src/whop_sdk/_client.py +++ b/src/whop_sdk/_client.py @@ -180,6 +180,7 @@ class Whop(SyncAPIClient): api_key: str webhook_key: str | None app_id: str | None + version: str | None def __init__( self, @@ -187,6 +188,7 @@ def __init__( api_key: str | None = None, webhook_key: str | None = None, app_id: str | None = None, + version: str | None = None, base_url: str | httpx.URL | None = None, timeout: float | Timeout | None | NotGiven = not_given, max_retries: int = DEFAULT_MAX_RETRIES, @@ -212,6 +214,7 @@ def __init__( - `api_key` from `WHOP_API_KEY` - `webhook_key` from `WHOP_WEBHOOK_SECRET` - `app_id` from `WHOP_APP_ID` + - `version` from `WHOP_API_VERSION` """ if api_key is None: api_key = os.environ.get("WHOP_API_KEY") @@ -229,6 +232,10 @@ def __init__( app_id = os.environ.get("WHOP_APP_ID") self.app_id = app_id + if version is None: + version = os.environ.get("WHOP_API_VERSION") or "2026-06-08" + self.version = version + if base_url is None: base_url = os.environ.get("WHOP_BASE_URL") if base_url is None: @@ -382,7 +389,6 @@ def chat_channels(self) -> ChatChannelsResource: @cached_property def users(self) -> UsersResource: - """Users""" from .resources.users import UsersResource return UsersResource(self) @@ -723,6 +729,7 @@ def default_headers(self) -> dict[str, str | Omit]: **super().default_headers, "X-Stainless-Async": "false", "X-Whop-App-Id": self.app_id if self.app_id is not None else Omit(), + "Api-Version-Date": self.version if self.version is not None else Omit(), **self._custom_headers, } @@ -732,6 +739,7 @@ def copy( api_key: str | None = None, webhook_key: str | None = None, app_id: str | None = None, + version: str | None = None, base_url: str | httpx.URL | None = None, timeout: float | Timeout | None | NotGiven = not_given, http_client: httpx.Client | None = None, @@ -768,6 +776,7 @@ def copy( api_key=api_key or self.api_key, webhook_key=webhook_key or self.webhook_key, app_id=app_id or self.app_id, + version=version or self.version, base_url=base_url or self.base_url, timeout=self.timeout if isinstance(timeout, NotGiven) else timeout, http_client=http_client, @@ -820,6 +829,7 @@ class AsyncWhop(AsyncAPIClient): api_key: str webhook_key: str | None app_id: str | None + version: str | None def __init__( self, @@ -827,6 +837,7 @@ def __init__( api_key: str | None = None, webhook_key: str | None = None, app_id: str | None = None, + version: str | None = None, base_url: str | httpx.URL | None = None, timeout: float | Timeout | None | NotGiven = not_given, max_retries: int = DEFAULT_MAX_RETRIES, @@ -852,6 +863,7 @@ def __init__( - `api_key` from `WHOP_API_KEY` - `webhook_key` from `WHOP_WEBHOOK_SECRET` - `app_id` from `WHOP_APP_ID` + - `version` from `WHOP_API_VERSION` """ if api_key is None: api_key = os.environ.get("WHOP_API_KEY") @@ -869,6 +881,10 @@ def __init__( app_id = os.environ.get("WHOP_APP_ID") self.app_id = app_id + if version is None: + version = os.environ.get("WHOP_API_VERSION") or "2026-06-08" + self.version = version + if base_url is None: base_url = os.environ.get("WHOP_BASE_URL") if base_url is None: @@ -1022,7 +1038,6 @@ def chat_channels(self) -> AsyncChatChannelsResource: @cached_property def users(self) -> AsyncUsersResource: - """Users""" from .resources.users import AsyncUsersResource return AsyncUsersResource(self) @@ -1363,6 +1378,7 @@ def default_headers(self) -> dict[str, str | Omit]: **super().default_headers, "X-Stainless-Async": f"async:{get_async_library()}", "X-Whop-App-Id": self.app_id if self.app_id is not None else Omit(), + "Api-Version-Date": self.version if self.version is not None else Omit(), **self._custom_headers, } @@ -1372,6 +1388,7 @@ def copy( api_key: str | None = None, webhook_key: str | None = None, app_id: str | None = None, + version: str | None = None, base_url: str | httpx.URL | None = None, timeout: float | Timeout | None | NotGiven = not_given, http_client: httpx.AsyncClient | None = None, @@ -1408,6 +1425,7 @@ def copy( api_key=api_key or self.api_key, webhook_key=webhook_key or self.webhook_key, app_id=app_id or self.app_id, + version=version or self.version, base_url=base_url or self.base_url, timeout=self.timeout if isinstance(timeout, NotGiven) else timeout, http_client=http_client, @@ -1589,7 +1607,6 @@ def chat_channels(self) -> chat_channels.ChatChannelsResourceWithRawResponse: @cached_property def users(self) -> users.UsersResourceWithRawResponse: - """Users""" from .resources.users import UsersResourceWithRawResponse return UsersResourceWithRawResponse(self._client.users) @@ -2041,7 +2058,6 @@ def chat_channels(self) -> chat_channels.AsyncChatChannelsResourceWithRawRespons @cached_property def users(self) -> users.AsyncUsersResourceWithRawResponse: - """Users""" from .resources.users import AsyncUsersResourceWithRawResponse return AsyncUsersResourceWithRawResponse(self._client.users) @@ -2495,7 +2511,6 @@ def chat_channels(self) -> chat_channels.ChatChannelsResourceWithStreamingRespon @cached_property def users(self) -> users.UsersResourceWithStreamingResponse: - """Users""" from .resources.users import UsersResourceWithStreamingResponse return UsersResourceWithStreamingResponse(self._client.users) @@ -2951,7 +2966,6 @@ def chat_channels(self) -> chat_channels.AsyncChatChannelsResourceWithStreamingR @cached_property def users(self) -> users.AsyncUsersResourceWithStreamingResponse: - """Users""" from .resources.users import AsyncUsersResourceWithStreamingResponse return AsyncUsersResourceWithStreamingResponse(self._client.users) diff --git a/src/whop_sdk/resources/conversions.py b/src/whop_sdk/resources/conversions.py index 8f5cf755..4c351f91 100644 --- a/src/whop_sdk/resources/conversions.py +++ b/src/whop_sdk/resources/conversions.py @@ -117,7 +117,7 @@ def create( currency: The available currencies on the platform - custom_name: Custom event name when event_name is 'custom'. Maximum 35 chars for this value. + custom_name: Custom event name when event_name is 'custom'. duration: For 'leave' events: milliseconds the visitor spent on the page. @@ -275,7 +275,7 @@ async def create( currency: The available currencies on the platform - custom_name: Custom event name when event_name is 'custom'. Maximum 35 chars for this value. + custom_name: Custom event name when event_name is 'custom'. duration: For 'leave' events: milliseconds the visitor spent on the page. diff --git a/src/whop_sdk/resources/users.py b/src/whop_sdk/resources/users.py index e0ce27c0..1468d977 100644 --- a/src/whop_sdk/resources/users.py +++ b/src/whop_sdk/resources/users.py @@ -2,11 +2,9 @@ from __future__ import annotations -from typing import Optional - import httpx -from ..types import user_list_params, user_update_params, user_retrieve_params +from ..types import user_list_params, user_update_params, user_retrieve_params, user_update_me_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 @@ -20,15 +18,12 @@ from ..pagination import SyncCursorPage, AsyncCursorPage from ..types.user import User from .._base_client import AsyncPaginator, make_request_options -from ..types.user_list_response import UserListResponse from ..types.user_check_access_response import UserCheckAccessResponse __all__ = ["UsersResource", "AsyncUsersResource"] class UsersResource(SyncAPIResource): - """Users""" - @cached_property def with_raw_response(self) -> UsersResourceWithRawResponse: """ @@ -52,7 +47,7 @@ def retrieve( self, id: str, *, - company_id: Optional[str] | Omit = omit, + account_id: 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, @@ -61,11 +56,11 @@ def retrieve( timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> User: """ - Retrieves the details of an existing user. + Retrieves a user's public profile by user\\__ tag, username, or 'me'. Args: - company_id: When provided, returns the user's company-specific profile overrides (name, - profile picture) instead of their global profile. + account_id: When set, returns the user's account-specific profile overrides for this + account. extra_headers: Send extra headers @@ -84,7 +79,7 @@ def retrieve( extra_query=extra_query, extra_body=extra_body, timeout=timeout, - query=maybe_transform({"company_id": company_id}, user_retrieve_params.UserRetrieveParams), + query=maybe_transform({"account_id": account_id}, user_retrieve_params.UserRetrieveParams), ), cast_to=User, ) @@ -93,11 +88,9 @@ def update( self, id: str, *, - bio: Optional[str] | Omit = omit, - company_id: Optional[str] | Omit = omit, - name: Optional[str] | Omit = omit, - profile_picture: Optional[user_update_params.ProfilePicture] | Omit = omit, - username: Optional[str] | Omit = omit, + account_id: str | Omit = omit, + bio: str | Omit = omit, + name: 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, @@ -105,26 +98,13 @@ def update( extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> User: - """ - Update a user's profile by their ID. - - Required permissions: + """Updates a user. - - `user:profile:update` + A user token updates their own global profile; an API key + updates the user's account-specific profile override (account_id required). Args: - bio: A short biography displayed on the user's public profile. - - company_id: When provided, updates the user's profile overrides for this company instead of - the global profile. Pass name and profile_picture to set overrides, or null to - clear them. - - name: The user's display name shown on their public profile. Maximum 100 characters. - - profile_picture: The user's profile picture image attachment. - - username: The user's unique username. Alphanumeric characters and hyphens only. Maximum 42 - characters. + account_id: The account whose profile override to update. Required for API key callers. extra_headers: Send extra headers @@ -141,15 +121,16 @@ def update( body=maybe_transform( { "bio": bio, - "company_id": company_id, "name": name, - "profile_picture": profile_picture, - "username": username, }, user_update_params.UserUpdateParams, ), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform({"account_id": account_id}, user_update_params.UserUpdateParams), ), cast_to=User, ) @@ -157,32 +138,33 @@ def update( def list( self, *, - after: Optional[str] | Omit = omit, - before: Optional[str] | Omit = omit, - first: Optional[int] | Omit = omit, - last: Optional[int] | Omit = omit, - query: Optional[str] | Omit = omit, + after: str | Omit = omit, + before: str | Omit = omit, + first: int | Omit = omit, + last: int | Omit = omit, + query: 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, - ) -> SyncCursorPage[UserListResponse]: + ) -> SyncCursorPage[User]: """ Search for users by name or username, ranked by social proximity to the - authenticated user. + authenticated user. Returns the user's most recently followed users when no + query is given. Args: - after: Returns the elements in the list that come after the specified cursor. + after: A cursor; returns users after this position. - before: Returns the elements in the list that come before the specified cursor. + before: A cursor; returns users before this position. - first: Returns the first _n_ elements from the list. + first: The number of users to return (max 50). - last: Returns the last _n_ elements from the list. + last: The number of users to return from the end of the range. - query: Search term to filter by name or username. + query: A search term to filter users by name or username. extra_headers: Send extra headers @@ -194,7 +176,7 @@ def list( """ return self._get_api_list( "/users", - page=SyncCursorPage[UserListResponse], + page=SyncCursorPage[User], options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, @@ -211,7 +193,7 @@ def list( user_list_params.UserListParams, ), ), - model=UserListResponse, + model=User, ) def check_access( @@ -227,8 +209,8 @@ def check_access( timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> UserCheckAccessResponse: """ - Check whether a user has access to a specific resource, and return their access - level. + Checks whether a user has access to a company, product, or experience the caller + can reach. Args: extra_headers: Send extra headers @@ -251,10 +233,52 @@ def check_access( cast_to=UserCheckAccessResponse, ) + def update_me( + self, + *, + bio: str | Omit = omit, + name: str | Omit = omit, + profile_picture: user_update_me_params.ProfilePicture | Omit = omit, + username: 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, + ) -> User: + """Updates the authenticated user's global profile. -class AsyncUsersResource(AsyncAPIResource): - """Users""" + Not available to API keys. + + 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 + """ + return self._patch( + "/users/me", + body=maybe_transform( + { + "bio": bio, + "name": name, + "profile_picture": profile_picture, + "username": username, + }, + user_update_me_params.UserUpdateMeParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=User, + ) + +class AsyncUsersResource(AsyncAPIResource): @cached_property def with_raw_response(self) -> AsyncUsersResourceWithRawResponse: """ @@ -278,7 +302,7 @@ async def retrieve( self, id: str, *, - company_id: Optional[str] | Omit = omit, + account_id: 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, @@ -287,11 +311,11 @@ async def retrieve( timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> User: """ - Retrieves the details of an existing user. + Retrieves a user's public profile by user\\__ tag, username, or 'me'. Args: - company_id: When provided, returns the user's company-specific profile overrides (name, - profile picture) instead of their global profile. + account_id: When set, returns the user's account-specific profile overrides for this + account. extra_headers: Send extra headers @@ -310,7 +334,7 @@ async def retrieve( extra_query=extra_query, extra_body=extra_body, timeout=timeout, - query=await async_maybe_transform({"company_id": company_id}, user_retrieve_params.UserRetrieveParams), + query=await async_maybe_transform({"account_id": account_id}, user_retrieve_params.UserRetrieveParams), ), cast_to=User, ) @@ -319,11 +343,9 @@ async def update( self, id: str, *, - bio: Optional[str] | Omit = omit, - company_id: Optional[str] | Omit = omit, - name: Optional[str] | Omit = omit, - profile_picture: Optional[user_update_params.ProfilePicture] | Omit = omit, - username: Optional[str] | Omit = omit, + account_id: str | Omit = omit, + bio: str | Omit = omit, + name: 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, @@ -331,26 +353,13 @@ async def update( extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> User: - """ - Update a user's profile by their ID. + """Updates a user. - Required permissions: - - - `user:profile:update` + A user token updates their own global profile; an API key + updates the user's account-specific profile override (account_id required). Args: - bio: A short biography displayed on the user's public profile. - - company_id: When provided, updates the user's profile overrides for this company instead of - the global profile. Pass name and profile_picture to set overrides, or null to - clear them. - - name: The user's display name shown on their public profile. Maximum 100 characters. - - profile_picture: The user's profile picture image attachment. - - username: The user's unique username. Alphanumeric characters and hyphens only. Maximum 42 - characters. + account_id: The account whose profile override to update. Required for API key callers. extra_headers: Send extra headers @@ -367,15 +376,16 @@ async def update( body=await async_maybe_transform( { "bio": bio, - "company_id": company_id, "name": name, - "profile_picture": profile_picture, - "username": username, }, user_update_params.UserUpdateParams, ), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=await async_maybe_transform({"account_id": account_id}, user_update_params.UserUpdateParams), ), cast_to=User, ) @@ -383,32 +393,33 @@ async def update( def list( self, *, - after: Optional[str] | Omit = omit, - before: Optional[str] | Omit = omit, - first: Optional[int] | Omit = omit, - last: Optional[int] | Omit = omit, - query: Optional[str] | Omit = omit, + after: str | Omit = omit, + before: str | Omit = omit, + first: int | Omit = omit, + last: int | Omit = omit, + query: 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, - ) -> AsyncPaginator[UserListResponse, AsyncCursorPage[UserListResponse]]: + ) -> AsyncPaginator[User, AsyncCursorPage[User]]: """ Search for users by name or username, ranked by social proximity to the - authenticated user. + authenticated user. Returns the user's most recently followed users when no + query is given. Args: - after: Returns the elements in the list that come after the specified cursor. + after: A cursor; returns users after this position. - before: Returns the elements in the list that come before the specified cursor. + before: A cursor; returns users before this position. - first: Returns the first _n_ elements from the list. + first: The number of users to return (max 50). - last: Returns the last _n_ elements from the list. + last: The number of users to return from the end of the range. - query: Search term to filter by name or username. + query: A search term to filter users by name or username. extra_headers: Send extra headers @@ -420,7 +431,7 @@ def list( """ return self._get_api_list( "/users", - page=AsyncCursorPage[UserListResponse], + page=AsyncCursorPage[User], options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, @@ -437,7 +448,7 @@ def list( user_list_params.UserListParams, ), ), - model=UserListResponse, + model=User, ) async def check_access( @@ -453,8 +464,8 @@ async def check_access( timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> UserCheckAccessResponse: """ - Check whether a user has access to a specific resource, and return their access - level. + Checks whether a user has access to a company, product, or experience the caller + can reach. Args: extra_headers: Send extra headers @@ -477,6 +488,50 @@ async def check_access( cast_to=UserCheckAccessResponse, ) + async def update_me( + self, + *, + bio: str | Omit = omit, + name: str | Omit = omit, + profile_picture: user_update_me_params.ProfilePicture | Omit = omit, + username: 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, + ) -> User: + """Updates the authenticated user's global profile. + + Not available to API keys. + + 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 + """ + return await self._patch( + "/users/me", + body=await async_maybe_transform( + { + "bio": bio, + "name": name, + "profile_picture": profile_picture, + "username": username, + }, + user_update_me_params.UserUpdateMeParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=User, + ) + class UsersResourceWithRawResponse: def __init__(self, users: UsersResource) -> None: @@ -494,6 +549,9 @@ def __init__(self, users: UsersResource) -> None: self.check_access = to_raw_response_wrapper( users.check_access, ) + self.update_me = to_raw_response_wrapper( + users.update_me, + ) class AsyncUsersResourceWithRawResponse: @@ -512,6 +570,9 @@ def __init__(self, users: AsyncUsersResource) -> None: self.check_access = async_to_raw_response_wrapper( users.check_access, ) + self.update_me = async_to_raw_response_wrapper( + users.update_me, + ) class UsersResourceWithStreamingResponse: @@ -530,6 +591,9 @@ def __init__(self, users: UsersResource) -> None: self.check_access = to_streamed_response_wrapper( users.check_access, ) + self.update_me = to_streamed_response_wrapper( + users.update_me, + ) class AsyncUsersResourceWithStreamingResponse: @@ -548,3 +612,6 @@ def __init__(self, users: AsyncUsersResource) -> None: self.check_access = async_to_streamed_response_wrapper( users.check_access, ) + self.update_me = async_to_streamed_response_wrapper( + users.update_me, + ) diff --git a/src/whop_sdk/types/__init__.py b/src/whop_sdk/types/__init__.py index 23a936a3..9e70ccd3 100644 --- a/src/whop_sdk/types/__init__.py +++ b/src/whop_sdk/types/__init__.py @@ -138,7 +138,6 @@ from .refund_list_params import RefundListParams as RefundListParams from .review_list_params import ReviewListParams as ReviewListParams from .swap_create_params import SwapCreateParams as SwapCreateParams -from .user_list_response import UserListResponse as UserListResponse from .user_update_params import UserUpdateParams as UserUpdateParams from .wallet_send_params import WalletSendParams as WalletSendParams from .account_list_params import AccountListParams as AccountListParams @@ -212,6 +211,7 @@ from .product_update_params import ProductUpdateParams as ProductUpdateParams from .refund_reference_type import RefundReferenceType as RefundReferenceType from .topup_create_response import TopupCreateResponse as TopupCreateResponse +from .user_update_me_params import UserUpdateMeParams as UserUpdateMeParams from .wallet_balance_params import WalletBalanceParams as WalletBalanceParams from .webhook_create_params import WebhookCreateParams as WebhookCreateParams from .webhook_list_response import WebhookListResponse as WebhookListResponse diff --git a/src/whop_sdk/types/conversion_create_params.py b/src/whop_sdk/types/conversion_create_params.py index eb09b960..21b04154 100644 --- a/src/whop_sdk/types/conversion_create_params.py +++ b/src/whop_sdk/types/conversion_create_params.py @@ -53,7 +53,7 @@ class ConversionCreateParams(TypedDict, total=False): """The available currencies on the platform""" custom_name: Optional[str] - """Custom event name when event_name is 'custom'. Maximum 35 chars for this value.""" + """Custom event name when event_name is 'custom'.""" duration: Optional[int] """For 'leave' events: milliseconds the visitor spent on the page.""" diff --git a/src/whop_sdk/types/user.py b/src/whop_sdk/types/user.py index 4e52982f..9e5491e4 100644 --- a/src/whop_sdk/types/user.py +++ b/src/whop_sdk/types/user.py @@ -1,49 +1,27 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from typing import Optional -from datetime import datetime from .._models import BaseModel -__all__ = ["User", "ProfilePicture"] - - -class ProfilePicture(BaseModel): - """The user's profile picture attachment with URL, content type, and file metadata. - - Null if using a legacy profile picture. - """ - - url: Optional[str] = None - """A pre-optimized URL for rendering this attachment on the client. - - This should be used for displaying attachments in apps. - """ +__all__ = ["User"] class User(BaseModel): - """A user account on Whop. - - Contains profile information, identity details, and social connections. - """ - id: str - """The unique identifier for the user.""" + """The ID of the user, which will look like user\\__******\\********""" bio: Optional[str] = None - """A short biography written by the user, displayed on their public profile.""" + """The user's biography""" - created_at: datetime - """The datetime the user was created.""" + created_at: str + """When the user was created, as an ISO 8601 timestamp""" name: Optional[str] = None - """The user's display name shown on their public profile.""" - - profile_picture: Optional[ProfilePicture] = None - """The user's profile picture attachment with URL, content type, and file metadata. + """The user's display name""" - Null if using a legacy profile picture. - """ + profile_picture: Optional[object] = None + """The user's profile picture, an object with a url""" username: str - """The user's unique username shown on their public profile.""" + """The user's unique username""" diff --git a/src/whop_sdk/types/user_check_access_response.py b/src/whop_sdk/types/user_check_access_response.py index 270d822a..ad8fb2fe 100644 --- a/src/whop_sdk/types/user_check_access_response.py +++ b/src/whop_sdk/types/user_check_access_response.py @@ -1,16 +1,13 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. +from typing_extensions import Literal + from .._models import BaseModel -from .shared.access_level import AccessLevel __all__ = ["UserCheckAccessResponse"] class UserCheckAccessResponse(BaseModel): - """The result of a has access check for the developer API""" - - access_level: AccessLevel - """The permission level of the user""" + access_level: Literal["no_access", "admin", "customer"] has_access: bool - """Whether the user has access to the resource""" diff --git a/src/whop_sdk/types/user_list_params.py b/src/whop_sdk/types/user_list_params.py index bce47d6c..14d3e4fa 100644 --- a/src/whop_sdk/types/user_list_params.py +++ b/src/whop_sdk/types/user_list_params.py @@ -2,24 +2,23 @@ from __future__ import annotations -from typing import Optional from typing_extensions import TypedDict __all__ = ["UserListParams"] class UserListParams(TypedDict, total=False): - after: Optional[str] - """Returns the elements in the list that come after the specified cursor.""" + after: str + """A cursor; returns users after this position.""" - before: Optional[str] - """Returns the elements in the list that come before the specified cursor.""" + before: str + """A cursor; returns users before this position.""" - first: Optional[int] - """Returns the first _n_ elements from the list.""" + first: int + """The number of users to return (max 50).""" - last: Optional[int] - """Returns the last _n_ elements from the list.""" + last: int + """The number of users to return from the end of the range.""" - query: Optional[str] - """Search term to filter by name or username.""" + query: str + """A search term to filter users by name or username.""" diff --git a/src/whop_sdk/types/user_list_response.py b/src/whop_sdk/types/user_list_response.py deleted file mode 100644 index 1621e20c..00000000 --- a/src/whop_sdk/types/user_list_response.py +++ /dev/null @@ -1,49 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from typing import Optional -from datetime import datetime - -from .._models import BaseModel - -__all__ = ["UserListResponse", "ProfilePicture"] - - -class ProfilePicture(BaseModel): - """The user's profile picture attachment with URL, content type, and file metadata. - - Null if using a legacy profile picture. - """ - - url: Optional[str] = None - """A pre-optimized URL for rendering this attachment on the client. - - This should be used for displaying attachments in apps. - """ - - -class UserListResponse(BaseModel): - """A user account on Whop. - - Contains profile information, identity details, and social connections. - """ - - id: str - """The unique identifier for the user.""" - - bio: Optional[str] = None - """A short biography written by the user, displayed on their public profile.""" - - created_at: datetime - """The datetime the user was created.""" - - name: Optional[str] = None - """The user's display name shown on their public profile.""" - - profile_picture: Optional[ProfilePicture] = None - """The user's profile picture attachment with URL, content type, and file metadata. - - Null if using a legacy profile picture. - """ - - username: str - """The user's unique username shown on their public profile.""" diff --git a/src/whop_sdk/types/user_retrieve_params.py b/src/whop_sdk/types/user_retrieve_params.py index 8534761b..afb9ca68 100644 --- a/src/whop_sdk/types/user_retrieve_params.py +++ b/src/whop_sdk/types/user_retrieve_params.py @@ -2,15 +2,14 @@ from __future__ import annotations -from typing import Optional from typing_extensions import TypedDict __all__ = ["UserRetrieveParams"] class UserRetrieveParams(TypedDict, total=False): - company_id: Optional[str] + account_id: str """ - When provided, returns the user's company-specific profile overrides (name, - profile picture) instead of their global profile. + When set, returns the user's account-specific profile overrides for this + account. """ diff --git a/src/whop_sdk/types/user_update_me_params.py b/src/whop_sdk/types/user_update_me_params.py new file mode 100644 index 00000000..cbd38b21 --- /dev/null +++ b/src/whop_sdk/types/user_update_me_params.py @@ -0,0 +1,23 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import TypedDict + +__all__ = ["UserUpdateMeParams", "ProfilePicture"] + + +class UserUpdateMeParams(TypedDict, total=False): + bio: str + + name: str + + profile_picture: ProfilePicture + + username: str + + +class ProfilePicture(TypedDict, total=False): + id: str + + direct_upload_id: str diff --git a/src/whop_sdk/types/user_update_params.py b/src/whop_sdk/types/user_update_params.py index a1c8572a..6ac5f721 100644 --- a/src/whop_sdk/types/user_update_params.py +++ b/src/whop_sdk/types/user_update_params.py @@ -2,38 +2,15 @@ from __future__ import annotations -from typing import Optional -from typing_extensions import Required, TypedDict +from typing_extensions import TypedDict -__all__ = ["UserUpdateParams", "ProfilePicture"] +__all__ = ["UserUpdateParams"] class UserUpdateParams(TypedDict, total=False): - bio: Optional[str] - """A short biography displayed on the user's public profile.""" + account_id: str + """The account whose profile override to update. Required for API key callers.""" - company_id: Optional[str] - """ - When provided, updates the user's profile overrides for this company instead of - the global profile. Pass name and profile_picture to set overrides, or null to - clear them. - """ + bio: str - name: Optional[str] - """The user's display name shown on their public profile. Maximum 100 characters.""" - - profile_picture: Optional[ProfilePicture] - """The user's profile picture image attachment.""" - - username: Optional[str] - """The user's unique username. - - Alphanumeric characters and hyphens only. Maximum 42 characters. - """ - - -class ProfilePicture(TypedDict, total=False): - """The user's profile picture image attachment.""" - - id: Required[str] - """The ID of an existing file object.""" + name: str diff --git a/tests/api_resources/test_users.py b/tests/api_resources/test_users.py index ee565217..df9b5aa7 100644 --- a/tests/api_resources/test_users.py +++ b/tests/api_resources/test_users.py @@ -11,7 +11,6 @@ from tests.utils import assert_matches_type from whop_sdk.types import ( User, - UserListResponse, UserCheckAccessResponse, ) from whop_sdk.pagination import SyncCursorPage, AsyncCursorPage @@ -26,7 +25,7 @@ class TestUsers: @parametrize def test_method_retrieve(self, client: Whop) -> None: user = client.users.retrieve( - id="user_xxxxxxxxxxxxx", + id="id", ) assert_matches_type(User, user, path=["response"]) @@ -34,8 +33,8 @@ def test_method_retrieve(self, client: Whop) -> None: @parametrize def test_method_retrieve_with_all_params(self, client: Whop) -> None: user = client.users.retrieve( - id="user_xxxxxxxxxxxxx", - company_id="biz_xxxxxxxxxxxxxx", + id="id", + account_id="account_id", ) assert_matches_type(User, user, path=["response"]) @@ -43,7 +42,7 @@ def test_method_retrieve_with_all_params(self, client: Whop) -> None: @parametrize def test_raw_response_retrieve(self, client: Whop) -> None: response = client.users.with_raw_response.retrieve( - id="user_xxxxxxxxxxxxx", + id="id", ) assert response.is_closed is True @@ -55,7 +54,7 @@ def test_raw_response_retrieve(self, client: Whop) -> None: @parametrize def test_streaming_response_retrieve(self, client: Whop) -> None: with client.users.with_streaming_response.retrieve( - id="user_xxxxxxxxxxxxx", + id="id", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -77,7 +76,7 @@ def test_path_params_retrieve(self, client: Whop) -> None: @parametrize def test_method_update(self, client: Whop) -> None: user = client.users.update( - id="user_xxxxxxxxxxxxx", + id="id", ) assert_matches_type(User, user, path=["response"]) @@ -85,12 +84,10 @@ def test_method_update(self, client: Whop) -> None: @parametrize def test_method_update_with_all_params(self, client: Whop) -> None: user = client.users.update( - id="user_xxxxxxxxxxxxx", + id="id", + account_id="account_id", bio="bio", - company_id="biz_xxxxxxxxxxxxxx", name="name", - profile_picture={"id": "id"}, - username="username", ) assert_matches_type(User, user, path=["response"]) @@ -98,7 +95,7 @@ def test_method_update_with_all_params(self, client: Whop) -> None: @parametrize def test_raw_response_update(self, client: Whop) -> None: response = client.users.with_raw_response.update( - id="user_xxxxxxxxxxxxx", + id="id", ) assert response.is_closed is True @@ -110,7 +107,7 @@ def test_raw_response_update(self, client: Whop) -> None: @parametrize def test_streaming_response_update(self, client: Whop) -> None: with client.users.with_streaming_response.update( - id="user_xxxxxxxxxxxxx", + id="id", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -132,7 +129,7 @@ def test_path_params_update(self, client: Whop) -> None: @parametrize def test_method_list(self, client: Whop) -> None: user = client.users.list() - assert_matches_type(SyncCursorPage[UserListResponse], user, path=["response"]) + assert_matches_type(SyncCursorPage[User], user, path=["response"]) @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize @@ -140,11 +137,11 @@ def test_method_list_with_all_params(self, client: Whop) -> None: user = client.users.list( after="after", before="before", - first=42, - last=42, + first=0, + last=0, query="query", ) - assert_matches_type(SyncCursorPage[UserListResponse], user, path=["response"]) + assert_matches_type(SyncCursorPage[User], user, path=["response"]) @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize @@ -154,7 +151,7 @@ def test_raw_response_list(self, client: Whop) -> None: assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" user = response.parse() - assert_matches_type(SyncCursorPage[UserListResponse], user, path=["response"]) + assert_matches_type(SyncCursorPage[User], user, path=["response"]) @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize @@ -164,7 +161,7 @@ def test_streaming_response_list(self, client: Whop) -> None: assert response.http_request.headers.get("X-Stainless-Lang") == "python" user = response.parse() - assert_matches_type(SyncCursorPage[UserListResponse], user, path=["response"]) + assert_matches_type(SyncCursorPage[User], user, path=["response"]) assert cast(Any, response.is_closed) is True @@ -173,7 +170,7 @@ def test_streaming_response_list(self, client: Whop) -> None: def test_method_check_access(self, client: Whop) -> None: user = client.users.check_access( resource_id="resource_id", - id="user_xxxxxxxxxxxxx", + id="id", ) assert_matches_type(UserCheckAccessResponse, user, path=["response"]) @@ -182,7 +179,7 @@ def test_method_check_access(self, client: Whop) -> None: def test_raw_response_check_access(self, client: Whop) -> None: response = client.users.with_raw_response.check_access( resource_id="resource_id", - id="user_xxxxxxxxxxxxx", + id="id", ) assert response.is_closed is True @@ -195,7 +192,7 @@ def test_raw_response_check_access(self, client: Whop) -> None: def test_streaming_response_check_access(self, client: Whop) -> None: with client.users.with_streaming_response.check_access( resource_id="resource_id", - id="user_xxxxxxxxxxxxx", + id="id", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -217,9 +214,51 @@ def test_path_params_check_access(self, client: Whop) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `resource_id` but received ''"): client.users.with_raw_response.check_access( resource_id="", - id="user_xxxxxxxxxxxxx", + id="id", ) + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_update_me(self, client: Whop) -> None: + user = client.users.update_me() + assert_matches_type(User, user, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_update_me_with_all_params(self, client: Whop) -> None: + user = client.users.update_me( + bio="bio", + name="name", + profile_picture={ + "id": "id", + "direct_upload_id": "direct_upload_id", + }, + username="username", + ) + assert_matches_type(User, user, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_raw_response_update_me(self, client: Whop) -> None: + response = client.users.with_raw_response.update_me() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + user = response.parse() + assert_matches_type(User, user, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_streaming_response_update_me(self, client: Whop) -> None: + with client.users.with_streaming_response.update_me() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + user = response.parse() + assert_matches_type(User, user, path=["response"]) + + assert cast(Any, response.is_closed) is True + class TestAsyncUsers: parametrize = pytest.mark.parametrize( @@ -230,7 +269,7 @@ class TestAsyncUsers: @parametrize async def test_method_retrieve(self, async_client: AsyncWhop) -> None: user = await async_client.users.retrieve( - id="user_xxxxxxxxxxxxx", + id="id", ) assert_matches_type(User, user, path=["response"]) @@ -238,8 +277,8 @@ async def test_method_retrieve(self, async_client: AsyncWhop) -> None: @parametrize async def test_method_retrieve_with_all_params(self, async_client: AsyncWhop) -> None: user = await async_client.users.retrieve( - id="user_xxxxxxxxxxxxx", - company_id="biz_xxxxxxxxxxxxxx", + id="id", + account_id="account_id", ) assert_matches_type(User, user, path=["response"]) @@ -247,7 +286,7 @@ async def test_method_retrieve_with_all_params(self, async_client: AsyncWhop) -> @parametrize async def test_raw_response_retrieve(self, async_client: AsyncWhop) -> None: response = await async_client.users.with_raw_response.retrieve( - id="user_xxxxxxxxxxxxx", + id="id", ) assert response.is_closed is True @@ -259,7 +298,7 @@ async def test_raw_response_retrieve(self, async_client: AsyncWhop) -> None: @parametrize async def test_streaming_response_retrieve(self, async_client: AsyncWhop) -> None: async with async_client.users.with_streaming_response.retrieve( - id="user_xxxxxxxxxxxxx", + id="id", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -281,7 +320,7 @@ async def test_path_params_retrieve(self, async_client: AsyncWhop) -> None: @parametrize async def test_method_update(self, async_client: AsyncWhop) -> None: user = await async_client.users.update( - id="user_xxxxxxxxxxxxx", + id="id", ) assert_matches_type(User, user, path=["response"]) @@ -289,12 +328,10 @@ async def test_method_update(self, async_client: AsyncWhop) -> None: @parametrize async def test_method_update_with_all_params(self, async_client: AsyncWhop) -> None: user = await async_client.users.update( - id="user_xxxxxxxxxxxxx", + id="id", + account_id="account_id", bio="bio", - company_id="biz_xxxxxxxxxxxxxx", name="name", - profile_picture={"id": "id"}, - username="username", ) assert_matches_type(User, user, path=["response"]) @@ -302,7 +339,7 @@ async def test_method_update_with_all_params(self, async_client: AsyncWhop) -> N @parametrize async def test_raw_response_update(self, async_client: AsyncWhop) -> None: response = await async_client.users.with_raw_response.update( - id="user_xxxxxxxxxxxxx", + id="id", ) assert response.is_closed is True @@ -314,7 +351,7 @@ async def test_raw_response_update(self, async_client: AsyncWhop) -> None: @parametrize async def test_streaming_response_update(self, async_client: AsyncWhop) -> None: async with async_client.users.with_streaming_response.update( - id="user_xxxxxxxxxxxxx", + id="id", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -336,7 +373,7 @@ async def test_path_params_update(self, async_client: AsyncWhop) -> None: @parametrize async def test_method_list(self, async_client: AsyncWhop) -> None: user = await async_client.users.list() - assert_matches_type(AsyncCursorPage[UserListResponse], user, path=["response"]) + assert_matches_type(AsyncCursorPage[User], user, path=["response"]) @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize @@ -344,11 +381,11 @@ async def test_method_list_with_all_params(self, async_client: AsyncWhop) -> Non user = await async_client.users.list( after="after", before="before", - first=42, - last=42, + first=0, + last=0, query="query", ) - assert_matches_type(AsyncCursorPage[UserListResponse], user, path=["response"]) + assert_matches_type(AsyncCursorPage[User], user, path=["response"]) @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize @@ -358,7 +395,7 @@ async def test_raw_response_list(self, async_client: AsyncWhop) -> None: assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" user = await response.parse() - assert_matches_type(AsyncCursorPage[UserListResponse], user, path=["response"]) + assert_matches_type(AsyncCursorPage[User], user, path=["response"]) @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize @@ -368,7 +405,7 @@ async def test_streaming_response_list(self, async_client: AsyncWhop) -> None: assert response.http_request.headers.get("X-Stainless-Lang") == "python" user = await response.parse() - assert_matches_type(AsyncCursorPage[UserListResponse], user, path=["response"]) + assert_matches_type(AsyncCursorPage[User], user, path=["response"]) assert cast(Any, response.is_closed) is True @@ -377,7 +414,7 @@ async def test_streaming_response_list(self, async_client: AsyncWhop) -> None: async def test_method_check_access(self, async_client: AsyncWhop) -> None: user = await async_client.users.check_access( resource_id="resource_id", - id="user_xxxxxxxxxxxxx", + id="id", ) assert_matches_type(UserCheckAccessResponse, user, path=["response"]) @@ -386,7 +423,7 @@ async def test_method_check_access(self, async_client: AsyncWhop) -> None: async def test_raw_response_check_access(self, async_client: AsyncWhop) -> None: response = await async_client.users.with_raw_response.check_access( resource_id="resource_id", - id="user_xxxxxxxxxxxxx", + id="id", ) assert response.is_closed is True @@ -399,7 +436,7 @@ async def test_raw_response_check_access(self, async_client: AsyncWhop) -> None: async def test_streaming_response_check_access(self, async_client: AsyncWhop) -> None: async with async_client.users.with_streaming_response.check_access( resource_id="resource_id", - id="user_xxxxxxxxxxxxx", + id="id", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -421,5 +458,47 @@ async def test_path_params_check_access(self, async_client: AsyncWhop) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `resource_id` but received ''"): await async_client.users.with_raw_response.check_access( resource_id="", - id="user_xxxxxxxxxxxxx", + id="id", ) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_update_me(self, async_client: AsyncWhop) -> None: + user = await async_client.users.update_me() + assert_matches_type(User, user, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_update_me_with_all_params(self, async_client: AsyncWhop) -> None: + user = await async_client.users.update_me( + bio="bio", + name="name", + profile_picture={ + "id": "id", + "direct_upload_id": "direct_upload_id", + }, + username="username", + ) + assert_matches_type(User, user, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_raw_response_update_me(self, async_client: AsyncWhop) -> None: + response = await async_client.users.with_raw_response.update_me() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + user = await response.parse() + assert_matches_type(User, user, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_streaming_response_update_me(self, async_client: AsyncWhop) -> None: + async with async_client.users.with_streaming_response.update_me() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + user = await response.parse() + assert_matches_type(User, user, path=["response"]) + + assert cast(Any, response.is_closed) is True From d4f4acae1f18d634cf5ec5746f2b13d9affc7ddb Mon Sep 17 00:00:00 2001 From: stlc-bot Date: Tue, 9 Jun 2026 16:41:36 +0000 Subject: [PATCH 010/109] remove stlc Stainless-Generated-From: c5cb20877dda469a72fa8637e67bedc644b0a224 --- .stats.yml | 2 +- README.md | 4 +- api.md | 5 +- src/whop_sdk/_client.py | 26 +- src/whop_sdk/resources/conversions.py | 4 +- src/whop_sdk/resources/users.py | 271 +++++++----------- src/whop_sdk/types/__init__.py | 2 +- .../types/conversion_create_params.py | 2 +- src/whop_sdk/types/user.py | 40 ++- .../types/user_check_access_response.py | 9 +- src/whop_sdk/types/user_list_params.py | 21 +- src/whop_sdk/types/user_list_response.py | 49 ++++ src/whop_sdk/types/user_retrieve_params.py | 7 +- src/whop_sdk/types/user_update_me_params.py | 23 -- src/whop_sdk/types/user_update_params.py | 35 ++- tests/api_resources/test_users.py | 169 +++-------- 16 files changed, 292 insertions(+), 377 deletions(-) create mode 100644 src/whop_sdk/types/user_list_response.py delete mode 100644 src/whop_sdk/types/user_update_me_params.py diff --git a/.stats.yml b/.stats.yml index cd5b9cf9..f1fe8e08 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1 +1 @@ -configured_endpoints: 230 +configured_endpoints: 229 diff --git a/README.md b/README.md index f4049761..f695ea0b 100644 --- a/README.md +++ b/README.md @@ -13,8 +13,8 @@ It is generated with [Stainless](https://www.stainless.com/). Use the Whop MCP Server to enable AI assistants to interact with this API, allowing them to explore endpoints, make test requests, and use documentation to help integrate this SDK into your application. -[![Add to Cursor](https://cursor.com/deeplink/mcp-install-dark.svg)](https://cursor.com/en-US/install-mcp?name=%40whop%2Fmcp&config=eyJjb21tYW5kIjoibnB4IiwiYXJncyI6WyIteSIsIkB3aG9wL21jcCJdLCJlbnYiOnsiV0hPUF9BUElfS0VZIjoiTXkgQVBJIEtleSIsIldIT1BfV0VCSE9PS19TRUNSRVQiOiJNeSBXZWJob29rIEtleSIsIldIT1BfQVBQX0lEIjoiYXBwX3h4eHh4eHh4eHh4eHh4IiwiV0hPUF9BUElfVkVSU0lPTiI6IjIwMjYtMDYtMDgifX0) -[![Install in VS Code](https://img.shields.io/badge/_-Add_to_VS_Code-blue?style=for-the-badge&logo=data:image/svg%2bxml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGZpbGw9Im5vbmUiIHZpZXdCb3g9IjAgMCA0MCA0MCI+PHBhdGggZmlsbD0iI0VFRSIgZmlsbC1ydWxlPSJldmVub2RkIiBkPSJNMzAuMjM1IDM5Ljg4NGEyLjQ5MSAyLjQ5MSAwIDAgMS0xLjc4MS0uNzNMMTIuNyAyNC43OGwtMy40NiAyLjYyNC0zLjQwNiAyLjU4MmExLjY2NSAxLjY2NSAwIDAgMS0xLjA4Mi4zMzggMS42NjQgMS42NjQgMCAwIDEtMS4wNDYtLjQzMWwtMi4yLTJhMS42NjYgMS42NjYgMCAwIDEgMC0yLjQ2M0w3LjQ1OCAyMCA0LjY3IDE3LjQ1MyAxLjUwNyAxNC41N2ExLjY2NSAxLjY2NSAwIDAgMSAwLTIuNDYzbDIuMi0yYTEuNjY1IDEuNjY1IDAgMCAxIDIuMTMtLjA5N2w2Ljg2MyA1LjIwOUwyOC40NTIuODQ0YTIuNDg4IDIuNDg4IDAgMCAxIDEuODQxLS43MjljLjM1MS4wMDkuNjk5LjA5MSAxLjAxOS4yNDVsOC4yMzYgMy45NjFhMi41IDIuNSAwIDAgMSAxLjQxNSAyLjI1M3YuMDk5LS4wNDVWMzMuMzd2LS4wNDUuMDk1YTIuNTAxIDIuNTAxIDAgMCAxLTEuNDE2IDIuMjU3bC04LjIzNSAzLjk2MWEyLjQ5MiAyLjQ5MiAwIDAgMS0xLjA3Ny4yNDZabS43MTYtMjguOTQ3LTExLjk0OCA5LjA2MiAxMS45NTIgOS4wNjUtLjAwNC0xOC4xMjdaIi8+PC9zdmc+)](https://vscode.stainless.com/mcp/%7B%22name%22%3A%22%40whop%2Fmcp%22%2C%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22-y%22%2C%22%40whop%2Fmcp%22%5D%2C%22env%22%3A%7B%22WHOP_API_KEY%22%3A%22My%20API%20Key%22%2C%22WHOP_WEBHOOK_SECRET%22%3A%22My%20Webhook%20Key%22%2C%22WHOP_APP_ID%22%3A%22app_xxxxxxxxxxxxxx%22%2C%22WHOP_API_VERSION%22%3A%222026-06-08%22%7D%7D) +[![Add to Cursor](https://cursor.com/deeplink/mcp-install-dark.svg)](https://cursor.com/en-US/install-mcp?name=%40whop%2Fmcp&config=eyJjb21tYW5kIjoibnB4IiwiYXJncyI6WyIteSIsIkB3aG9wL21jcCJdLCJlbnYiOnsiV0hPUF9BUElfS0VZIjoiTXkgQVBJIEtleSIsIldIT1BfV0VCSE9PS19TRUNSRVQiOiJNeSBXZWJob29rIEtleSIsIldIT1BfQVBQX0lEIjoiYXBwX3h4eHh4eHh4eHh4eHh4In19) +[![Install in VS Code](https://img.shields.io/badge/_-Add_to_VS_Code-blue?style=for-the-badge&logo=data:image/svg%2bxml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGZpbGw9Im5vbmUiIHZpZXdCb3g9IjAgMCA0MCA0MCI+PHBhdGggZmlsbD0iI0VFRSIgZmlsbC1ydWxlPSJldmVub2RkIiBkPSJNMzAuMjM1IDM5Ljg4NGEyLjQ5MSAyLjQ5MSAwIDAgMS0xLjc4MS0uNzNMMTIuNyAyNC43OGwtMy40NiAyLjYyNC0zLjQwNiAyLjU4MmExLjY2NSAxLjY2NSAwIDAgMS0xLjA4Mi4zMzggMS42NjQgMS42NjQgMCAwIDEtMS4wNDYtLjQzMWwtMi4yLTJhMS42NjYgMS42NjYgMCAwIDEgMC0yLjQ2M0w3LjQ1OCAyMCA0LjY3IDE3LjQ1MyAxLjUwNyAxNC41N2ExLjY2NSAxLjY2NSAwIDAgMSAwLTIuNDYzbDIuMi0yYTEuNjY1IDEuNjY1IDAgMCAxIDIuMTMtLjA5N2w2Ljg2MyA1LjIwOUwyOC40NTIuODQ0YTIuNDg4IDIuNDg4IDAgMCAxIDEuODQxLS43MjljLjM1MS4wMDkuNjk5LjA5MSAxLjAxOS4yNDVsOC4yMzYgMy45NjFhMi41IDIuNSAwIDAgMSAxLjQxNSAyLjI1M3YuMDk5LS4wNDVWMzMuMzd2LS4wNDUuMDk1YTIuNTAxIDIuNTAxIDAgMCAxLTEuNDE2IDIuMjU3bC04LjIzNSAzLjk2MWEyLjQ5MiAyLjQ5MiAwIDAgMS0xLjA3Ny4yNDZabS43MTYtMjguOTQ3LTExLjk0OCA5LjA2MiAxMS45NTIgOS4wNjUtLjAwNC0xOC4xMjdaIi8+PC9zdmc+)](https://vscode.stainless.com/mcp/%7B%22name%22%3A%22%40whop%2Fmcp%22%2C%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22-y%22%2C%22%40whop%2Fmcp%22%5D%2C%22env%22%3A%7B%22WHOP_API_KEY%22%3A%22My%20API%20Key%22%2C%22WHOP_WEBHOOK_SECRET%22%3A%22My%20Webhook%20Key%22%2C%22WHOP_APP_ID%22%3A%22app_xxxxxxxxxxxxxx%22%7D%7D) > Note: You may need to set environment variables in your MCP client. diff --git a/api.md b/api.md index 401d5f4f..8f4e6e82 100644 --- a/api.md +++ b/api.md @@ -387,16 +387,15 @@ Methods: Types: ```python -from whop_sdk.types import User, UserCheckAccessResponse +from whop_sdk.types import User, UserListResponse, UserCheckAccessResponse ``` Methods: - client.users.retrieve(id, \*\*params) -> User - client.users.update(id, \*\*params) -> User -- client.users.list(\*\*params) -> SyncCursorPage[User] +- client.users.list(\*\*params) -> SyncCursorPage[UserListResponse] - client.users.check_access(resource_id, \*, id) -> UserCheckAccessResponse -- client.users.update_me(\*\*params) -> User # Payments diff --git a/src/whop_sdk/_client.py b/src/whop_sdk/_client.py index 1259e198..4455e1e1 100644 --- a/src/whop_sdk/_client.py +++ b/src/whop_sdk/_client.py @@ -180,7 +180,6 @@ class Whop(SyncAPIClient): api_key: str webhook_key: str | None app_id: str | None - version: str | None def __init__( self, @@ -188,7 +187,6 @@ def __init__( api_key: str | None = None, webhook_key: str | None = None, app_id: str | None = None, - version: str | None = None, base_url: str | httpx.URL | None = None, timeout: float | Timeout | None | NotGiven = not_given, max_retries: int = DEFAULT_MAX_RETRIES, @@ -214,7 +212,6 @@ def __init__( - `api_key` from `WHOP_API_KEY` - `webhook_key` from `WHOP_WEBHOOK_SECRET` - `app_id` from `WHOP_APP_ID` - - `version` from `WHOP_API_VERSION` """ if api_key is None: api_key = os.environ.get("WHOP_API_KEY") @@ -232,10 +229,6 @@ def __init__( app_id = os.environ.get("WHOP_APP_ID") self.app_id = app_id - if version is None: - version = os.environ.get("WHOP_API_VERSION") or "2026-06-08" - self.version = version - if base_url is None: base_url = os.environ.get("WHOP_BASE_URL") if base_url is None: @@ -389,6 +382,7 @@ def chat_channels(self) -> ChatChannelsResource: @cached_property def users(self) -> UsersResource: + """Users""" from .resources.users import UsersResource return UsersResource(self) @@ -729,7 +723,6 @@ def default_headers(self) -> dict[str, str | Omit]: **super().default_headers, "X-Stainless-Async": "false", "X-Whop-App-Id": self.app_id if self.app_id is not None else Omit(), - "Api-Version-Date": self.version if self.version is not None else Omit(), **self._custom_headers, } @@ -739,7 +732,6 @@ def copy( api_key: str | None = None, webhook_key: str | None = None, app_id: str | None = None, - version: str | None = None, base_url: str | httpx.URL | None = None, timeout: float | Timeout | None | NotGiven = not_given, http_client: httpx.Client | None = None, @@ -776,7 +768,6 @@ def copy( api_key=api_key or self.api_key, webhook_key=webhook_key or self.webhook_key, app_id=app_id or self.app_id, - version=version or self.version, base_url=base_url or self.base_url, timeout=self.timeout if isinstance(timeout, NotGiven) else timeout, http_client=http_client, @@ -829,7 +820,6 @@ class AsyncWhop(AsyncAPIClient): api_key: str webhook_key: str | None app_id: str | None - version: str | None def __init__( self, @@ -837,7 +827,6 @@ def __init__( api_key: str | None = None, webhook_key: str | None = None, app_id: str | None = None, - version: str | None = None, base_url: str | httpx.URL | None = None, timeout: float | Timeout | None | NotGiven = not_given, max_retries: int = DEFAULT_MAX_RETRIES, @@ -863,7 +852,6 @@ def __init__( - `api_key` from `WHOP_API_KEY` - `webhook_key` from `WHOP_WEBHOOK_SECRET` - `app_id` from `WHOP_APP_ID` - - `version` from `WHOP_API_VERSION` """ if api_key is None: api_key = os.environ.get("WHOP_API_KEY") @@ -881,10 +869,6 @@ def __init__( app_id = os.environ.get("WHOP_APP_ID") self.app_id = app_id - if version is None: - version = os.environ.get("WHOP_API_VERSION") or "2026-06-08" - self.version = version - if base_url is None: base_url = os.environ.get("WHOP_BASE_URL") if base_url is None: @@ -1038,6 +1022,7 @@ def chat_channels(self) -> AsyncChatChannelsResource: @cached_property def users(self) -> AsyncUsersResource: + """Users""" from .resources.users import AsyncUsersResource return AsyncUsersResource(self) @@ -1378,7 +1363,6 @@ def default_headers(self) -> dict[str, str | Omit]: **super().default_headers, "X-Stainless-Async": f"async:{get_async_library()}", "X-Whop-App-Id": self.app_id if self.app_id is not None else Omit(), - "Api-Version-Date": self.version if self.version is not None else Omit(), **self._custom_headers, } @@ -1388,7 +1372,6 @@ def copy( api_key: str | None = None, webhook_key: str | None = None, app_id: str | None = None, - version: str | None = None, base_url: str | httpx.URL | None = None, timeout: float | Timeout | None | NotGiven = not_given, http_client: httpx.AsyncClient | None = None, @@ -1425,7 +1408,6 @@ def copy( api_key=api_key or self.api_key, webhook_key=webhook_key or self.webhook_key, app_id=app_id or self.app_id, - version=version or self.version, base_url=base_url or self.base_url, timeout=self.timeout if isinstance(timeout, NotGiven) else timeout, http_client=http_client, @@ -1607,6 +1589,7 @@ def chat_channels(self) -> chat_channels.ChatChannelsResourceWithRawResponse: @cached_property def users(self) -> users.UsersResourceWithRawResponse: + """Users""" from .resources.users import UsersResourceWithRawResponse return UsersResourceWithRawResponse(self._client.users) @@ -2058,6 +2041,7 @@ def chat_channels(self) -> chat_channels.AsyncChatChannelsResourceWithRawRespons @cached_property def users(self) -> users.AsyncUsersResourceWithRawResponse: + """Users""" from .resources.users import AsyncUsersResourceWithRawResponse return AsyncUsersResourceWithRawResponse(self._client.users) @@ -2511,6 +2495,7 @@ def chat_channels(self) -> chat_channels.ChatChannelsResourceWithStreamingRespon @cached_property def users(self) -> users.UsersResourceWithStreamingResponse: + """Users""" from .resources.users import UsersResourceWithStreamingResponse return UsersResourceWithStreamingResponse(self._client.users) @@ -2966,6 +2951,7 @@ def chat_channels(self) -> chat_channels.AsyncChatChannelsResourceWithStreamingR @cached_property def users(self) -> users.AsyncUsersResourceWithStreamingResponse: + """Users""" from .resources.users import AsyncUsersResourceWithStreamingResponse return AsyncUsersResourceWithStreamingResponse(self._client.users) diff --git a/src/whop_sdk/resources/conversions.py b/src/whop_sdk/resources/conversions.py index 4c351f91..8f5cf755 100644 --- a/src/whop_sdk/resources/conversions.py +++ b/src/whop_sdk/resources/conversions.py @@ -117,7 +117,7 @@ def create( currency: The available currencies on the platform - custom_name: Custom event name when event_name is 'custom'. + custom_name: Custom event name when event_name is 'custom'. Maximum 35 chars for this value. duration: For 'leave' events: milliseconds the visitor spent on the page. @@ -275,7 +275,7 @@ async def create( currency: The available currencies on the platform - custom_name: Custom event name when event_name is 'custom'. + custom_name: Custom event name when event_name is 'custom'. Maximum 35 chars for this value. duration: For 'leave' events: milliseconds the visitor spent on the page. diff --git a/src/whop_sdk/resources/users.py b/src/whop_sdk/resources/users.py index 1468d977..e0ce27c0 100644 --- a/src/whop_sdk/resources/users.py +++ b/src/whop_sdk/resources/users.py @@ -2,9 +2,11 @@ from __future__ import annotations +from typing import Optional + import httpx -from ..types import user_list_params, user_update_params, user_retrieve_params, user_update_me_params +from ..types import user_list_params, user_update_params, user_retrieve_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 @@ -18,12 +20,15 @@ from ..pagination import SyncCursorPage, AsyncCursorPage from ..types.user import User from .._base_client import AsyncPaginator, make_request_options +from ..types.user_list_response import UserListResponse from ..types.user_check_access_response import UserCheckAccessResponse __all__ = ["UsersResource", "AsyncUsersResource"] class UsersResource(SyncAPIResource): + """Users""" + @cached_property def with_raw_response(self) -> UsersResourceWithRawResponse: """ @@ -47,7 +52,7 @@ def retrieve( self, id: str, *, - account_id: str | Omit = omit, + company_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, @@ -56,11 +61,11 @@ def retrieve( timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> User: """ - Retrieves a user's public profile by user\\__ tag, username, or 'me'. + Retrieves the details of an existing user. Args: - account_id: When set, returns the user's account-specific profile overrides for this - account. + company_id: When provided, returns the user's company-specific profile overrides (name, + profile picture) instead of their global profile. extra_headers: Send extra headers @@ -79,7 +84,7 @@ def retrieve( extra_query=extra_query, extra_body=extra_body, timeout=timeout, - query=maybe_transform({"account_id": account_id}, user_retrieve_params.UserRetrieveParams), + query=maybe_transform({"company_id": company_id}, user_retrieve_params.UserRetrieveParams), ), cast_to=User, ) @@ -88,9 +93,11 @@ def update( self, id: str, *, - account_id: str | Omit = omit, - bio: str | Omit = omit, - name: str | Omit = omit, + bio: Optional[str] | Omit = omit, + company_id: Optional[str] | Omit = omit, + name: Optional[str] | Omit = omit, + profile_picture: Optional[user_update_params.ProfilePicture] | Omit = omit, + username: 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, @@ -98,13 +105,26 @@ def update( extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> User: - """Updates a user. + """ + Update a user's profile by their ID. + + Required permissions: - A user token updates their own global profile; an API key - updates the user's account-specific profile override (account_id required). + - `user:profile:update` Args: - account_id: The account whose profile override to update. Required for API key callers. + bio: A short biography displayed on the user's public profile. + + company_id: When provided, updates the user's profile overrides for this company instead of + the global profile. Pass name and profile_picture to set overrides, or null to + clear them. + + name: The user's display name shown on their public profile. Maximum 100 characters. + + profile_picture: The user's profile picture image attachment. + + username: The user's unique username. Alphanumeric characters and hyphens only. Maximum 42 + characters. extra_headers: Send extra headers @@ -121,16 +141,15 @@ def update( body=maybe_transform( { "bio": bio, + "company_id": company_id, "name": name, + "profile_picture": profile_picture, + "username": username, }, user_update_params.UserUpdateParams, ), options=make_request_options( - extra_headers=extra_headers, - extra_query=extra_query, - extra_body=extra_body, - timeout=timeout, - query=maybe_transform({"account_id": account_id}, user_update_params.UserUpdateParams), + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), cast_to=User, ) @@ -138,33 +157,32 @@ def update( def list( self, *, - after: str | Omit = omit, - before: str | Omit = omit, - first: int | Omit = omit, - last: int | Omit = omit, - query: str | Omit = omit, + after: Optional[str] | Omit = omit, + before: Optional[str] | Omit = omit, + first: Optional[int] | Omit = omit, + last: Optional[int] | Omit = omit, + query: 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, - ) -> SyncCursorPage[User]: + ) -> SyncCursorPage[UserListResponse]: """ Search for users by name or username, ranked by social proximity to the - authenticated user. Returns the user's most recently followed users when no - query is given. + authenticated user. Args: - after: A cursor; returns users after this position. + after: Returns the elements in the list that come after the specified cursor. - before: A cursor; returns users before this position. + before: Returns the elements in the list that come before the specified cursor. - first: The number of users to return (max 50). + first: Returns the first _n_ elements from the list. - last: The number of users to return from the end of the range. + last: Returns the last _n_ elements from the list. - query: A search term to filter users by name or username. + query: Search term to filter by name or username. extra_headers: Send extra headers @@ -176,7 +194,7 @@ def list( """ return self._get_api_list( "/users", - page=SyncCursorPage[User], + page=SyncCursorPage[UserListResponse], options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, @@ -193,7 +211,7 @@ def list( user_list_params.UserListParams, ), ), - model=User, + model=UserListResponse, ) def check_access( @@ -209,8 +227,8 @@ def check_access( timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> UserCheckAccessResponse: """ - Checks whether a user has access to a company, product, or experience the caller - can reach. + Check whether a user has access to a specific resource, and return their access + level. Args: extra_headers: Send extra headers @@ -233,52 +251,10 @@ def check_access( cast_to=UserCheckAccessResponse, ) - def update_me( - self, - *, - bio: str | Omit = omit, - name: str | Omit = omit, - profile_picture: user_update_me_params.ProfilePicture | Omit = omit, - username: 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, - ) -> User: - """Updates the authenticated user's global profile. - - Not available to API keys. - - 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 - """ - return self._patch( - "/users/me", - body=maybe_transform( - { - "bio": bio, - "name": name, - "profile_picture": profile_picture, - "username": username, - }, - user_update_me_params.UserUpdateMeParams, - ), - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout - ), - cast_to=User, - ) - class AsyncUsersResource(AsyncAPIResource): + """Users""" + @cached_property def with_raw_response(self) -> AsyncUsersResourceWithRawResponse: """ @@ -302,7 +278,7 @@ async def retrieve( self, id: str, *, - account_id: str | Omit = omit, + company_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, @@ -311,11 +287,11 @@ async def retrieve( timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> User: """ - Retrieves a user's public profile by user\\__ tag, username, or 'me'. + Retrieves the details of an existing user. Args: - account_id: When set, returns the user's account-specific profile overrides for this - account. + company_id: When provided, returns the user's company-specific profile overrides (name, + profile picture) instead of their global profile. extra_headers: Send extra headers @@ -334,7 +310,7 @@ async def retrieve( extra_query=extra_query, extra_body=extra_body, timeout=timeout, - query=await async_maybe_transform({"account_id": account_id}, user_retrieve_params.UserRetrieveParams), + query=await async_maybe_transform({"company_id": company_id}, user_retrieve_params.UserRetrieveParams), ), cast_to=User, ) @@ -343,9 +319,11 @@ async def update( self, id: str, *, - account_id: str | Omit = omit, - bio: str | Omit = omit, - name: str | Omit = omit, + bio: Optional[str] | Omit = omit, + company_id: Optional[str] | Omit = omit, + name: Optional[str] | Omit = omit, + profile_picture: Optional[user_update_params.ProfilePicture] | Omit = omit, + username: 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, @@ -353,13 +331,26 @@ async def update( extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> User: - """Updates a user. + """ + Update a user's profile by their ID. - A user token updates their own global profile; an API key - updates the user's account-specific profile override (account_id required). + Required permissions: + + - `user:profile:update` Args: - account_id: The account whose profile override to update. Required for API key callers. + bio: A short biography displayed on the user's public profile. + + company_id: When provided, updates the user's profile overrides for this company instead of + the global profile. Pass name and profile_picture to set overrides, or null to + clear them. + + name: The user's display name shown on their public profile. Maximum 100 characters. + + profile_picture: The user's profile picture image attachment. + + username: The user's unique username. Alphanumeric characters and hyphens only. Maximum 42 + characters. extra_headers: Send extra headers @@ -376,16 +367,15 @@ async def update( body=await async_maybe_transform( { "bio": bio, + "company_id": company_id, "name": name, + "profile_picture": profile_picture, + "username": username, }, user_update_params.UserUpdateParams, ), options=make_request_options( - extra_headers=extra_headers, - extra_query=extra_query, - extra_body=extra_body, - timeout=timeout, - query=await async_maybe_transform({"account_id": account_id}, user_update_params.UserUpdateParams), + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), cast_to=User, ) @@ -393,33 +383,32 @@ async def update( def list( self, *, - after: str | Omit = omit, - before: str | Omit = omit, - first: int | Omit = omit, - last: int | Omit = omit, - query: str | Omit = omit, + after: Optional[str] | Omit = omit, + before: Optional[str] | Omit = omit, + first: Optional[int] | Omit = omit, + last: Optional[int] | Omit = omit, + query: 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, - ) -> AsyncPaginator[User, AsyncCursorPage[User]]: + ) -> AsyncPaginator[UserListResponse, AsyncCursorPage[UserListResponse]]: """ Search for users by name or username, ranked by social proximity to the - authenticated user. Returns the user's most recently followed users when no - query is given. + authenticated user. Args: - after: A cursor; returns users after this position. + after: Returns the elements in the list that come after the specified cursor. - before: A cursor; returns users before this position. + before: Returns the elements in the list that come before the specified cursor. - first: The number of users to return (max 50). + first: Returns the first _n_ elements from the list. - last: The number of users to return from the end of the range. + last: Returns the last _n_ elements from the list. - query: A search term to filter users by name or username. + query: Search term to filter by name or username. extra_headers: Send extra headers @@ -431,7 +420,7 @@ def list( """ return self._get_api_list( "/users", - page=AsyncCursorPage[User], + page=AsyncCursorPage[UserListResponse], options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, @@ -448,7 +437,7 @@ def list( user_list_params.UserListParams, ), ), - model=User, + model=UserListResponse, ) async def check_access( @@ -464,8 +453,8 @@ async def check_access( timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> UserCheckAccessResponse: """ - Checks whether a user has access to a company, product, or experience the caller - can reach. + Check whether a user has access to a specific resource, and return their access + level. Args: extra_headers: Send extra headers @@ -488,50 +477,6 @@ async def check_access( cast_to=UserCheckAccessResponse, ) - async def update_me( - self, - *, - bio: str | Omit = omit, - name: str | Omit = omit, - profile_picture: user_update_me_params.ProfilePicture | Omit = omit, - username: 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, - ) -> User: - """Updates the authenticated user's global profile. - - Not available to API keys. - - 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 - """ - return await self._patch( - "/users/me", - body=await async_maybe_transform( - { - "bio": bio, - "name": name, - "profile_picture": profile_picture, - "username": username, - }, - user_update_me_params.UserUpdateMeParams, - ), - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout - ), - cast_to=User, - ) - class UsersResourceWithRawResponse: def __init__(self, users: UsersResource) -> None: @@ -549,9 +494,6 @@ def __init__(self, users: UsersResource) -> None: self.check_access = to_raw_response_wrapper( users.check_access, ) - self.update_me = to_raw_response_wrapper( - users.update_me, - ) class AsyncUsersResourceWithRawResponse: @@ -570,9 +512,6 @@ def __init__(self, users: AsyncUsersResource) -> None: self.check_access = async_to_raw_response_wrapper( users.check_access, ) - self.update_me = async_to_raw_response_wrapper( - users.update_me, - ) class UsersResourceWithStreamingResponse: @@ -591,9 +530,6 @@ def __init__(self, users: UsersResource) -> None: self.check_access = to_streamed_response_wrapper( users.check_access, ) - self.update_me = to_streamed_response_wrapper( - users.update_me, - ) class AsyncUsersResourceWithStreamingResponse: @@ -612,6 +548,3 @@ def __init__(self, users: AsyncUsersResource) -> None: self.check_access = async_to_streamed_response_wrapper( users.check_access, ) - self.update_me = async_to_streamed_response_wrapper( - users.update_me, - ) diff --git a/src/whop_sdk/types/__init__.py b/src/whop_sdk/types/__init__.py index 9e70ccd3..23a936a3 100644 --- a/src/whop_sdk/types/__init__.py +++ b/src/whop_sdk/types/__init__.py @@ -138,6 +138,7 @@ from .refund_list_params import RefundListParams as RefundListParams from .review_list_params import ReviewListParams as ReviewListParams from .swap_create_params import SwapCreateParams as SwapCreateParams +from .user_list_response import UserListResponse as UserListResponse from .user_update_params import UserUpdateParams as UserUpdateParams from .wallet_send_params import WalletSendParams as WalletSendParams from .account_list_params import AccountListParams as AccountListParams @@ -211,7 +212,6 @@ from .product_update_params import ProductUpdateParams as ProductUpdateParams from .refund_reference_type import RefundReferenceType as RefundReferenceType from .topup_create_response import TopupCreateResponse as TopupCreateResponse -from .user_update_me_params import UserUpdateMeParams as UserUpdateMeParams from .wallet_balance_params import WalletBalanceParams as WalletBalanceParams from .webhook_create_params import WebhookCreateParams as WebhookCreateParams from .webhook_list_response import WebhookListResponse as WebhookListResponse diff --git a/src/whop_sdk/types/conversion_create_params.py b/src/whop_sdk/types/conversion_create_params.py index 21b04154..eb09b960 100644 --- a/src/whop_sdk/types/conversion_create_params.py +++ b/src/whop_sdk/types/conversion_create_params.py @@ -53,7 +53,7 @@ class ConversionCreateParams(TypedDict, total=False): """The available currencies on the platform""" custom_name: Optional[str] - """Custom event name when event_name is 'custom'.""" + """Custom event name when event_name is 'custom'. Maximum 35 chars for this value.""" duration: Optional[int] """For 'leave' events: milliseconds the visitor spent on the page.""" diff --git a/src/whop_sdk/types/user.py b/src/whop_sdk/types/user.py index 9e5491e4..4e52982f 100644 --- a/src/whop_sdk/types/user.py +++ b/src/whop_sdk/types/user.py @@ -1,27 +1,49 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from typing import Optional +from datetime import datetime from .._models import BaseModel -__all__ = ["User"] +__all__ = ["User", "ProfilePicture"] + + +class ProfilePicture(BaseModel): + """The user's profile picture attachment with URL, content type, and file metadata. + + Null if using a legacy profile picture. + """ + + url: Optional[str] = None + """A pre-optimized URL for rendering this attachment on the client. + + This should be used for displaying attachments in apps. + """ class User(BaseModel): + """A user account on Whop. + + Contains profile information, identity details, and social connections. + """ + id: str - """The ID of the user, which will look like user\\__******\\********""" + """The unique identifier for the user.""" bio: Optional[str] = None - """The user's biography""" + """A short biography written by the user, displayed on their public profile.""" - created_at: str - """When the user was created, as an ISO 8601 timestamp""" + created_at: datetime + """The datetime the user was created.""" name: Optional[str] = None - """The user's display name""" + """The user's display name shown on their public profile.""" + + profile_picture: Optional[ProfilePicture] = None + """The user's profile picture attachment with URL, content type, and file metadata. - profile_picture: Optional[object] = None - """The user's profile picture, an object with a url""" + Null if using a legacy profile picture. + """ username: str - """The user's unique username""" + """The user's unique username shown on their public profile.""" diff --git a/src/whop_sdk/types/user_check_access_response.py b/src/whop_sdk/types/user_check_access_response.py index ad8fb2fe..270d822a 100644 --- a/src/whop_sdk/types/user_check_access_response.py +++ b/src/whop_sdk/types/user_check_access_response.py @@ -1,13 +1,16 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -from typing_extensions import Literal - from .._models import BaseModel +from .shared.access_level import AccessLevel __all__ = ["UserCheckAccessResponse"] class UserCheckAccessResponse(BaseModel): - access_level: Literal["no_access", "admin", "customer"] + """The result of a has access check for the developer API""" + + access_level: AccessLevel + """The permission level of the user""" has_access: bool + """Whether the user has access to the resource""" diff --git a/src/whop_sdk/types/user_list_params.py b/src/whop_sdk/types/user_list_params.py index 14d3e4fa..bce47d6c 100644 --- a/src/whop_sdk/types/user_list_params.py +++ b/src/whop_sdk/types/user_list_params.py @@ -2,23 +2,24 @@ from __future__ import annotations +from typing import Optional from typing_extensions import TypedDict __all__ = ["UserListParams"] class UserListParams(TypedDict, total=False): - after: str - """A cursor; returns users after this position.""" + after: Optional[str] + """Returns the elements in the list that come after the specified cursor.""" - before: str - """A cursor; returns users before this position.""" + before: Optional[str] + """Returns the elements in the list that come before the specified cursor.""" - first: int - """The number of users to return (max 50).""" + first: Optional[int] + """Returns the first _n_ elements from the list.""" - last: int - """The number of users to return from the end of the range.""" + last: Optional[int] + """Returns the last _n_ elements from the list.""" - query: str - """A search term to filter users by name or username.""" + query: Optional[str] + """Search term to filter by name or username.""" diff --git a/src/whop_sdk/types/user_list_response.py b/src/whop_sdk/types/user_list_response.py new file mode 100644 index 00000000..1621e20c --- /dev/null +++ b/src/whop_sdk/types/user_list_response.py @@ -0,0 +1,49 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from datetime import datetime + +from .._models import BaseModel + +__all__ = ["UserListResponse", "ProfilePicture"] + + +class ProfilePicture(BaseModel): + """The user's profile picture attachment with URL, content type, and file metadata. + + Null if using a legacy profile picture. + """ + + url: Optional[str] = None + """A pre-optimized URL for rendering this attachment on the client. + + This should be used for displaying attachments in apps. + """ + + +class UserListResponse(BaseModel): + """A user account on Whop. + + Contains profile information, identity details, and social connections. + """ + + id: str + """The unique identifier for the user.""" + + bio: Optional[str] = None + """A short biography written by the user, displayed on their public profile.""" + + created_at: datetime + """The datetime the user was created.""" + + name: Optional[str] = None + """The user's display name shown on their public profile.""" + + profile_picture: Optional[ProfilePicture] = None + """The user's profile picture attachment with URL, content type, and file metadata. + + Null if using a legacy profile picture. + """ + + username: str + """The user's unique username shown on their public profile.""" diff --git a/src/whop_sdk/types/user_retrieve_params.py b/src/whop_sdk/types/user_retrieve_params.py index afb9ca68..8534761b 100644 --- a/src/whop_sdk/types/user_retrieve_params.py +++ b/src/whop_sdk/types/user_retrieve_params.py @@ -2,14 +2,15 @@ from __future__ import annotations +from typing import Optional from typing_extensions import TypedDict __all__ = ["UserRetrieveParams"] class UserRetrieveParams(TypedDict, total=False): - account_id: str + company_id: Optional[str] """ - When set, returns the user's account-specific profile overrides for this - account. + When provided, returns the user's company-specific profile overrides (name, + profile picture) instead of their global profile. """ diff --git a/src/whop_sdk/types/user_update_me_params.py b/src/whop_sdk/types/user_update_me_params.py deleted file mode 100644 index cbd38b21..00000000 --- a/src/whop_sdk/types/user_update_me_params.py +++ /dev/null @@ -1,23 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from __future__ import annotations - -from typing_extensions import TypedDict - -__all__ = ["UserUpdateMeParams", "ProfilePicture"] - - -class UserUpdateMeParams(TypedDict, total=False): - bio: str - - name: str - - profile_picture: ProfilePicture - - username: str - - -class ProfilePicture(TypedDict, total=False): - id: str - - direct_upload_id: str diff --git a/src/whop_sdk/types/user_update_params.py b/src/whop_sdk/types/user_update_params.py index 6ac5f721..a1c8572a 100644 --- a/src/whop_sdk/types/user_update_params.py +++ b/src/whop_sdk/types/user_update_params.py @@ -2,15 +2,38 @@ from __future__ import annotations -from typing_extensions import TypedDict +from typing import Optional +from typing_extensions import Required, TypedDict -__all__ = ["UserUpdateParams"] +__all__ = ["UserUpdateParams", "ProfilePicture"] class UserUpdateParams(TypedDict, total=False): - account_id: str - """The account whose profile override to update. Required for API key callers.""" + bio: Optional[str] + """A short biography displayed on the user's public profile.""" - bio: str + company_id: Optional[str] + """ + When provided, updates the user's profile overrides for this company instead of + the global profile. Pass name and profile_picture to set overrides, or null to + clear them. + """ - name: str + name: Optional[str] + """The user's display name shown on their public profile. Maximum 100 characters.""" + + profile_picture: Optional[ProfilePicture] + """The user's profile picture image attachment.""" + + username: Optional[str] + """The user's unique username. + + Alphanumeric characters and hyphens only. Maximum 42 characters. + """ + + +class ProfilePicture(TypedDict, total=False): + """The user's profile picture image attachment.""" + + id: Required[str] + """The ID of an existing file object.""" diff --git a/tests/api_resources/test_users.py b/tests/api_resources/test_users.py index df9b5aa7..ee565217 100644 --- a/tests/api_resources/test_users.py +++ b/tests/api_resources/test_users.py @@ -11,6 +11,7 @@ from tests.utils import assert_matches_type from whop_sdk.types import ( User, + UserListResponse, UserCheckAccessResponse, ) from whop_sdk.pagination import SyncCursorPage, AsyncCursorPage @@ -25,7 +26,7 @@ class TestUsers: @parametrize def test_method_retrieve(self, client: Whop) -> None: user = client.users.retrieve( - id="id", + id="user_xxxxxxxxxxxxx", ) assert_matches_type(User, user, path=["response"]) @@ -33,8 +34,8 @@ def test_method_retrieve(self, client: Whop) -> None: @parametrize def test_method_retrieve_with_all_params(self, client: Whop) -> None: user = client.users.retrieve( - id="id", - account_id="account_id", + id="user_xxxxxxxxxxxxx", + company_id="biz_xxxxxxxxxxxxxx", ) assert_matches_type(User, user, path=["response"]) @@ -42,7 +43,7 @@ def test_method_retrieve_with_all_params(self, client: Whop) -> None: @parametrize def test_raw_response_retrieve(self, client: Whop) -> None: response = client.users.with_raw_response.retrieve( - id="id", + id="user_xxxxxxxxxxxxx", ) assert response.is_closed is True @@ -54,7 +55,7 @@ def test_raw_response_retrieve(self, client: Whop) -> None: @parametrize def test_streaming_response_retrieve(self, client: Whop) -> None: with client.users.with_streaming_response.retrieve( - id="id", + id="user_xxxxxxxxxxxxx", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -76,7 +77,7 @@ def test_path_params_retrieve(self, client: Whop) -> None: @parametrize def test_method_update(self, client: Whop) -> None: user = client.users.update( - id="id", + id="user_xxxxxxxxxxxxx", ) assert_matches_type(User, user, path=["response"]) @@ -84,10 +85,12 @@ def test_method_update(self, client: Whop) -> None: @parametrize def test_method_update_with_all_params(self, client: Whop) -> None: user = client.users.update( - id="id", - account_id="account_id", + id="user_xxxxxxxxxxxxx", bio="bio", + company_id="biz_xxxxxxxxxxxxxx", name="name", + profile_picture={"id": "id"}, + username="username", ) assert_matches_type(User, user, path=["response"]) @@ -95,7 +98,7 @@ def test_method_update_with_all_params(self, client: Whop) -> None: @parametrize def test_raw_response_update(self, client: Whop) -> None: response = client.users.with_raw_response.update( - id="id", + id="user_xxxxxxxxxxxxx", ) assert response.is_closed is True @@ -107,7 +110,7 @@ def test_raw_response_update(self, client: Whop) -> None: @parametrize def test_streaming_response_update(self, client: Whop) -> None: with client.users.with_streaming_response.update( - id="id", + id="user_xxxxxxxxxxxxx", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -129,7 +132,7 @@ def test_path_params_update(self, client: Whop) -> None: @parametrize def test_method_list(self, client: Whop) -> None: user = client.users.list() - assert_matches_type(SyncCursorPage[User], user, path=["response"]) + assert_matches_type(SyncCursorPage[UserListResponse], user, path=["response"]) @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize @@ -137,11 +140,11 @@ def test_method_list_with_all_params(self, client: Whop) -> None: user = client.users.list( after="after", before="before", - first=0, - last=0, + first=42, + last=42, query="query", ) - assert_matches_type(SyncCursorPage[User], user, path=["response"]) + assert_matches_type(SyncCursorPage[UserListResponse], user, path=["response"]) @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize @@ -151,7 +154,7 @@ def test_raw_response_list(self, client: Whop) -> None: assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" user = response.parse() - assert_matches_type(SyncCursorPage[User], user, path=["response"]) + assert_matches_type(SyncCursorPage[UserListResponse], user, path=["response"]) @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize @@ -161,7 +164,7 @@ def test_streaming_response_list(self, client: Whop) -> None: assert response.http_request.headers.get("X-Stainless-Lang") == "python" user = response.parse() - assert_matches_type(SyncCursorPage[User], user, path=["response"]) + assert_matches_type(SyncCursorPage[UserListResponse], user, path=["response"]) assert cast(Any, response.is_closed) is True @@ -170,7 +173,7 @@ def test_streaming_response_list(self, client: Whop) -> None: def test_method_check_access(self, client: Whop) -> None: user = client.users.check_access( resource_id="resource_id", - id="id", + id="user_xxxxxxxxxxxxx", ) assert_matches_type(UserCheckAccessResponse, user, path=["response"]) @@ -179,7 +182,7 @@ def test_method_check_access(self, client: Whop) -> None: def test_raw_response_check_access(self, client: Whop) -> None: response = client.users.with_raw_response.check_access( resource_id="resource_id", - id="id", + id="user_xxxxxxxxxxxxx", ) assert response.is_closed is True @@ -192,7 +195,7 @@ def test_raw_response_check_access(self, client: Whop) -> None: def test_streaming_response_check_access(self, client: Whop) -> None: with client.users.with_streaming_response.check_access( resource_id="resource_id", - id="id", + id="user_xxxxxxxxxxxxx", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -214,51 +217,9 @@ def test_path_params_check_access(self, client: Whop) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `resource_id` but received ''"): client.users.with_raw_response.check_access( resource_id="", - id="id", + id="user_xxxxxxxxxxxxx", ) - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - def test_method_update_me(self, client: Whop) -> None: - user = client.users.update_me() - assert_matches_type(User, user, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - def test_method_update_me_with_all_params(self, client: Whop) -> None: - user = client.users.update_me( - bio="bio", - name="name", - profile_picture={ - "id": "id", - "direct_upload_id": "direct_upload_id", - }, - username="username", - ) - assert_matches_type(User, user, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - def test_raw_response_update_me(self, client: Whop) -> None: - response = client.users.with_raw_response.update_me() - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - user = response.parse() - assert_matches_type(User, user, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - def test_streaming_response_update_me(self, client: Whop) -> None: - with client.users.with_streaming_response.update_me() as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - user = response.parse() - assert_matches_type(User, user, path=["response"]) - - assert cast(Any, response.is_closed) is True - class TestAsyncUsers: parametrize = pytest.mark.parametrize( @@ -269,7 +230,7 @@ class TestAsyncUsers: @parametrize async def test_method_retrieve(self, async_client: AsyncWhop) -> None: user = await async_client.users.retrieve( - id="id", + id="user_xxxxxxxxxxxxx", ) assert_matches_type(User, user, path=["response"]) @@ -277,8 +238,8 @@ async def test_method_retrieve(self, async_client: AsyncWhop) -> None: @parametrize async def test_method_retrieve_with_all_params(self, async_client: AsyncWhop) -> None: user = await async_client.users.retrieve( - id="id", - account_id="account_id", + id="user_xxxxxxxxxxxxx", + company_id="biz_xxxxxxxxxxxxxx", ) assert_matches_type(User, user, path=["response"]) @@ -286,7 +247,7 @@ async def test_method_retrieve_with_all_params(self, async_client: AsyncWhop) -> @parametrize async def test_raw_response_retrieve(self, async_client: AsyncWhop) -> None: response = await async_client.users.with_raw_response.retrieve( - id="id", + id="user_xxxxxxxxxxxxx", ) assert response.is_closed is True @@ -298,7 +259,7 @@ async def test_raw_response_retrieve(self, async_client: AsyncWhop) -> None: @parametrize async def test_streaming_response_retrieve(self, async_client: AsyncWhop) -> None: async with async_client.users.with_streaming_response.retrieve( - id="id", + id="user_xxxxxxxxxxxxx", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -320,7 +281,7 @@ async def test_path_params_retrieve(self, async_client: AsyncWhop) -> None: @parametrize async def test_method_update(self, async_client: AsyncWhop) -> None: user = await async_client.users.update( - id="id", + id="user_xxxxxxxxxxxxx", ) assert_matches_type(User, user, path=["response"]) @@ -328,10 +289,12 @@ async def test_method_update(self, async_client: AsyncWhop) -> None: @parametrize async def test_method_update_with_all_params(self, async_client: AsyncWhop) -> None: user = await async_client.users.update( - id="id", - account_id="account_id", + id="user_xxxxxxxxxxxxx", bio="bio", + company_id="biz_xxxxxxxxxxxxxx", name="name", + profile_picture={"id": "id"}, + username="username", ) assert_matches_type(User, user, path=["response"]) @@ -339,7 +302,7 @@ async def test_method_update_with_all_params(self, async_client: AsyncWhop) -> N @parametrize async def test_raw_response_update(self, async_client: AsyncWhop) -> None: response = await async_client.users.with_raw_response.update( - id="id", + id="user_xxxxxxxxxxxxx", ) assert response.is_closed is True @@ -351,7 +314,7 @@ async def test_raw_response_update(self, async_client: AsyncWhop) -> None: @parametrize async def test_streaming_response_update(self, async_client: AsyncWhop) -> None: async with async_client.users.with_streaming_response.update( - id="id", + id="user_xxxxxxxxxxxxx", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -373,7 +336,7 @@ async def test_path_params_update(self, async_client: AsyncWhop) -> None: @parametrize async def test_method_list(self, async_client: AsyncWhop) -> None: user = await async_client.users.list() - assert_matches_type(AsyncCursorPage[User], user, path=["response"]) + assert_matches_type(AsyncCursorPage[UserListResponse], user, path=["response"]) @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize @@ -381,11 +344,11 @@ async def test_method_list_with_all_params(self, async_client: AsyncWhop) -> Non user = await async_client.users.list( after="after", before="before", - first=0, - last=0, + first=42, + last=42, query="query", ) - assert_matches_type(AsyncCursorPage[User], user, path=["response"]) + assert_matches_type(AsyncCursorPage[UserListResponse], user, path=["response"]) @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize @@ -395,7 +358,7 @@ async def test_raw_response_list(self, async_client: AsyncWhop) -> None: assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" user = await response.parse() - assert_matches_type(AsyncCursorPage[User], user, path=["response"]) + assert_matches_type(AsyncCursorPage[UserListResponse], user, path=["response"]) @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize @@ -405,7 +368,7 @@ async def test_streaming_response_list(self, async_client: AsyncWhop) -> None: assert response.http_request.headers.get("X-Stainless-Lang") == "python" user = await response.parse() - assert_matches_type(AsyncCursorPage[User], user, path=["response"]) + assert_matches_type(AsyncCursorPage[UserListResponse], user, path=["response"]) assert cast(Any, response.is_closed) is True @@ -414,7 +377,7 @@ async def test_streaming_response_list(self, async_client: AsyncWhop) -> None: async def test_method_check_access(self, async_client: AsyncWhop) -> None: user = await async_client.users.check_access( resource_id="resource_id", - id="id", + id="user_xxxxxxxxxxxxx", ) assert_matches_type(UserCheckAccessResponse, user, path=["response"]) @@ -423,7 +386,7 @@ async def test_method_check_access(self, async_client: AsyncWhop) -> None: async def test_raw_response_check_access(self, async_client: AsyncWhop) -> None: response = await async_client.users.with_raw_response.check_access( resource_id="resource_id", - id="id", + id="user_xxxxxxxxxxxxx", ) assert response.is_closed is True @@ -436,7 +399,7 @@ async def test_raw_response_check_access(self, async_client: AsyncWhop) -> None: async def test_streaming_response_check_access(self, async_client: AsyncWhop) -> None: async with async_client.users.with_streaming_response.check_access( resource_id="resource_id", - id="id", + id="user_xxxxxxxxxxxxx", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -458,47 +421,5 @@ async def test_path_params_check_access(self, async_client: AsyncWhop) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `resource_id` but received ''"): await async_client.users.with_raw_response.check_access( resource_id="", - id="id", + id="user_xxxxxxxxxxxxx", ) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - async def test_method_update_me(self, async_client: AsyncWhop) -> None: - user = await async_client.users.update_me() - assert_matches_type(User, user, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - async def test_method_update_me_with_all_params(self, async_client: AsyncWhop) -> None: - user = await async_client.users.update_me( - bio="bio", - name="name", - profile_picture={ - "id": "id", - "direct_upload_id": "direct_upload_id", - }, - username="username", - ) - assert_matches_type(User, user, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - async def test_raw_response_update_me(self, async_client: AsyncWhop) -> None: - response = await async_client.users.with_raw_response.update_me() - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - user = await response.parse() - assert_matches_type(User, user, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - async def test_streaming_response_update_me(self, async_client: AsyncWhop) -> None: - async with async_client.users.with_streaming_response.update_me() as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - user = await response.parse() - assert_matches_type(User, user, path=["response"]) - - assert cast(Any, response.is_closed) is True From b13d3f26857125f245bca5420c88dd2e2528bcbd Mon Sep 17 00:00:00 2001 From: stlc-bot Date: Tue, 9 Jun 2026 19:46:53 +0000 Subject: [PATCH 011/109] feat(ads): normalize and classify ad platform issues Stainless-Generated-From: 75c4fa2552714b27dab4edbc91ce910e86acd5aa --- src/whop_sdk/types/ad.py | 18 +++++++++--------- src/whop_sdk/types/ad_campaign.py | 18 +++++++++--------- .../types/ad_campaign_list_response.py | 18 +++++++++--------- src/whop_sdk/types/ad_group.py | 18 +++++++++--------- src/whop_sdk/types/ad_group_list_response.py | 18 +++++++++--------- src/whop_sdk/types/ad_list_response.py | 18 +++++++++--------- 6 files changed, 54 insertions(+), 54 deletions(-) diff --git a/src/whop_sdk/types/ad.py b/src/whop_sdk/types/ad.py index f88c0755..9c405ea6 100644 --- a/src/whop_sdk/types/ad.py +++ b/src/whop_sdk/types/ad.py @@ -29,18 +29,12 @@ class AdGroup(BaseModel): class Issue(BaseModel): """A platform-reported issue on an ad object (rejection, policy flag, etc.).""" + category: Optional[Literal["policy_rejection", "creative_media", "audience_targeting", "ad_volume_limit"]] = None + """Whop's canonical category that a raw platform issue is bucketed into.""" + created_at: datetime """When the issue was first reported.""" - error_code: Optional[str] = None - """Platform-specific error code.""" - - error_message: Optional[str] = None - """Full error detail from the platform.""" - - error_summary: str - """Short description of the issue.""" - resolution_status: Literal["open", "resolved", "acknowledged"] """Current resolution status.""" @@ -56,6 +50,12 @@ class Issue(BaseModel): Pairs with `resourceId`. """ + subtype: Optional[str] = None + """Finer-grained sub-bucket within the category (e.g. + + the specific Meta policy for a rejection). + """ + class Ad(BaseModel): """An ad belonging to an ad group.""" diff --git a/src/whop_sdk/types/ad_campaign.py b/src/whop_sdk/types/ad_campaign.py index 2d43d055..80f875e1 100644 --- a/src/whop_sdk/types/ad_campaign.py +++ b/src/whop_sdk/types/ad_campaign.py @@ -16,18 +16,12 @@ class Issue(BaseModel): """A platform-reported issue on an ad object (rejection, policy flag, etc.).""" + category: Optional[Literal["policy_rejection", "creative_media", "audience_targeting", "ad_volume_limit"]] = None + """Whop's canonical category that a raw platform issue is bucketed into.""" + created_at: datetime """When the issue was first reported.""" - error_code: Optional[str] = None - """Platform-specific error code.""" - - error_message: Optional[str] = None - """Full error detail from the platform.""" - - error_summary: str - """Short description of the issue.""" - resolution_status: Literal["open", "resolved", "acknowledged"] """Current resolution status.""" @@ -43,6 +37,12 @@ class Issue(BaseModel): Pairs with `resourceId`. """ + subtype: Optional[str] = None + """Finer-grained sub-bucket within the category (e.g. + + the specific Meta policy for a rejection). + """ + class AdCampaign(BaseModel): """An advertising campaign running on an external platform or within Whop.""" diff --git a/src/whop_sdk/types/ad_campaign_list_response.py b/src/whop_sdk/types/ad_campaign_list_response.py index 01227bbe..731878bf 100644 --- a/src/whop_sdk/types/ad_campaign_list_response.py +++ b/src/whop_sdk/types/ad_campaign_list_response.py @@ -16,18 +16,12 @@ class Issue(BaseModel): """A platform-reported issue on an ad object (rejection, policy flag, etc.).""" + category: Optional[Literal["policy_rejection", "creative_media", "audience_targeting", "ad_volume_limit"]] = None + """Whop's canonical category that a raw platform issue is bucketed into.""" + created_at: datetime """When the issue was first reported.""" - error_code: Optional[str] = None - """Platform-specific error code.""" - - error_message: Optional[str] = None - """Full error detail from the platform.""" - - error_summary: str - """Short description of the issue.""" - resolution_status: Literal["open", "resolved", "acknowledged"] """Current resolution status.""" @@ -43,6 +37,12 @@ class Issue(BaseModel): Pairs with `resourceId`. """ + subtype: Optional[str] = None + """Finer-grained sub-bucket within the category (e.g. + + the specific Meta policy for a rejection). + """ + class AdCampaignListResponse(BaseModel): """An advertising campaign running on an external platform or within Whop.""" diff --git a/src/whop_sdk/types/ad_group.py b/src/whop_sdk/types/ad_group.py index 34f18caa..de67e0bb 100644 --- a/src/whop_sdk/types/ad_group.py +++ b/src/whop_sdk/types/ad_group.py @@ -23,18 +23,12 @@ class AdCampaign(BaseModel): class Issue(BaseModel): """A platform-reported issue on an ad object (rejection, policy flag, etc.).""" + category: Optional[Literal["policy_rejection", "creative_media", "audience_targeting", "ad_volume_limit"]] = None + """Whop's canonical category that a raw platform issue is bucketed into.""" + created_at: datetime """When the issue was first reported.""" - error_code: Optional[str] = None - """Platform-specific error code.""" - - error_message: Optional[str] = None - """Full error detail from the platform.""" - - error_summary: str - """Short description of the issue.""" - resolution_status: Literal["open", "resolved", "acknowledged"] """Current resolution status.""" @@ -50,6 +44,12 @@ class Issue(BaseModel): Pairs with `resourceId`. """ + subtype: Optional[str] = None + """Finer-grained sub-bucket within the category (e.g. + + the specific Meta policy for a rejection). + """ + class AdGroup(BaseModel): """An ad group belonging to an ad campaign.""" diff --git a/src/whop_sdk/types/ad_group_list_response.py b/src/whop_sdk/types/ad_group_list_response.py index 35deef5d..4345765a 100644 --- a/src/whop_sdk/types/ad_group_list_response.py +++ b/src/whop_sdk/types/ad_group_list_response.py @@ -23,18 +23,12 @@ class AdCampaign(BaseModel): class Issue(BaseModel): """A platform-reported issue on an ad object (rejection, policy flag, etc.).""" + category: Optional[Literal["policy_rejection", "creative_media", "audience_targeting", "ad_volume_limit"]] = None + """Whop's canonical category that a raw platform issue is bucketed into.""" + created_at: datetime """When the issue was first reported.""" - error_code: Optional[str] = None - """Platform-specific error code.""" - - error_message: Optional[str] = None - """Full error detail from the platform.""" - - error_summary: str - """Short description of the issue.""" - resolution_status: Literal["open", "resolved", "acknowledged"] """Current resolution status.""" @@ -50,6 +44,12 @@ class Issue(BaseModel): Pairs with `resourceId`. """ + subtype: Optional[str] = None + """Finer-grained sub-bucket within the category (e.g. + + the specific Meta policy for a rejection). + """ + class AdGroupListResponse(BaseModel): """An ad group belonging to an ad campaign.""" diff --git a/src/whop_sdk/types/ad_list_response.py b/src/whop_sdk/types/ad_list_response.py index 7c956fb3..742a4b3d 100644 --- a/src/whop_sdk/types/ad_list_response.py +++ b/src/whop_sdk/types/ad_list_response.py @@ -29,18 +29,12 @@ class AdGroup(BaseModel): class Issue(BaseModel): """A platform-reported issue on an ad object (rejection, policy flag, etc.).""" + category: Optional[Literal["policy_rejection", "creative_media", "audience_targeting", "ad_volume_limit"]] = None + """Whop's canonical category that a raw platform issue is bucketed into.""" + created_at: datetime """When the issue was first reported.""" - error_code: Optional[str] = None - """Platform-specific error code.""" - - error_message: Optional[str] = None - """Full error detail from the platform.""" - - error_summary: str - """Short description of the issue.""" - resolution_status: Literal["open", "resolved", "acknowledged"] """Current resolution status.""" @@ -56,6 +50,12 @@ class Issue(BaseModel): Pairs with `resourceId`. """ + subtype: Optional[str] = None + """Finer-grained sub-bucket within the category (e.g. + + the specific Meta policy for a rejection). + """ + class AdListResponse(BaseModel): """An ad belonging to an ad group.""" From 83f00f71fe0e203c20f674a5fb8a03a62307256c Mon Sep 17 00:00:00 2001 From: stlc-bot Date: Tue, 9 Jun 2026 21:37:34 +0000 Subject: [PATCH 012/109] feat(api): native /users resource + date-based API versioning Stainless-Generated-From: a66d6b36f558b31e8f7726397989a9c15cce278c --- .stats.yml | 2 +- README.md | 4 +- api.md | 5 +- src/whop_sdk/_client.py | 26 +- src/whop_sdk/resources/users.py | 287 ++++++++++++------ src/whop_sdk/types/__init__.py | 2 +- src/whop_sdk/types/user.py | 40 +-- .../types/user_check_access_response.py | 9 +- src/whop_sdk/types/user_list_params.py | 21 +- src/whop_sdk/types/user_list_response.py | 49 --- src/whop_sdk/types/user_retrieve_params.py | 7 +- src/whop_sdk/types/user_update_me_params.py | 29 ++ src/whop_sdk/types/user_update_params.py | 31 +- tests/api_resources/test_users.py | 177 ++++++++--- 14 files changed, 413 insertions(+), 276 deletions(-) delete mode 100644 src/whop_sdk/types/user_list_response.py create mode 100644 src/whop_sdk/types/user_update_me_params.py diff --git a/.stats.yml b/.stats.yml index f1fe8e08..cd5b9cf9 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1 +1 @@ -configured_endpoints: 229 +configured_endpoints: 230 diff --git a/README.md b/README.md index f695ea0b..f4049761 100644 --- a/README.md +++ b/README.md @@ -13,8 +13,8 @@ It is generated with [Stainless](https://www.stainless.com/). Use the Whop MCP Server to enable AI assistants to interact with this API, allowing them to explore endpoints, make test requests, and use documentation to help integrate this SDK into your application. -[![Add to Cursor](https://cursor.com/deeplink/mcp-install-dark.svg)](https://cursor.com/en-US/install-mcp?name=%40whop%2Fmcp&config=eyJjb21tYW5kIjoibnB4IiwiYXJncyI6WyIteSIsIkB3aG9wL21jcCJdLCJlbnYiOnsiV0hPUF9BUElfS0VZIjoiTXkgQVBJIEtleSIsIldIT1BfV0VCSE9PS19TRUNSRVQiOiJNeSBXZWJob29rIEtleSIsIldIT1BfQVBQX0lEIjoiYXBwX3h4eHh4eHh4eHh4eHh4In19) -[![Install in VS Code](https://img.shields.io/badge/_-Add_to_VS_Code-blue?style=for-the-badge&logo=data:image/svg%2bxml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGZpbGw9Im5vbmUiIHZpZXdCb3g9IjAgMCA0MCA0MCI+PHBhdGggZmlsbD0iI0VFRSIgZmlsbC1ydWxlPSJldmVub2RkIiBkPSJNMzAuMjM1IDM5Ljg4NGEyLjQ5MSAyLjQ5MSAwIDAgMS0xLjc4MS0uNzNMMTIuNyAyNC43OGwtMy40NiAyLjYyNC0zLjQwNiAyLjU4MmExLjY2NSAxLjY2NSAwIDAgMS0xLjA4Mi4zMzggMS42NjQgMS42NjQgMCAwIDEtMS4wNDYtLjQzMWwtMi4yLTJhMS42NjYgMS42NjYgMCAwIDEgMC0yLjQ2M0w3LjQ1OCAyMCA0LjY3IDE3LjQ1MyAxLjUwNyAxNC41N2ExLjY2NSAxLjY2NSAwIDAgMSAwLTIuNDYzbDIuMi0yYTEuNjY1IDEuNjY1IDAgMCAxIDIuMTMtLjA5N2w2Ljg2MyA1LjIwOUwyOC40NTIuODQ0YTIuNDg4IDIuNDg4IDAgMCAxIDEuODQxLS43MjljLjM1MS4wMDkuNjk5LjA5MSAxLjAxOS4yNDVsOC4yMzYgMy45NjFhMi41IDIuNSAwIDAgMSAxLjQxNSAyLjI1M3YuMDk5LS4wNDVWMzMuMzd2LS4wNDUuMDk1YTIuNTAxIDIuNTAxIDAgMCAxLTEuNDE2IDIuMjU3bC04LjIzNSAzLjk2MWEyLjQ5MiAyLjQ5MiAwIDAgMS0xLjA3Ny4yNDZabS43MTYtMjguOTQ3LTExLjk0OCA5LjA2MiAxMS45NTIgOS4wNjUtLjAwNC0xOC4xMjdaIi8+PC9zdmc+)](https://vscode.stainless.com/mcp/%7B%22name%22%3A%22%40whop%2Fmcp%22%2C%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22-y%22%2C%22%40whop%2Fmcp%22%5D%2C%22env%22%3A%7B%22WHOP_API_KEY%22%3A%22My%20API%20Key%22%2C%22WHOP_WEBHOOK_SECRET%22%3A%22My%20Webhook%20Key%22%2C%22WHOP_APP_ID%22%3A%22app_xxxxxxxxxxxxxx%22%7D%7D) +[![Add to Cursor](https://cursor.com/deeplink/mcp-install-dark.svg)](https://cursor.com/en-US/install-mcp?name=%40whop%2Fmcp&config=eyJjb21tYW5kIjoibnB4IiwiYXJncyI6WyIteSIsIkB3aG9wL21jcCJdLCJlbnYiOnsiV0hPUF9BUElfS0VZIjoiTXkgQVBJIEtleSIsIldIT1BfV0VCSE9PS19TRUNSRVQiOiJNeSBXZWJob29rIEtleSIsIldIT1BfQVBQX0lEIjoiYXBwX3h4eHh4eHh4eHh4eHh4IiwiV0hPUF9BUElfVkVSU0lPTiI6IjIwMjYtMDYtMDgifX0) +[![Install in VS Code](https://img.shields.io/badge/_-Add_to_VS_Code-blue?style=for-the-badge&logo=data:image/svg%2bxml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGZpbGw9Im5vbmUiIHZpZXdCb3g9IjAgMCA0MCA0MCI+PHBhdGggZmlsbD0iI0VFRSIgZmlsbC1ydWxlPSJldmVub2RkIiBkPSJNMzAuMjM1IDM5Ljg4NGEyLjQ5MSAyLjQ5MSAwIDAgMS0xLjc4MS0uNzNMMTIuNyAyNC43OGwtMy40NiAyLjYyNC0zLjQwNiAyLjU4MmExLjY2NSAxLjY2NSAwIDAgMS0xLjA4Mi4zMzggMS42NjQgMS42NjQgMCAwIDEtMS4wNDYtLjQzMWwtMi4yLTJhMS42NjYgMS42NjYgMCAwIDEgMC0yLjQ2M0w3LjQ1OCAyMCA0LjY3IDE3LjQ1MyAxLjUwNyAxNC41N2ExLjY2NSAxLjY2NSAwIDAgMSAwLTIuNDYzbDIuMi0yYTEuNjY1IDEuNjY1IDAgMCAxIDIuMTMtLjA5N2w2Ljg2MyA1LjIwOUwyOC40NTIuODQ0YTIuNDg4IDIuNDg4IDAgMCAxIDEuODQxLS43MjljLjM1MS4wMDkuNjk5LjA5MSAxLjAxOS4yNDVsOC4yMzYgMy45NjFhMi41IDIuNSAwIDAgMSAxLjQxNSAyLjI1M3YuMDk5LS4wNDVWMzMuMzd2LS4wNDUuMDk1YTIuNTAxIDIuNTAxIDAgMCAxLTEuNDE2IDIuMjU3bC04LjIzNSAzLjk2MWEyLjQ5MiAyLjQ5MiAwIDAgMS0xLjA3Ny4yNDZabS43MTYtMjguOTQ3LTExLjk0OCA5LjA2MiAxMS45NTIgOS4wNjUtLjAwNC0xOC4xMjdaIi8+PC9zdmc+)](https://vscode.stainless.com/mcp/%7B%22name%22%3A%22%40whop%2Fmcp%22%2C%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22-y%22%2C%22%40whop%2Fmcp%22%5D%2C%22env%22%3A%7B%22WHOP_API_KEY%22%3A%22My%20API%20Key%22%2C%22WHOP_WEBHOOK_SECRET%22%3A%22My%20Webhook%20Key%22%2C%22WHOP_APP_ID%22%3A%22app_xxxxxxxxxxxxxx%22%2C%22WHOP_API_VERSION%22%3A%222026-06-08%22%7D%7D) > Note: You may need to set environment variables in your MCP client. diff --git a/api.md b/api.md index 8f4e6e82..401d5f4f 100644 --- a/api.md +++ b/api.md @@ -387,15 +387,16 @@ Methods: Types: ```python -from whop_sdk.types import User, UserListResponse, UserCheckAccessResponse +from whop_sdk.types import User, UserCheckAccessResponse ``` Methods: - client.users.retrieve(id, \*\*params) -> User - client.users.update(id, \*\*params) -> User -- client.users.list(\*\*params) -> SyncCursorPage[UserListResponse] +- client.users.list(\*\*params) -> SyncCursorPage[User] - client.users.check_access(resource_id, \*, id) -> UserCheckAccessResponse +- client.users.update_me(\*\*params) -> User # Payments diff --git a/src/whop_sdk/_client.py b/src/whop_sdk/_client.py index 4455e1e1..1259e198 100644 --- a/src/whop_sdk/_client.py +++ b/src/whop_sdk/_client.py @@ -180,6 +180,7 @@ class Whop(SyncAPIClient): api_key: str webhook_key: str | None app_id: str | None + version: str | None def __init__( self, @@ -187,6 +188,7 @@ def __init__( api_key: str | None = None, webhook_key: str | None = None, app_id: str | None = None, + version: str | None = None, base_url: str | httpx.URL | None = None, timeout: float | Timeout | None | NotGiven = not_given, max_retries: int = DEFAULT_MAX_RETRIES, @@ -212,6 +214,7 @@ def __init__( - `api_key` from `WHOP_API_KEY` - `webhook_key` from `WHOP_WEBHOOK_SECRET` - `app_id` from `WHOP_APP_ID` + - `version` from `WHOP_API_VERSION` """ if api_key is None: api_key = os.environ.get("WHOP_API_KEY") @@ -229,6 +232,10 @@ def __init__( app_id = os.environ.get("WHOP_APP_ID") self.app_id = app_id + if version is None: + version = os.environ.get("WHOP_API_VERSION") or "2026-06-08" + self.version = version + if base_url is None: base_url = os.environ.get("WHOP_BASE_URL") if base_url is None: @@ -382,7 +389,6 @@ def chat_channels(self) -> ChatChannelsResource: @cached_property def users(self) -> UsersResource: - """Users""" from .resources.users import UsersResource return UsersResource(self) @@ -723,6 +729,7 @@ def default_headers(self) -> dict[str, str | Omit]: **super().default_headers, "X-Stainless-Async": "false", "X-Whop-App-Id": self.app_id if self.app_id is not None else Omit(), + "Api-Version-Date": self.version if self.version is not None else Omit(), **self._custom_headers, } @@ -732,6 +739,7 @@ def copy( api_key: str | None = None, webhook_key: str | None = None, app_id: str | None = None, + version: str | None = None, base_url: str | httpx.URL | None = None, timeout: float | Timeout | None | NotGiven = not_given, http_client: httpx.Client | None = None, @@ -768,6 +776,7 @@ def copy( api_key=api_key or self.api_key, webhook_key=webhook_key or self.webhook_key, app_id=app_id or self.app_id, + version=version or self.version, base_url=base_url or self.base_url, timeout=self.timeout if isinstance(timeout, NotGiven) else timeout, http_client=http_client, @@ -820,6 +829,7 @@ class AsyncWhop(AsyncAPIClient): api_key: str webhook_key: str | None app_id: str | None + version: str | None def __init__( self, @@ -827,6 +837,7 @@ def __init__( api_key: str | None = None, webhook_key: str | None = None, app_id: str | None = None, + version: str | None = None, base_url: str | httpx.URL | None = None, timeout: float | Timeout | None | NotGiven = not_given, max_retries: int = DEFAULT_MAX_RETRIES, @@ -852,6 +863,7 @@ def __init__( - `api_key` from `WHOP_API_KEY` - `webhook_key` from `WHOP_WEBHOOK_SECRET` - `app_id` from `WHOP_APP_ID` + - `version` from `WHOP_API_VERSION` """ if api_key is None: api_key = os.environ.get("WHOP_API_KEY") @@ -869,6 +881,10 @@ def __init__( app_id = os.environ.get("WHOP_APP_ID") self.app_id = app_id + if version is None: + version = os.environ.get("WHOP_API_VERSION") or "2026-06-08" + self.version = version + if base_url is None: base_url = os.environ.get("WHOP_BASE_URL") if base_url is None: @@ -1022,7 +1038,6 @@ def chat_channels(self) -> AsyncChatChannelsResource: @cached_property def users(self) -> AsyncUsersResource: - """Users""" from .resources.users import AsyncUsersResource return AsyncUsersResource(self) @@ -1363,6 +1378,7 @@ def default_headers(self) -> dict[str, str | Omit]: **super().default_headers, "X-Stainless-Async": f"async:{get_async_library()}", "X-Whop-App-Id": self.app_id if self.app_id is not None else Omit(), + "Api-Version-Date": self.version if self.version is not None else Omit(), **self._custom_headers, } @@ -1372,6 +1388,7 @@ def copy( api_key: str | None = None, webhook_key: str | None = None, app_id: str | None = None, + version: str | None = None, base_url: str | httpx.URL | None = None, timeout: float | Timeout | None | NotGiven = not_given, http_client: httpx.AsyncClient | None = None, @@ -1408,6 +1425,7 @@ def copy( api_key=api_key or self.api_key, webhook_key=webhook_key or self.webhook_key, app_id=app_id or self.app_id, + version=version or self.version, base_url=base_url or self.base_url, timeout=self.timeout if isinstance(timeout, NotGiven) else timeout, http_client=http_client, @@ -1589,7 +1607,6 @@ def chat_channels(self) -> chat_channels.ChatChannelsResourceWithRawResponse: @cached_property def users(self) -> users.UsersResourceWithRawResponse: - """Users""" from .resources.users import UsersResourceWithRawResponse return UsersResourceWithRawResponse(self._client.users) @@ -2041,7 +2058,6 @@ def chat_channels(self) -> chat_channels.AsyncChatChannelsResourceWithRawRespons @cached_property def users(self) -> users.AsyncUsersResourceWithRawResponse: - """Users""" from .resources.users import AsyncUsersResourceWithRawResponse return AsyncUsersResourceWithRawResponse(self._client.users) @@ -2495,7 +2511,6 @@ def chat_channels(self) -> chat_channels.ChatChannelsResourceWithStreamingRespon @cached_property def users(self) -> users.UsersResourceWithStreamingResponse: - """Users""" from .resources.users import UsersResourceWithStreamingResponse return UsersResourceWithStreamingResponse(self._client.users) @@ -2951,7 +2966,6 @@ def chat_channels(self) -> chat_channels.AsyncChatChannelsResourceWithStreamingR @cached_property def users(self) -> users.AsyncUsersResourceWithStreamingResponse: - """Users""" from .resources.users import AsyncUsersResourceWithStreamingResponse return AsyncUsersResourceWithStreamingResponse(self._client.users) diff --git a/src/whop_sdk/resources/users.py b/src/whop_sdk/resources/users.py index e0ce27c0..39c16355 100644 --- a/src/whop_sdk/resources/users.py +++ b/src/whop_sdk/resources/users.py @@ -2,11 +2,9 @@ from __future__ import annotations -from typing import Optional - import httpx -from ..types import user_list_params, user_update_params, user_retrieve_params +from ..types import user_list_params, user_update_params, user_retrieve_params, user_update_me_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 @@ -20,15 +18,12 @@ from ..pagination import SyncCursorPage, AsyncCursorPage from ..types.user import User from .._base_client import AsyncPaginator, make_request_options -from ..types.user_list_response import UserListResponse from ..types.user_check_access_response import UserCheckAccessResponse __all__ = ["UsersResource", "AsyncUsersResource"] class UsersResource(SyncAPIResource): - """Users""" - @cached_property def with_raw_response(self) -> UsersResourceWithRawResponse: """ @@ -52,7 +47,7 @@ def retrieve( self, id: str, *, - company_id: Optional[str] | Omit = omit, + account_id: 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, @@ -61,11 +56,11 @@ def retrieve( timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> User: """ - Retrieves the details of an existing user. + Retrieves a user's public profile by user\\__ tag, username, or 'me'. Args: - company_id: When provided, returns the user's company-specific profile overrides (name, - profile picture) instead of their global profile. + account_id: When set, returns the user's account-specific profile overrides for this + account. extra_headers: Send extra headers @@ -84,7 +79,7 @@ def retrieve( extra_query=extra_query, extra_body=extra_body, timeout=timeout, - query=maybe_transform({"company_id": company_id}, user_retrieve_params.UserRetrieveParams), + query=maybe_transform({"account_id": account_id}, user_retrieve_params.UserRetrieveParams), ), cast_to=User, ) @@ -93,11 +88,11 @@ def update( self, id: str, *, - bio: Optional[str] | Omit = omit, - company_id: Optional[str] | Omit = omit, - name: Optional[str] | Omit = omit, - profile_picture: Optional[user_update_params.ProfilePicture] | Omit = omit, - username: Optional[str] | Omit = omit, + account_id: str | Omit = omit, + bio: str | Omit = omit, + name: str | Omit = omit, + profile_picture: user_update_params.ProfilePicture | Omit = omit, + username: 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, @@ -105,26 +100,13 @@ def update( extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> User: - """ - Update a user's profile by their ID. - - Required permissions: + """Updates a user. - - `user:profile:update` + A user token updates their own global profile; an API key + updates the user's account-specific profile override (account_id required). Args: - bio: A short biography displayed on the user's public profile. - - company_id: When provided, updates the user's profile overrides for this company instead of - the global profile. Pass name and profile_picture to set overrides, or null to - clear them. - - name: The user's display name shown on their public profile. Maximum 100 characters. - - profile_picture: The user's profile picture image attachment. - - username: The user's unique username. Alphanumeric characters and hyphens only. Maximum 42 - characters. + account_id: The account whose profile override to update. Required for API key callers. extra_headers: Send extra headers @@ -141,7 +123,6 @@ def update( body=maybe_transform( { "bio": bio, - "company_id": company_id, "name": name, "profile_picture": profile_picture, "username": username, @@ -149,7 +130,11 @@ def update( user_update_params.UserUpdateParams, ), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform({"account_id": account_id}, user_update_params.UserUpdateParams), ), cast_to=User, ) @@ -157,32 +142,33 @@ def update( def list( self, *, - after: Optional[str] | Omit = omit, - before: Optional[str] | Omit = omit, - first: Optional[int] | Omit = omit, - last: Optional[int] | Omit = omit, - query: Optional[str] | Omit = omit, + after: str | Omit = omit, + before: str | Omit = omit, + first: int | Omit = omit, + last: int | Omit = omit, + query: 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, - ) -> SyncCursorPage[UserListResponse]: + ) -> SyncCursorPage[User]: """ Search for users by name or username, ranked by social proximity to the - authenticated user. + authenticated user. Returns the user's most recently followed users when no + query is given. Args: - after: Returns the elements in the list that come after the specified cursor. + after: A cursor; returns users after this position. - before: Returns the elements in the list that come before the specified cursor. + before: A cursor; returns users before this position. - first: Returns the first _n_ elements from the list. + first: The number of users to return (max 50). - last: Returns the last _n_ elements from the list. + last: The number of users to return from the end of the range. - query: Search term to filter by name or username. + query: A search term to filter users by name or username. extra_headers: Send extra headers @@ -194,7 +180,7 @@ def list( """ return self._get_api_list( "/users", - page=SyncCursorPage[UserListResponse], + page=SyncCursorPage[User], options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, @@ -211,7 +197,7 @@ def list( user_list_params.UserListParams, ), ), - model=UserListResponse, + model=User, ) def check_access( @@ -227,8 +213,8 @@ def check_access( timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> UserCheckAccessResponse: """ - Check whether a user has access to a specific resource, and return their access - level. + Checks whether a user has access to a company, product, or experience the caller + can reach. Args: extra_headers: Send extra headers @@ -251,10 +237,60 @@ def check_access( cast_to=UserCheckAccessResponse, ) + def update_me( + self, + *, + account_id: str | Omit = omit, + bio: str | Omit = omit, + name: str | Omit = omit, + profile_picture: user_update_me_params.ProfilePicture | Omit = omit, + username: 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, + ) -> User: + """ + Updates the authenticated user's global profile, or their profile override for + an account when account_id is given. Not available to API keys. -class AsyncUsersResource(AsyncAPIResource): - """Users""" + Args: + account_id: When set, updates the authenticated user's profile override for this account + instead of their global profile. + + 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._patch( + "/users/me", + body=maybe_transform( + { + "bio": bio, + "name": name, + "profile_picture": profile_picture, + "username": username, + }, + user_update_me_params.UserUpdateMeParams, + ), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform({"account_id": account_id}, user_update_me_params.UserUpdateMeParams), + ), + cast_to=User, + ) + + +class AsyncUsersResource(AsyncAPIResource): @cached_property def with_raw_response(self) -> AsyncUsersResourceWithRawResponse: """ @@ -278,7 +314,7 @@ async def retrieve( self, id: str, *, - company_id: Optional[str] | Omit = omit, + account_id: 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, @@ -287,11 +323,11 @@ async def retrieve( timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> User: """ - Retrieves the details of an existing user. + Retrieves a user's public profile by user\\__ tag, username, or 'me'. Args: - company_id: When provided, returns the user's company-specific profile overrides (name, - profile picture) instead of their global profile. + account_id: When set, returns the user's account-specific profile overrides for this + account. extra_headers: Send extra headers @@ -310,7 +346,7 @@ async def retrieve( extra_query=extra_query, extra_body=extra_body, timeout=timeout, - query=await async_maybe_transform({"company_id": company_id}, user_retrieve_params.UserRetrieveParams), + query=await async_maybe_transform({"account_id": account_id}, user_retrieve_params.UserRetrieveParams), ), cast_to=User, ) @@ -319,11 +355,11 @@ async def update( self, id: str, *, - bio: Optional[str] | Omit = omit, - company_id: Optional[str] | Omit = omit, - name: Optional[str] | Omit = omit, - profile_picture: Optional[user_update_params.ProfilePicture] | Omit = omit, - username: Optional[str] | Omit = omit, + account_id: str | Omit = omit, + bio: str | Omit = omit, + name: str | Omit = omit, + profile_picture: user_update_params.ProfilePicture | Omit = omit, + username: 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, @@ -331,26 +367,13 @@ async def update( extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> User: - """ - Update a user's profile by their ID. + """Updates a user. - Required permissions: - - - `user:profile:update` + A user token updates their own global profile; an API key + updates the user's account-specific profile override (account_id required). Args: - bio: A short biography displayed on the user's public profile. - - company_id: When provided, updates the user's profile overrides for this company instead of - the global profile. Pass name and profile_picture to set overrides, or null to - clear them. - - name: The user's display name shown on their public profile. Maximum 100 characters. - - profile_picture: The user's profile picture image attachment. - - username: The user's unique username. Alphanumeric characters and hyphens only. Maximum 42 - characters. + account_id: The account whose profile override to update. Required for API key callers. extra_headers: Send extra headers @@ -367,7 +390,6 @@ async def update( body=await async_maybe_transform( { "bio": bio, - "company_id": company_id, "name": name, "profile_picture": profile_picture, "username": username, @@ -375,7 +397,11 @@ async def update( user_update_params.UserUpdateParams, ), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=await async_maybe_transform({"account_id": account_id}, user_update_params.UserUpdateParams), ), cast_to=User, ) @@ -383,32 +409,33 @@ async def update( def list( self, *, - after: Optional[str] | Omit = omit, - before: Optional[str] | Omit = omit, - first: Optional[int] | Omit = omit, - last: Optional[int] | Omit = omit, - query: Optional[str] | Omit = omit, + after: str | Omit = omit, + before: str | Omit = omit, + first: int | Omit = omit, + last: int | Omit = omit, + query: 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, - ) -> AsyncPaginator[UserListResponse, AsyncCursorPage[UserListResponse]]: + ) -> AsyncPaginator[User, AsyncCursorPage[User]]: """ Search for users by name or username, ranked by social proximity to the - authenticated user. + authenticated user. Returns the user's most recently followed users when no + query is given. Args: - after: Returns the elements in the list that come after the specified cursor. + after: A cursor; returns users after this position. - before: Returns the elements in the list that come before the specified cursor. + before: A cursor; returns users before this position. - first: Returns the first _n_ elements from the list. + first: The number of users to return (max 50). - last: Returns the last _n_ elements from the list. + last: The number of users to return from the end of the range. - query: Search term to filter by name or username. + query: A search term to filter users by name or username. extra_headers: Send extra headers @@ -420,7 +447,7 @@ def list( """ return self._get_api_list( "/users", - page=AsyncCursorPage[UserListResponse], + page=AsyncCursorPage[User], options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, @@ -437,7 +464,7 @@ def list( user_list_params.UserListParams, ), ), - model=UserListResponse, + model=User, ) async def check_access( @@ -453,8 +480,8 @@ async def check_access( timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> UserCheckAccessResponse: """ - Check whether a user has access to a specific resource, and return their access - level. + Checks whether a user has access to a company, product, or experience the caller + can reach. Args: extra_headers: Send extra headers @@ -477,6 +504,58 @@ async def check_access( cast_to=UserCheckAccessResponse, ) + async def update_me( + self, + *, + account_id: str | Omit = omit, + bio: str | Omit = omit, + name: str | Omit = omit, + profile_picture: user_update_me_params.ProfilePicture | Omit = omit, + username: 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, + ) -> User: + """ + Updates the authenticated user's global profile, or their profile override for + an account when account_id is given. Not available to API keys. + + Args: + account_id: When set, updates the authenticated user's profile override for this account + instead of their global profile. + + 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._patch( + "/users/me", + body=await async_maybe_transform( + { + "bio": bio, + "name": name, + "profile_picture": profile_picture, + "username": username, + }, + user_update_me_params.UserUpdateMeParams, + ), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=await async_maybe_transform({"account_id": account_id}, user_update_me_params.UserUpdateMeParams), + ), + cast_to=User, + ) + class UsersResourceWithRawResponse: def __init__(self, users: UsersResource) -> None: @@ -494,6 +573,9 @@ def __init__(self, users: UsersResource) -> None: self.check_access = to_raw_response_wrapper( users.check_access, ) + self.update_me = to_raw_response_wrapper( + users.update_me, + ) class AsyncUsersResourceWithRawResponse: @@ -512,6 +594,9 @@ def __init__(self, users: AsyncUsersResource) -> None: self.check_access = async_to_raw_response_wrapper( users.check_access, ) + self.update_me = async_to_raw_response_wrapper( + users.update_me, + ) class UsersResourceWithStreamingResponse: @@ -530,6 +615,9 @@ def __init__(self, users: UsersResource) -> None: self.check_access = to_streamed_response_wrapper( users.check_access, ) + self.update_me = to_streamed_response_wrapper( + users.update_me, + ) class AsyncUsersResourceWithStreamingResponse: @@ -548,3 +636,6 @@ def __init__(self, users: AsyncUsersResource) -> None: self.check_access = async_to_streamed_response_wrapper( users.check_access, ) + self.update_me = async_to_streamed_response_wrapper( + users.update_me, + ) diff --git a/src/whop_sdk/types/__init__.py b/src/whop_sdk/types/__init__.py index 23a936a3..9e70ccd3 100644 --- a/src/whop_sdk/types/__init__.py +++ b/src/whop_sdk/types/__init__.py @@ -138,7 +138,6 @@ from .refund_list_params import RefundListParams as RefundListParams from .review_list_params import ReviewListParams as ReviewListParams from .swap_create_params import SwapCreateParams as SwapCreateParams -from .user_list_response import UserListResponse as UserListResponse from .user_update_params import UserUpdateParams as UserUpdateParams from .wallet_send_params import WalletSendParams as WalletSendParams from .account_list_params import AccountListParams as AccountListParams @@ -212,6 +211,7 @@ from .product_update_params import ProductUpdateParams as ProductUpdateParams from .refund_reference_type import RefundReferenceType as RefundReferenceType from .topup_create_response import TopupCreateResponse as TopupCreateResponse +from .user_update_me_params import UserUpdateMeParams as UserUpdateMeParams from .wallet_balance_params import WalletBalanceParams as WalletBalanceParams from .webhook_create_params import WebhookCreateParams as WebhookCreateParams from .webhook_list_response import WebhookListResponse as WebhookListResponse diff --git a/src/whop_sdk/types/user.py b/src/whop_sdk/types/user.py index 4e52982f..9e5491e4 100644 --- a/src/whop_sdk/types/user.py +++ b/src/whop_sdk/types/user.py @@ -1,49 +1,27 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from typing import Optional -from datetime import datetime from .._models import BaseModel -__all__ = ["User", "ProfilePicture"] - - -class ProfilePicture(BaseModel): - """The user's profile picture attachment with URL, content type, and file metadata. - - Null if using a legacy profile picture. - """ - - url: Optional[str] = None - """A pre-optimized URL for rendering this attachment on the client. - - This should be used for displaying attachments in apps. - """ +__all__ = ["User"] class User(BaseModel): - """A user account on Whop. - - Contains profile information, identity details, and social connections. - """ - id: str - """The unique identifier for the user.""" + """The ID of the user, which will look like user\\__******\\********""" bio: Optional[str] = None - """A short biography written by the user, displayed on their public profile.""" + """The user's biography""" - created_at: datetime - """The datetime the user was created.""" + created_at: str + """When the user was created, as an ISO 8601 timestamp""" name: Optional[str] = None - """The user's display name shown on their public profile.""" - - profile_picture: Optional[ProfilePicture] = None - """The user's profile picture attachment with URL, content type, and file metadata. + """The user's display name""" - Null if using a legacy profile picture. - """ + profile_picture: Optional[object] = None + """The user's profile picture, an object with a url""" username: str - """The user's unique username shown on their public profile.""" + """The user's unique username""" diff --git a/src/whop_sdk/types/user_check_access_response.py b/src/whop_sdk/types/user_check_access_response.py index 270d822a..ad8fb2fe 100644 --- a/src/whop_sdk/types/user_check_access_response.py +++ b/src/whop_sdk/types/user_check_access_response.py @@ -1,16 +1,13 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. +from typing_extensions import Literal + from .._models import BaseModel -from .shared.access_level import AccessLevel __all__ = ["UserCheckAccessResponse"] class UserCheckAccessResponse(BaseModel): - """The result of a has access check for the developer API""" - - access_level: AccessLevel - """The permission level of the user""" + access_level: Literal["no_access", "admin", "customer"] has_access: bool - """Whether the user has access to the resource""" diff --git a/src/whop_sdk/types/user_list_params.py b/src/whop_sdk/types/user_list_params.py index bce47d6c..14d3e4fa 100644 --- a/src/whop_sdk/types/user_list_params.py +++ b/src/whop_sdk/types/user_list_params.py @@ -2,24 +2,23 @@ from __future__ import annotations -from typing import Optional from typing_extensions import TypedDict __all__ = ["UserListParams"] class UserListParams(TypedDict, total=False): - after: Optional[str] - """Returns the elements in the list that come after the specified cursor.""" + after: str + """A cursor; returns users after this position.""" - before: Optional[str] - """Returns the elements in the list that come before the specified cursor.""" + before: str + """A cursor; returns users before this position.""" - first: Optional[int] - """Returns the first _n_ elements from the list.""" + first: int + """The number of users to return (max 50).""" - last: Optional[int] - """Returns the last _n_ elements from the list.""" + last: int + """The number of users to return from the end of the range.""" - query: Optional[str] - """Search term to filter by name or username.""" + query: str + """A search term to filter users by name or username.""" diff --git a/src/whop_sdk/types/user_list_response.py b/src/whop_sdk/types/user_list_response.py deleted file mode 100644 index 1621e20c..00000000 --- a/src/whop_sdk/types/user_list_response.py +++ /dev/null @@ -1,49 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from typing import Optional -from datetime import datetime - -from .._models import BaseModel - -__all__ = ["UserListResponse", "ProfilePicture"] - - -class ProfilePicture(BaseModel): - """The user's profile picture attachment with URL, content type, and file metadata. - - Null if using a legacy profile picture. - """ - - url: Optional[str] = None - """A pre-optimized URL for rendering this attachment on the client. - - This should be used for displaying attachments in apps. - """ - - -class UserListResponse(BaseModel): - """A user account on Whop. - - Contains profile information, identity details, and social connections. - """ - - id: str - """The unique identifier for the user.""" - - bio: Optional[str] = None - """A short biography written by the user, displayed on their public profile.""" - - created_at: datetime - """The datetime the user was created.""" - - name: Optional[str] = None - """The user's display name shown on their public profile.""" - - profile_picture: Optional[ProfilePicture] = None - """The user's profile picture attachment with URL, content type, and file metadata. - - Null if using a legacy profile picture. - """ - - username: str - """The user's unique username shown on their public profile.""" diff --git a/src/whop_sdk/types/user_retrieve_params.py b/src/whop_sdk/types/user_retrieve_params.py index 8534761b..afb9ca68 100644 --- a/src/whop_sdk/types/user_retrieve_params.py +++ b/src/whop_sdk/types/user_retrieve_params.py @@ -2,15 +2,14 @@ from __future__ import annotations -from typing import Optional from typing_extensions import TypedDict __all__ = ["UserRetrieveParams"] class UserRetrieveParams(TypedDict, total=False): - company_id: Optional[str] + account_id: str """ - When provided, returns the user's company-specific profile overrides (name, - profile picture) instead of their global profile. + When set, returns the user's account-specific profile overrides for this + account. """ diff --git a/src/whop_sdk/types/user_update_me_params.py b/src/whop_sdk/types/user_update_me_params.py new file mode 100644 index 00000000..63a6a725 --- /dev/null +++ b/src/whop_sdk/types/user_update_me_params.py @@ -0,0 +1,29 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import TypedDict + +__all__ = ["UserUpdateMeParams", "ProfilePicture"] + + +class UserUpdateMeParams(TypedDict, total=False): + account_id: str + """ + When set, updates the authenticated user's profile override for this account + instead of their global profile. + """ + + bio: str + + name: str + + profile_picture: ProfilePicture + + username: str + + +class ProfilePicture(TypedDict, total=False): + id: str + + direct_upload_id: str diff --git a/src/whop_sdk/types/user_update_params.py b/src/whop_sdk/types/user_update_params.py index a1c8572a..c957a3c6 100644 --- a/src/whop_sdk/types/user_update_params.py +++ b/src/whop_sdk/types/user_update_params.py @@ -2,38 +2,25 @@ from __future__ import annotations -from typing import Optional -from typing_extensions import Required, TypedDict +from typing_extensions import TypedDict __all__ = ["UserUpdateParams", "ProfilePicture"] class UserUpdateParams(TypedDict, total=False): - bio: Optional[str] - """A short biography displayed on the user's public profile.""" + account_id: str + """The account whose profile override to update. Required for API key callers.""" - company_id: Optional[str] - """ - When provided, updates the user's profile overrides for this company instead of - the global profile. Pass name and profile_picture to set overrides, or null to - clear them. - """ + bio: str - name: Optional[str] - """The user's display name shown on their public profile. Maximum 100 characters.""" + name: str - profile_picture: Optional[ProfilePicture] - """The user's profile picture image attachment.""" + profile_picture: ProfilePicture - username: Optional[str] - """The user's unique username. - - Alphanumeric characters and hyphens only. Maximum 42 characters. - """ + username: str class ProfilePicture(TypedDict, total=False): - """The user's profile picture image attachment.""" + id: str - id: Required[str] - """The ID of an existing file object.""" + direct_upload_id: str diff --git a/tests/api_resources/test_users.py b/tests/api_resources/test_users.py index ee565217..9b0014ac 100644 --- a/tests/api_resources/test_users.py +++ b/tests/api_resources/test_users.py @@ -11,7 +11,6 @@ from tests.utils import assert_matches_type from whop_sdk.types import ( User, - UserListResponse, UserCheckAccessResponse, ) from whop_sdk.pagination import SyncCursorPage, AsyncCursorPage @@ -26,7 +25,7 @@ class TestUsers: @parametrize def test_method_retrieve(self, client: Whop) -> None: user = client.users.retrieve( - id="user_xxxxxxxxxxxxx", + id="id", ) assert_matches_type(User, user, path=["response"]) @@ -34,8 +33,8 @@ def test_method_retrieve(self, client: Whop) -> None: @parametrize def test_method_retrieve_with_all_params(self, client: Whop) -> None: user = client.users.retrieve( - id="user_xxxxxxxxxxxxx", - company_id="biz_xxxxxxxxxxxxxx", + id="id", + account_id="account_id", ) assert_matches_type(User, user, path=["response"]) @@ -43,7 +42,7 @@ def test_method_retrieve_with_all_params(self, client: Whop) -> None: @parametrize def test_raw_response_retrieve(self, client: Whop) -> None: response = client.users.with_raw_response.retrieve( - id="user_xxxxxxxxxxxxx", + id="id", ) assert response.is_closed is True @@ -55,7 +54,7 @@ def test_raw_response_retrieve(self, client: Whop) -> None: @parametrize def test_streaming_response_retrieve(self, client: Whop) -> None: with client.users.with_streaming_response.retrieve( - id="user_xxxxxxxxxxxxx", + id="id", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -77,7 +76,7 @@ def test_path_params_retrieve(self, client: Whop) -> None: @parametrize def test_method_update(self, client: Whop) -> None: user = client.users.update( - id="user_xxxxxxxxxxxxx", + id="id", ) assert_matches_type(User, user, path=["response"]) @@ -85,11 +84,14 @@ def test_method_update(self, client: Whop) -> None: @parametrize def test_method_update_with_all_params(self, client: Whop) -> None: user = client.users.update( - id="user_xxxxxxxxxxxxx", + id="id", + account_id="account_id", bio="bio", - company_id="biz_xxxxxxxxxxxxxx", name="name", - profile_picture={"id": "id"}, + profile_picture={ + "id": "id", + "direct_upload_id": "direct_upload_id", + }, username="username", ) assert_matches_type(User, user, path=["response"]) @@ -98,7 +100,7 @@ def test_method_update_with_all_params(self, client: Whop) -> None: @parametrize def test_raw_response_update(self, client: Whop) -> None: response = client.users.with_raw_response.update( - id="user_xxxxxxxxxxxxx", + id="id", ) assert response.is_closed is True @@ -110,7 +112,7 @@ def test_raw_response_update(self, client: Whop) -> None: @parametrize def test_streaming_response_update(self, client: Whop) -> None: with client.users.with_streaming_response.update( - id="user_xxxxxxxxxxxxx", + id="id", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -132,7 +134,7 @@ def test_path_params_update(self, client: Whop) -> None: @parametrize def test_method_list(self, client: Whop) -> None: user = client.users.list() - assert_matches_type(SyncCursorPage[UserListResponse], user, path=["response"]) + assert_matches_type(SyncCursorPage[User], user, path=["response"]) @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize @@ -140,11 +142,11 @@ def test_method_list_with_all_params(self, client: Whop) -> None: user = client.users.list( after="after", before="before", - first=42, - last=42, + first=0, + last=0, query="query", ) - assert_matches_type(SyncCursorPage[UserListResponse], user, path=["response"]) + assert_matches_type(SyncCursorPage[User], user, path=["response"]) @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize @@ -154,7 +156,7 @@ def test_raw_response_list(self, client: Whop) -> None: assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" user = response.parse() - assert_matches_type(SyncCursorPage[UserListResponse], user, path=["response"]) + assert_matches_type(SyncCursorPage[User], user, path=["response"]) @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize @@ -164,7 +166,7 @@ def test_streaming_response_list(self, client: Whop) -> None: assert response.http_request.headers.get("X-Stainless-Lang") == "python" user = response.parse() - assert_matches_type(SyncCursorPage[UserListResponse], user, path=["response"]) + assert_matches_type(SyncCursorPage[User], user, path=["response"]) assert cast(Any, response.is_closed) is True @@ -173,7 +175,7 @@ def test_streaming_response_list(self, client: Whop) -> None: def test_method_check_access(self, client: Whop) -> None: user = client.users.check_access( resource_id="resource_id", - id="user_xxxxxxxxxxxxx", + id="id", ) assert_matches_type(UserCheckAccessResponse, user, path=["response"]) @@ -182,7 +184,7 @@ def test_method_check_access(self, client: Whop) -> None: def test_raw_response_check_access(self, client: Whop) -> None: response = client.users.with_raw_response.check_access( resource_id="resource_id", - id="user_xxxxxxxxxxxxx", + id="id", ) assert response.is_closed is True @@ -195,7 +197,7 @@ def test_raw_response_check_access(self, client: Whop) -> None: def test_streaming_response_check_access(self, client: Whop) -> None: with client.users.with_streaming_response.check_access( resource_id="resource_id", - id="user_xxxxxxxxxxxxx", + id="id", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -217,9 +219,52 @@ def test_path_params_check_access(self, client: Whop) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `resource_id` but received ''"): client.users.with_raw_response.check_access( resource_id="", - id="user_xxxxxxxxxxxxx", + id="id", ) + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_update_me(self, client: Whop) -> None: + user = client.users.update_me() + assert_matches_type(User, user, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_update_me_with_all_params(self, client: Whop) -> None: + user = client.users.update_me( + account_id="account_id", + bio="bio", + name="name", + profile_picture={ + "id": "id", + "direct_upload_id": "direct_upload_id", + }, + username="username", + ) + assert_matches_type(User, user, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_raw_response_update_me(self, client: Whop) -> None: + response = client.users.with_raw_response.update_me() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + user = response.parse() + assert_matches_type(User, user, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_streaming_response_update_me(self, client: Whop) -> None: + with client.users.with_streaming_response.update_me() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + user = response.parse() + assert_matches_type(User, user, path=["response"]) + + assert cast(Any, response.is_closed) is True + class TestAsyncUsers: parametrize = pytest.mark.parametrize( @@ -230,7 +275,7 @@ class TestAsyncUsers: @parametrize async def test_method_retrieve(self, async_client: AsyncWhop) -> None: user = await async_client.users.retrieve( - id="user_xxxxxxxxxxxxx", + id="id", ) assert_matches_type(User, user, path=["response"]) @@ -238,8 +283,8 @@ async def test_method_retrieve(self, async_client: AsyncWhop) -> None: @parametrize async def test_method_retrieve_with_all_params(self, async_client: AsyncWhop) -> None: user = await async_client.users.retrieve( - id="user_xxxxxxxxxxxxx", - company_id="biz_xxxxxxxxxxxxxx", + id="id", + account_id="account_id", ) assert_matches_type(User, user, path=["response"]) @@ -247,7 +292,7 @@ async def test_method_retrieve_with_all_params(self, async_client: AsyncWhop) -> @parametrize async def test_raw_response_retrieve(self, async_client: AsyncWhop) -> None: response = await async_client.users.with_raw_response.retrieve( - id="user_xxxxxxxxxxxxx", + id="id", ) assert response.is_closed is True @@ -259,7 +304,7 @@ async def test_raw_response_retrieve(self, async_client: AsyncWhop) -> None: @parametrize async def test_streaming_response_retrieve(self, async_client: AsyncWhop) -> None: async with async_client.users.with_streaming_response.retrieve( - id="user_xxxxxxxxxxxxx", + id="id", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -281,7 +326,7 @@ async def test_path_params_retrieve(self, async_client: AsyncWhop) -> None: @parametrize async def test_method_update(self, async_client: AsyncWhop) -> None: user = await async_client.users.update( - id="user_xxxxxxxxxxxxx", + id="id", ) assert_matches_type(User, user, path=["response"]) @@ -289,11 +334,14 @@ async def test_method_update(self, async_client: AsyncWhop) -> None: @parametrize async def test_method_update_with_all_params(self, async_client: AsyncWhop) -> None: user = await async_client.users.update( - id="user_xxxxxxxxxxxxx", + id="id", + account_id="account_id", bio="bio", - company_id="biz_xxxxxxxxxxxxxx", name="name", - profile_picture={"id": "id"}, + profile_picture={ + "id": "id", + "direct_upload_id": "direct_upload_id", + }, username="username", ) assert_matches_type(User, user, path=["response"]) @@ -302,7 +350,7 @@ async def test_method_update_with_all_params(self, async_client: AsyncWhop) -> N @parametrize async def test_raw_response_update(self, async_client: AsyncWhop) -> None: response = await async_client.users.with_raw_response.update( - id="user_xxxxxxxxxxxxx", + id="id", ) assert response.is_closed is True @@ -314,7 +362,7 @@ async def test_raw_response_update(self, async_client: AsyncWhop) -> None: @parametrize async def test_streaming_response_update(self, async_client: AsyncWhop) -> None: async with async_client.users.with_streaming_response.update( - id="user_xxxxxxxxxxxxx", + id="id", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -336,7 +384,7 @@ async def test_path_params_update(self, async_client: AsyncWhop) -> None: @parametrize async def test_method_list(self, async_client: AsyncWhop) -> None: user = await async_client.users.list() - assert_matches_type(AsyncCursorPage[UserListResponse], user, path=["response"]) + assert_matches_type(AsyncCursorPage[User], user, path=["response"]) @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize @@ -344,11 +392,11 @@ async def test_method_list_with_all_params(self, async_client: AsyncWhop) -> Non user = await async_client.users.list( after="after", before="before", - first=42, - last=42, + first=0, + last=0, query="query", ) - assert_matches_type(AsyncCursorPage[UserListResponse], user, path=["response"]) + assert_matches_type(AsyncCursorPage[User], user, path=["response"]) @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize @@ -358,7 +406,7 @@ async def test_raw_response_list(self, async_client: AsyncWhop) -> None: assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" user = await response.parse() - assert_matches_type(AsyncCursorPage[UserListResponse], user, path=["response"]) + assert_matches_type(AsyncCursorPage[User], user, path=["response"]) @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize @@ -368,7 +416,7 @@ async def test_streaming_response_list(self, async_client: AsyncWhop) -> None: assert response.http_request.headers.get("X-Stainless-Lang") == "python" user = await response.parse() - assert_matches_type(AsyncCursorPage[UserListResponse], user, path=["response"]) + assert_matches_type(AsyncCursorPage[User], user, path=["response"]) assert cast(Any, response.is_closed) is True @@ -377,7 +425,7 @@ async def test_streaming_response_list(self, async_client: AsyncWhop) -> None: async def test_method_check_access(self, async_client: AsyncWhop) -> None: user = await async_client.users.check_access( resource_id="resource_id", - id="user_xxxxxxxxxxxxx", + id="id", ) assert_matches_type(UserCheckAccessResponse, user, path=["response"]) @@ -386,7 +434,7 @@ async def test_method_check_access(self, async_client: AsyncWhop) -> None: async def test_raw_response_check_access(self, async_client: AsyncWhop) -> None: response = await async_client.users.with_raw_response.check_access( resource_id="resource_id", - id="user_xxxxxxxxxxxxx", + id="id", ) assert response.is_closed is True @@ -399,7 +447,7 @@ async def test_raw_response_check_access(self, async_client: AsyncWhop) -> None: async def test_streaming_response_check_access(self, async_client: AsyncWhop) -> None: async with async_client.users.with_streaming_response.check_access( resource_id="resource_id", - id="user_xxxxxxxxxxxxx", + id="id", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -421,5 +469,48 @@ async def test_path_params_check_access(self, async_client: AsyncWhop) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `resource_id` but received ''"): await async_client.users.with_raw_response.check_access( resource_id="", - id="user_xxxxxxxxxxxxx", + id="id", ) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_update_me(self, async_client: AsyncWhop) -> None: + user = await async_client.users.update_me() + assert_matches_type(User, user, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_update_me_with_all_params(self, async_client: AsyncWhop) -> None: + user = await async_client.users.update_me( + account_id="account_id", + bio="bio", + name="name", + profile_picture={ + "id": "id", + "direct_upload_id": "direct_upload_id", + }, + username="username", + ) + assert_matches_type(User, user, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_raw_response_update_me(self, async_client: AsyncWhop) -> None: + response = await async_client.users.with_raw_response.update_me() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + user = await response.parse() + assert_matches_type(User, user, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_streaming_response_update_me(self, async_client: AsyncWhop) -> None: + async with async_client.users.with_streaming_response.update_me() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + user = await response.parse() + assert_matches_type(User, user, path=["response"]) + + assert cast(Any, response.is_closed) is True From 7e715848daae3d9fcb913fcee294e05f26a84d6b Mon Sep 17 00:00:00 2001 From: stlc-bot Date: Wed, 10 Jun 2026 04:16:28 +0000 Subject: [PATCH 013/109] feat(pixel): add new conversion events and update dev panel Stainless-Generated-From: da46306b32cec9e722d8bb32a688ce45b370ad4b --- src/whop_sdk/resources/conversions.py | 8 ++++---- src/whop_sdk/types/conversion_create_params.py | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/whop_sdk/resources/conversions.py b/src/whop_sdk/resources/conversions.py index 8f5cf755..4f70d949 100644 --- a/src/whop_sdk/resources/conversions.py +++ b/src/whop_sdk/resources/conversions.py @@ -58,10 +58,10 @@ def create( "contact", "complete_registration", "schedule", + "view_content", + "add_to_cart", "custom", "page", - "leave", - "identify", ], action_source: Optional[ Literal[ @@ -216,10 +216,10 @@ async def create( "contact", "complete_registration", "schedule", + "view_content", + "add_to_cart", "custom", "page", - "leave", - "identify", ], action_source: Optional[ Literal[ diff --git a/src/whop_sdk/types/conversion_create_params.py b/src/whop_sdk/types/conversion_create_params.py index eb09b960..091a72f0 100644 --- a/src/whop_sdk/types/conversion_create_params.py +++ b/src/whop_sdk/types/conversion_create_params.py @@ -23,10 +23,10 @@ class ConversionCreateParams(TypedDict, total=False): "contact", "complete_registration", "schedule", + "view_content", + "add_to_cart", "custom", "page", - "leave", - "identify", ] ] """The type of event.""" From d2a0f529ee6860cf71dfac48ce07ff3949b6c620 Mon Sep 17 00:00:00 2001 From: stlc-bot Date: Wed, 10 Jun 2026 04:25:16 +0000 Subject: [PATCH 014/109] Make /plans a native V1 API resource, unblocking new plan endpoints Stainless-Generated-From: 63d934aa8c0043e172cf87e054308d46e837ee8a --- src/whop_sdk/_client.py | 6 - src/whop_sdk/resources/plans.py | 342 ++++++++------------ src/whop_sdk/types/plan_create_params.py | 150 +++------ src/whop_sdk/types/plan_list_params.py | 45 ++- src/whop_sdk/types/plan_list_response.py | 234 ++++---------- src/whop_sdk/types/plan_update_params.py | 144 +++------ src/whop_sdk/types/shared/plan.py | 377 ++++++++++------------- tests/api_resources/test_plans.py | 291 +++++++++-------- 8 files changed, 593 insertions(+), 996 deletions(-) diff --git a/src/whop_sdk/_client.py b/src/whop_sdk/_client.py index 1259e198..a12ecbe2 100644 --- a/src/whop_sdk/_client.py +++ b/src/whop_sdk/_client.py @@ -305,7 +305,6 @@ def webhooks(self) -> WebhooksResource: @cached_property def plans(self) -> PlansResource: - """Plans""" from .resources.plans import PlansResource return PlansResource(self) @@ -954,7 +953,6 @@ def webhooks(self) -> AsyncWebhooksResource: @cached_property def plans(self) -> AsyncPlansResource: - """Plans""" from .resources.plans import AsyncPlansResource return AsyncPlansResource(self) @@ -1523,7 +1521,6 @@ def webhooks(self) -> webhooks.WebhooksResourceWithRawResponse: @cached_property def plans(self) -> plans.PlansResourceWithRawResponse: - """Plans""" from .resources.plans import PlansResourceWithRawResponse return PlansResourceWithRawResponse(self._client.plans) @@ -1974,7 +1971,6 @@ def webhooks(self) -> webhooks.AsyncWebhooksResourceWithRawResponse: @cached_property def plans(self) -> plans.AsyncPlansResourceWithRawResponse: - """Plans""" from .resources.plans import AsyncPlansResourceWithRawResponse return AsyncPlansResourceWithRawResponse(self._client.plans) @@ -2427,7 +2423,6 @@ def webhooks(self) -> webhooks.WebhooksResourceWithStreamingResponse: @cached_property def plans(self) -> plans.PlansResourceWithStreamingResponse: - """Plans""" from .resources.plans import PlansResourceWithStreamingResponse return PlansResourceWithStreamingResponse(self._client.plans) @@ -2880,7 +2875,6 @@ def webhooks(self) -> webhooks.AsyncWebhooksResourceWithStreamingResponse: @cached_property def plans(self) -> plans.AsyncPlansResourceWithStreamingResponse: - """Plans""" from .resources.plans import AsyncPlansResourceWithStreamingResponse return AsyncPlansResourceWithStreamingResponse(self._client.plans) diff --git a/src/whop_sdk/resources/plans.py b/src/whop_sdk/resources/plans.py index e351bc6f..d11fe6d5 100644 --- a/src/whop_sdk/resources/plans.py +++ b/src/whop_sdk/resources/plans.py @@ -2,8 +2,7 @@ from __future__ import annotations -from typing import Dict, List, Union, Iterable, Optional -from datetime import datetime +from typing import Iterable, Optional from typing_extensions import Literal import httpx @@ -22,22 +21,13 @@ from ..pagination import SyncCursorPage, AsyncCursorPage from .._base_client import AsyncPaginator, make_request_options from ..types.shared.plan import Plan -from ..types.shared.currency import Currency -from ..types.shared.tax_type import TaxType -from ..types.shared.direction import Direction -from ..types.shared.plan_type import PlanType -from ..types.shared.visibility import Visibility from ..types.plan_list_response import PlanListResponse from ..types.plan_delete_response import PlanDeleteResponse -from ..types.shared.release_method import ReleaseMethod -from ..types.shared.visibility_filter import VisibilityFilter __all__ = ["PlansResource", "AsyncPlansResource"] class PlansResource(SyncAPIResource): - """Plans""" - @cached_property def with_raw_response(self) -> PlansResourceWithRawResponse: """ @@ -60,12 +50,12 @@ def with_streaming_response(self) -> PlansResourceWithStreamingResponse: def create( self, *, - 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, + checkout_styling: Optional[object] | Omit = omit, + company_id: str | Omit = omit, + currency: str | Omit = omit, custom_fields: Optional[Iterable[plan_create_params.CustomField]] | Omit = omit, description: Optional[str] | Omit = omit, expiration_days: Optional[int] | Omit = omit, @@ -73,19 +63,19 @@ def create( initial_price: Optional[float] | Omit = omit, internal_notes: Optional[str] | Omit = omit, legacy_payment_method_controls: Optional[bool] | Omit = omit, - metadata: Optional[Dict[str, object]] | Omit = omit, - override_tax_type: Optional[TaxType] | Omit = omit, + metadata: Optional[object] | Omit = omit, + override_tax_type: str | Omit = omit, payment_method_configuration: Optional[plan_create_params.PaymentMethodConfiguration] | Omit = omit, - plan_type: Optional[PlanType] | Omit = omit, - release_method: Optional[ReleaseMethod] | Omit = omit, + plan_type: str | Omit = omit, + release_method: str | Omit = omit, renewal_price: Optional[float] | Omit = omit, split_pay_required_payments: Optional[int] | Omit = omit, stock: Optional[int] | Omit = omit, - three_ds_level: Optional[Literal["mandate_challenge", "frictionless"]] | Omit = omit, + three_ds_level: Literal["mandate_challenge", "frictionless"] | Omit = omit, title: Optional[str] | Omit = omit, trial_period_days: Optional[int] | Omit = omit, unlimited_stock: Optional[bool] | Omit = omit, - visibility: Optional[Visibility] | Omit = omit, + visibility: 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, @@ -98,15 +88,7 @@ def create( The plan defines the billing interval, price, and availability for customers. - Required permissions: - - - `plan:create` - - `access_pass:basic:read` - - `plan:basic:read` - Args: - company_id: The unique identifier of the company to create this plan for. - 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. @@ -114,60 +96,58 @@ def create( billing_period: The number of days between recurring charges. For example, 30 for monthly or 365 for yearly. - checkout_styling: Checkout styling overrides for this plan. Pass null to inherit from the company - default. + checkout_styling: Checkout styling overrides for this plan. - currency: The available currencies on the platform + company_id: The unique identifier of the company to create this plan for. Defaults to the + caller's company. + + currency: The three-letter ISO currency code for the plan's pricing. Defaults to USD. custom_fields: An array of custom field definitions to collect from customers at checkout. + Omitting this field clears existing custom fields. description: A text description of the plan displayed to customers on the product page. - expiration_days: The number of days until the membership expires and access is revoked. Used for - expiration-based plans. + expiration_days: The number of days until the membership expires and access is revoked. image: An image displayed on the product page to represent this plan. - initial_price: The amount charged on the first purchase. For one-time plans, this is the full - price. For recurring plans, this is an additional charge on top of the renewal - price. Provided in the plan's currency (e.g., 10.43 for $10.43). + initial_price: The amount charged on the first purchase, in the plan's currency (e.g., 10.43 + for $10.43). internal_notes: Private notes visible only to the business owner. Not shown to customers. legacy_payment_method_controls: Whether this plan uses legacy payment method controls. metadata: Custom key-value pairs to store on the plan. Included in webhook payloads for - payment and membership events. Max 50 keys, 500 chars per key, 5000 chars per - value. + payment and membership events. - override_tax_type: Whether or not the tax is included in a plan's price (or if it hasn't been set - up) + override_tax_type: Override the default tax classification for this specific plan. payment_method_configuration: Explicit payment method configuration for the plan. When not provided, the company's defaults apply. - plan_type: The type of plan that can be attached to a product + plan_type: The billing type of the plan, such as one_time or renewal. - release_method: The methods of how a plan can be released. + release_method: The method used to sell this plan (e.g., buy_now, waitlist). - renewal_price: The amount charged each billing period for recurring plans. Provided in the - plan's currency (e.g., 10.43 for $10.43). + renewal_price: The amount charged each billing period for recurring plans, in the plan's + currency. split_pay_required_payments: The number of installment payments required before the subscription pauses. stock: The maximum number of units available for purchase. Ignored when unlimited_stock is true. - three_ds_level: The 3D Secure behavior for a plan. + three_ds_level: The 3D Secure behavior for this plan. Send null to inherit the account default. title: The display name of the plan shown to customers on the product page. trial_period_days: The number of free trial days before the first charge on a recurring plan. unlimited_stock: Whether the plan has unlimited stock. When true, the stock field is ignored. - Defaults to true. - visibility: Visibility of a resource + visibility: Whether the plan is visible to customers or hidden from public view. extra_headers: Send extra headers @@ -181,11 +161,11 @@ def create( "/plans", body=maybe_transform( { - "company_id": company_id, "product_id": product_id, "adaptive_pricing_enabled": adaptive_pricing_enabled, "billing_period": billing_period, "checkout_styling": checkout_styling, + "company_id": company_id, "currency": currency, "custom_fields": custom_fields, "description": description, @@ -230,10 +210,6 @@ def retrieve( """ Retrieves the details of an existing plan. - Required permissions: - - - `plan:basic:read` - Args: extra_headers: Send extra headers @@ -259,8 +235,8 @@ def update( *, 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, + checkout_styling: Optional[object] | Omit = omit, + currency: str | Omit = omit, custom_fields: Optional[Iterable[plan_update_params.CustomField]] | Omit = omit, description: Optional[str] | Omit = omit, expiration_days: Optional[int] | Omit = omit, @@ -268,19 +244,19 @@ def update( initial_price: Optional[float] | Omit = omit, internal_notes: Optional[str] | Omit = omit, legacy_payment_method_controls: Optional[bool] | Omit = omit, - metadata: Optional[Dict[str, object]] | Omit = omit, + metadata: Optional[object] | Omit = omit, offer_cancel_discount: Optional[bool] | Omit = omit, - override_tax_type: Optional[TaxType] | Omit = omit, + override_tax_type: str | Omit = omit, payment_method_configuration: Optional[plan_update_params.PaymentMethodConfiguration] | Omit = omit, renewal_price: Optional[float] | Omit = omit, stock: Optional[int] | Omit = omit, strike_through_initial_price: Optional[float] | Omit = omit, strike_through_renewal_price: Optional[float] | Omit = omit, - three_ds_level: Optional[Literal["mandate_challenge", "frictionless"]] | Omit = omit, + three_ds_level: Literal["mandate_challenge", "frictionless"] | Omit = omit, title: Optional[str] | Omit = omit, trial_period_days: Optional[int] | Omit = omit, unlimited_stock: Optional[bool] | Omit = omit, - visibility: Optional[Visibility] | Omit = omit, + visibility: 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, @@ -292,64 +268,53 @@ def update( Update a plan's pricing, billing interval, visibility, stock, and other settings. - Required permissions: - - - `plan:update` - - `access_pass:basic:read` - - `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. - checkout_styling: Checkout styling overrides for this plan. Pass null to remove all overrides and - inherit from the company default. + checkout_styling: Checkout styling overrides for this plan. - currency: The available currencies on the platform + currency: The three-letter ISO currency code for the plan's pricing. Defaults to USD. custom_fields: An array of custom field definitions to collect from customers at checkout. + Omitting this field clears existing custom fields. description: A text description of the plan displayed to customers on the product page. - expiration_days: The number of days until the membership expires and access is revoked. For - example, 365 for one-year access. + expiration_days: The number of days until the membership expires and access is revoked. image: An image displayed on the product page to represent this plan. - initial_price: The amount charged on the first purchase. Provided in the plan's currency (e.g., - 10.43 for $10.43). + initial_price: The amount charged on the first purchase, in the plan's currency (e.g., 10.43 + for $10.43). internal_notes: Private notes visible only to the business owner. Not shown to customers. legacy_payment_method_controls: Whether this plan uses legacy payment method controls. metadata: Custom key-value pairs to store on the plan. Included in webhook payloads for - payment and membership events. Max 50 keys, 500 chars per key, 5000 chars per - value. + payment and membership events. offer_cancel_discount: Whether to offer a retention discount when a customer attempts to cancel. - override_tax_type: Whether or not the tax is included in a plan's price (or if it hasn't been set - up) + override_tax_type: Override the default tax classification for this specific plan. - payment_method_configuration: Explicit payment method configuration for the plan. Sending null removes any - custom configuration. + payment_method_configuration: Explicit payment method configuration for the plan. When not provided, the + company's defaults apply. - renewal_price: The amount charged each billing period for recurring plans. Provided in the - plan's currency (e.g., 10.43 for $10.43). + renewal_price: The amount charged each billing period for recurring plans, in the plan's + currency. stock: The maximum number of units available for purchase. Ignored when unlimited_stock is true. strike_through_initial_price: A comparison price displayed with a strikethrough for the initial price. - Provided in the plan's currency (e.g., 19.99 for $19.99). strike_through_renewal_price: A comparison price displayed with a strikethrough for the renewal price. - Provided in the plan's currency (e.g., 19.99 for $19.99). - three_ds_level: The 3D Secure behavior for a plan. + three_ds_level: The 3D Secure behavior for this plan. Send null to inherit the account default. title: The display name of the plan shown to customers on the product page. @@ -357,7 +322,7 @@ def update( unlimited_stock: Whether the plan has unlimited stock. When true, the stock field is ignored. - visibility: Visibility of a resource + visibility: Whether the plan is visible to customers or hidden from public view. extra_headers: Send extra headers @@ -410,19 +375,18 @@ 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, - direction: Optional[Direction] | Omit = omit, - first: Optional[int] | Omit = omit, - last: Optional[int] | Omit = omit, - order: Optional[Literal["id", "active_members_count", "created_at", "internal_notes", "expires_at"]] - | Omit = omit, - plan_types: Optional[List[PlanType]] | Omit = omit, - product_ids: Optional[SequenceNotStr[str]] | Omit = omit, - release_methods: Optional[List[ReleaseMethod]] | Omit = omit, - visibilities: Optional[List[VisibilityFilter]] | Omit = omit, + after: str | Omit = omit, + before: str | Omit = omit, + created_after: str | Omit = omit, + created_before: str | Omit = omit, + direction: Literal["asc", "desc"] | Omit = omit, + first: int | Omit = omit, + last: int | Omit = omit, + order: Literal["id", "active_members_count", "created_at", "internal_notes", "expires_at"] | Omit = omit, + plan_types: SequenceNotStr[str] | Omit = omit, + product_ids: SequenceNotStr[str] | Omit = omit, + release_methods: SequenceNotStr[str] | Omit = omit, + visibilities: 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, @@ -434,28 +398,24 @@ def list( Returns a paginated list of plans belonging to a company, with optional filtering by visibility, type, release method, and product. - Required permissions: - - - `plan:basic:read` - Args: company_id: The unique identifier of the company to list plans for. - after: Returns the elements in the list that come after the specified cursor. + after: A cursor; returns plans after this position. - before: Returns the elements in the list that come before the specified cursor. + before: A cursor; returns plans before this position. created_after: Only return plans created after this timestamp. created_before: Only return plans created before this timestamp. - direction: The direction of the sort. + direction: The sort direction for results. Defaults to descending. - first: Returns the first _n_ elements from the list. + first: The number of plans to return (default and max 100). - last: Returns the last _n_ elements from the list. + last: The number of plans to return from the end of the range. - order: The ways a relation of Plans can be ordered + order: The field to sort results by. Defaults to created_at. plan_types: Filter to only plans matching these billing types. @@ -519,10 +479,6 @@ def delete( Existing memberships on this plan will not be affected. - Required permissions: - - - `plan:delete` - Args: extra_headers: Send extra headers @@ -544,8 +500,6 @@ def delete( class AsyncPlansResource(AsyncAPIResource): - """Plans""" - @cached_property def with_raw_response(self) -> AsyncPlansResourceWithRawResponse: """ @@ -568,12 +522,12 @@ def with_streaming_response(self) -> AsyncPlansResourceWithStreamingResponse: async def create( self, *, - 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, + checkout_styling: Optional[object] | Omit = omit, + company_id: str | Omit = omit, + currency: str | Omit = omit, custom_fields: Optional[Iterable[plan_create_params.CustomField]] | Omit = omit, description: Optional[str] | Omit = omit, expiration_days: Optional[int] | Omit = omit, @@ -581,19 +535,19 @@ async def create( initial_price: Optional[float] | Omit = omit, internal_notes: Optional[str] | Omit = omit, legacy_payment_method_controls: Optional[bool] | Omit = omit, - metadata: Optional[Dict[str, object]] | Omit = omit, - override_tax_type: Optional[TaxType] | Omit = omit, + metadata: Optional[object] | Omit = omit, + override_tax_type: str | Omit = omit, payment_method_configuration: Optional[plan_create_params.PaymentMethodConfiguration] | Omit = omit, - plan_type: Optional[PlanType] | Omit = omit, - release_method: Optional[ReleaseMethod] | Omit = omit, + plan_type: str | Omit = omit, + release_method: str | Omit = omit, renewal_price: Optional[float] | Omit = omit, split_pay_required_payments: Optional[int] | Omit = omit, stock: Optional[int] | Omit = omit, - three_ds_level: Optional[Literal["mandate_challenge", "frictionless"]] | Omit = omit, + three_ds_level: Literal["mandate_challenge", "frictionless"] | Omit = omit, title: Optional[str] | Omit = omit, trial_period_days: Optional[int] | Omit = omit, unlimited_stock: Optional[bool] | Omit = omit, - visibility: Optional[Visibility] | Omit = omit, + visibility: 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, @@ -606,15 +560,7 @@ async def create( The plan defines the billing interval, price, and availability for customers. - Required permissions: - - - `plan:create` - - `access_pass:basic:read` - - `plan:basic:read` - Args: - company_id: The unique identifier of the company to create this plan for. - 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. @@ -622,60 +568,58 @@ async def create( billing_period: The number of days between recurring charges. For example, 30 for monthly or 365 for yearly. - checkout_styling: Checkout styling overrides for this plan. Pass null to inherit from the company - default. + checkout_styling: Checkout styling overrides for this plan. - currency: The available currencies on the platform + company_id: The unique identifier of the company to create this plan for. Defaults to the + caller's company. + + currency: The three-letter ISO currency code for the plan's pricing. Defaults to USD. custom_fields: An array of custom field definitions to collect from customers at checkout. + Omitting this field clears existing custom fields. description: A text description of the plan displayed to customers on the product page. - expiration_days: The number of days until the membership expires and access is revoked. Used for - expiration-based plans. + expiration_days: The number of days until the membership expires and access is revoked. image: An image displayed on the product page to represent this plan. - initial_price: The amount charged on the first purchase. For one-time plans, this is the full - price. For recurring plans, this is an additional charge on top of the renewal - price. Provided in the plan's currency (e.g., 10.43 for $10.43). + initial_price: The amount charged on the first purchase, in the plan's currency (e.g., 10.43 + for $10.43). internal_notes: Private notes visible only to the business owner. Not shown to customers. legacy_payment_method_controls: Whether this plan uses legacy payment method controls. metadata: Custom key-value pairs to store on the plan. Included in webhook payloads for - payment and membership events. Max 50 keys, 500 chars per key, 5000 chars per - value. + payment and membership events. - override_tax_type: Whether or not the tax is included in a plan's price (or if it hasn't been set - up) + override_tax_type: Override the default tax classification for this specific plan. payment_method_configuration: Explicit payment method configuration for the plan. When not provided, the company's defaults apply. - plan_type: The type of plan that can be attached to a product + plan_type: The billing type of the plan, such as one_time or renewal. - release_method: The methods of how a plan can be released. + release_method: The method used to sell this plan (e.g., buy_now, waitlist). - renewal_price: The amount charged each billing period for recurring plans. Provided in the - plan's currency (e.g., 10.43 for $10.43). + renewal_price: The amount charged each billing period for recurring plans, in the plan's + currency. split_pay_required_payments: The number of installment payments required before the subscription pauses. stock: The maximum number of units available for purchase. Ignored when unlimited_stock is true. - three_ds_level: The 3D Secure behavior for a plan. + three_ds_level: The 3D Secure behavior for this plan. Send null to inherit the account default. title: The display name of the plan shown to customers on the product page. trial_period_days: The number of free trial days before the first charge on a recurring plan. unlimited_stock: Whether the plan has unlimited stock. When true, the stock field is ignored. - Defaults to true. - visibility: Visibility of a resource + visibility: Whether the plan is visible to customers or hidden from public view. extra_headers: Send extra headers @@ -689,11 +633,11 @@ async def create( "/plans", body=await async_maybe_transform( { - "company_id": company_id, "product_id": product_id, "adaptive_pricing_enabled": adaptive_pricing_enabled, "billing_period": billing_period, "checkout_styling": checkout_styling, + "company_id": company_id, "currency": currency, "custom_fields": custom_fields, "description": description, @@ -738,10 +682,6 @@ async def retrieve( """ Retrieves the details of an existing plan. - Required permissions: - - - `plan:basic:read` - Args: extra_headers: Send extra headers @@ -767,8 +707,8 @@ async def update( *, 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, + checkout_styling: Optional[object] | Omit = omit, + currency: str | Omit = omit, custom_fields: Optional[Iterable[plan_update_params.CustomField]] | Omit = omit, description: Optional[str] | Omit = omit, expiration_days: Optional[int] | Omit = omit, @@ -776,19 +716,19 @@ async def update( initial_price: Optional[float] | Omit = omit, internal_notes: Optional[str] | Omit = omit, legacy_payment_method_controls: Optional[bool] | Omit = omit, - metadata: Optional[Dict[str, object]] | Omit = omit, + metadata: Optional[object] | Omit = omit, offer_cancel_discount: Optional[bool] | Omit = omit, - override_tax_type: Optional[TaxType] | Omit = omit, + override_tax_type: str | Omit = omit, payment_method_configuration: Optional[plan_update_params.PaymentMethodConfiguration] | Omit = omit, renewal_price: Optional[float] | Omit = omit, stock: Optional[int] | Omit = omit, strike_through_initial_price: Optional[float] | Omit = omit, strike_through_renewal_price: Optional[float] | Omit = omit, - three_ds_level: Optional[Literal["mandate_challenge", "frictionless"]] | Omit = omit, + three_ds_level: Literal["mandate_challenge", "frictionless"] | Omit = omit, title: Optional[str] | Omit = omit, trial_period_days: Optional[int] | Omit = omit, unlimited_stock: Optional[bool] | Omit = omit, - visibility: Optional[Visibility] | Omit = omit, + visibility: 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, @@ -800,64 +740,53 @@ async def update( Update a plan's pricing, billing interval, visibility, stock, and other settings. - Required permissions: - - - `plan:update` - - `access_pass:basic:read` - - `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. - checkout_styling: Checkout styling overrides for this plan. Pass null to remove all overrides and - inherit from the company default. + checkout_styling: Checkout styling overrides for this plan. - currency: The available currencies on the platform + currency: The three-letter ISO currency code for the plan's pricing. Defaults to USD. custom_fields: An array of custom field definitions to collect from customers at checkout. + Omitting this field clears existing custom fields. description: A text description of the plan displayed to customers on the product page. - expiration_days: The number of days until the membership expires and access is revoked. For - example, 365 for one-year access. + expiration_days: The number of days until the membership expires and access is revoked. image: An image displayed on the product page to represent this plan. - initial_price: The amount charged on the first purchase. Provided in the plan's currency (e.g., - 10.43 for $10.43). + initial_price: The amount charged on the first purchase, in the plan's currency (e.g., 10.43 + for $10.43). internal_notes: Private notes visible only to the business owner. Not shown to customers. legacy_payment_method_controls: Whether this plan uses legacy payment method controls. metadata: Custom key-value pairs to store on the plan. Included in webhook payloads for - payment and membership events. Max 50 keys, 500 chars per key, 5000 chars per - value. + payment and membership events. offer_cancel_discount: Whether to offer a retention discount when a customer attempts to cancel. - override_tax_type: Whether or not the tax is included in a plan's price (or if it hasn't been set - up) + override_tax_type: Override the default tax classification for this specific plan. - payment_method_configuration: Explicit payment method configuration for the plan. Sending null removes any - custom configuration. + payment_method_configuration: Explicit payment method configuration for the plan. When not provided, the + company's defaults apply. - renewal_price: The amount charged each billing period for recurring plans. Provided in the - plan's currency (e.g., 10.43 for $10.43). + renewal_price: The amount charged each billing period for recurring plans, in the plan's + currency. stock: The maximum number of units available for purchase. Ignored when unlimited_stock is true. strike_through_initial_price: A comparison price displayed with a strikethrough for the initial price. - Provided in the plan's currency (e.g., 19.99 for $19.99). strike_through_renewal_price: A comparison price displayed with a strikethrough for the renewal price. - Provided in the plan's currency (e.g., 19.99 for $19.99). - three_ds_level: The 3D Secure behavior for a plan. + three_ds_level: The 3D Secure behavior for this plan. Send null to inherit the account default. title: The display name of the plan shown to customers on the product page. @@ -865,7 +794,7 @@ async def update( unlimited_stock: Whether the plan has unlimited stock. When true, the stock field is ignored. - visibility: Visibility of a resource + visibility: Whether the plan is visible to customers or hidden from public view. extra_headers: Send extra headers @@ -918,19 +847,18 @@ 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, - direction: Optional[Direction] | Omit = omit, - first: Optional[int] | Omit = omit, - last: Optional[int] | Omit = omit, - order: Optional[Literal["id", "active_members_count", "created_at", "internal_notes", "expires_at"]] - | Omit = omit, - plan_types: Optional[List[PlanType]] | Omit = omit, - product_ids: Optional[SequenceNotStr[str]] | Omit = omit, - release_methods: Optional[List[ReleaseMethod]] | Omit = omit, - visibilities: Optional[List[VisibilityFilter]] | Omit = omit, + after: str | Omit = omit, + before: str | Omit = omit, + created_after: str | Omit = omit, + created_before: str | Omit = omit, + direction: Literal["asc", "desc"] | Omit = omit, + first: int | Omit = omit, + last: int | Omit = omit, + order: Literal["id", "active_members_count", "created_at", "internal_notes", "expires_at"] | Omit = omit, + plan_types: SequenceNotStr[str] | Omit = omit, + product_ids: SequenceNotStr[str] | Omit = omit, + release_methods: SequenceNotStr[str] | Omit = omit, + visibilities: 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, @@ -942,28 +870,24 @@ def list( Returns a paginated list of plans belonging to a company, with optional filtering by visibility, type, release method, and product. - Required permissions: - - - `plan:basic:read` - Args: company_id: The unique identifier of the company to list plans for. - after: Returns the elements in the list that come after the specified cursor. + after: A cursor; returns plans after this position. - before: Returns the elements in the list that come before the specified cursor. + before: A cursor; returns plans before this position. created_after: Only return plans created after this timestamp. created_before: Only return plans created before this timestamp. - direction: The direction of the sort. + direction: The sort direction for results. Defaults to descending. - first: Returns the first _n_ elements from the list. + first: The number of plans to return (default and max 100). - last: Returns the last _n_ elements from the list. + last: The number of plans to return from the end of the range. - order: The ways a relation of Plans can be ordered + order: The field to sort results by. Defaults to created_at. plan_types: Filter to only plans matching these billing types. @@ -1027,10 +951,6 @@ async def delete( Existing memberships on this plan will not be affected. - Required permissions: - - - `plan:delete` - Args: extra_headers: Send extra headers diff --git a/src/whop_sdk/types/plan_create_params.py b/src/whop_sdk/types/plan_create_params.py index 34e9edc1..221ddb1c 100644 --- a/src/whop_sdk/types/plan_create_params.py +++ b/src/whop_sdk/types/plan_create_params.py @@ -2,25 +2,15 @@ from __future__ import annotations -from typing import Dict, List, Iterable, Optional +from typing import Iterable, Optional from typing_extensions import Literal, Required, TypedDict -from .checkout_font import CheckoutFont -from .checkout_shape import CheckoutShape -from .shared.currency import Currency -from .shared.tax_type import TaxType -from .shared.plan_type import PlanType -from .shared.visibility import Visibility -from .payment_method_types import PaymentMethodTypes -from .shared.release_method import ReleaseMethod +from .._types import SequenceNotStr -__all__ = ["PlanCreateParams", "CheckoutStyling", "CustomField", "Image", "PaymentMethodConfiguration"] +__all__ = ["PlanCreateParams", "CustomField", "Image", "PaymentMethodConfiguration"] class PlanCreateParams(TypedDict, total=False): - company_id: Required[str] - """The unique identifier of the company to create this plan for.""" - product_id: Required[str] """The unique identifier of the product to attach this plan to.""" @@ -33,36 +23,37 @@ class PlanCreateParams(TypedDict, total=False): For example, 30 for monthly or 365 for yearly. """ - checkout_styling: Optional[CheckoutStyling] - """Checkout styling overrides for this plan. + checkout_styling: Optional[object] + """Checkout styling overrides for this plan.""" + + company_id: str + """The unique identifier of the company to create this plan for. - Pass null to inherit from the company default. + Defaults to the caller's company. """ - currency: Optional[Currency] - """The available currencies on the platform""" + currency: str + """The three-letter ISO currency code for the plan's pricing. Defaults to USD.""" custom_fields: Optional[Iterable[CustomField]] - """An array of custom field definitions to collect from customers at checkout.""" + """An array of custom field definitions to collect from customers at checkout. + + Omitting this field clears existing custom fields. + """ description: Optional[str] """A text description of the plan displayed to customers on the product page.""" expiration_days: Optional[int] - """The number of days until the membership expires and access is revoked. - - Used for expiration-based plans. - """ + """The number of days until the membership expires and access is revoked.""" image: Optional[Image] """An image displayed on the product page to represent this plan.""" initial_price: Optional[float] - """The amount charged on the first purchase. - - For one-time plans, this is the full price. For recurring plans, this is an - additional charge on top of the renewal price. Provided in the plan's currency - (e.g., 10.43 for $10.43). + """ + The amount charged on the first purchase, in the plan's currency (e.g., 10.43 + for $10.43). """ internal_notes: Optional[str] @@ -71,18 +62,14 @@ class PlanCreateParams(TypedDict, total=False): legacy_payment_method_controls: Optional[bool] """Whether this plan uses legacy payment method controls.""" - metadata: Optional[Dict[str, object]] + metadata: Optional[object] """Custom key-value pairs to store on the plan. - Included in webhook payloads for payment and membership events. Max 50 keys, 500 - chars per key, 5000 chars per value. + Included in webhook payloads for payment and membership events. """ - override_tax_type: Optional[TaxType] - """ - Whether or not the tax is included in a plan's price (or if it hasn't been set - up) - """ + override_tax_type: str + """Override the default tax classification for this specific plan.""" payment_method_configuration: Optional[PaymentMethodConfiguration] """Explicit payment method configuration for the plan. @@ -90,16 +77,16 @@ class PlanCreateParams(TypedDict, total=False): When not provided, the company's defaults apply. """ - plan_type: Optional[PlanType] - """The type of plan that can be attached to a product""" + plan_type: str + """The billing type of the plan, such as one_time or renewal.""" - release_method: Optional[ReleaseMethod] - """The methods of how a plan can be released.""" + release_method: str + """The method used to sell this plan (e.g., buy_now, waitlist).""" renewal_price: Optional[float] - """The amount charged each billing period for recurring plans. - - Provided in the plan's currency (e.g., 10.43 for $10.43). + """ + The amount charged each billing period for recurring plans, in the plan's + currency. """ split_pay_required_payments: Optional[int] @@ -111,8 +98,8 @@ class PlanCreateParams(TypedDict, total=False): Ignored when unlimited_stock is true. """ - three_ds_level: Optional[Literal["mandate_challenge", "frictionless"]] - """The 3D Secure behavior for a plan.""" + three_ds_level: Literal["mandate_challenge", "frictionless"] + """The 3D Secure behavior for this plan. Send null to inherit the account default.""" title: Optional[str] """The display name of the plan shown to customers on the product page.""" @@ -121,62 +108,38 @@ class PlanCreateParams(TypedDict, total=False): """The number of free trial days before the first charge on a recurring plan.""" unlimited_stock: Optional[bool] - """Whether the plan has unlimited stock. - - When true, the stock field is ignored. Defaults to true. - """ - - visibility: Optional[Visibility] - """Visibility of a resource""" + """Whether the plan has unlimited stock. When true, the stock field is ignored.""" - -class CheckoutStyling(TypedDict, total=False): - """Checkout styling overrides for this plan. - - 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.""" - - button_color: Optional[str] - """A hex color code for the button color (e.g. #FF5733).""" - - font_family: Optional[CheckoutFont] - """The different font families available for checkout pages.""" + visibility: str + """Whether the plan is visible to customers or hidden from public view.""" class CustomField(TypedDict, total=False): - field_type: Required[Literal["text"]] + id: str + """The ID of the custom field (if being updated).""" + + field_type: Literal["text"] """The type of the custom field.""" - name: Required[str] + name: str """The name of the custom field.""" - id: Optional[str] - """The ID of the custom field (if being updated)""" - - order: Optional[int] + order: int """The order of the field.""" placeholder: Optional[str] - """The placeholder value of the field.""" + """An example response displayed in the input field.""" - required: Optional[bool] + required: bool """Whether or not the field is required.""" class Image(TypedDict, total=False): """An image displayed on the product page to represent this plan.""" - id: Required[str] - """The ID of an existing file object.""" + id: str + + direct_upload_id: str class PaymentMethodConfiguration(TypedDict, total=False): @@ -185,23 +148,8 @@ class PaymentMethodConfiguration(TypedDict, total=False): When not provided, the company's defaults apply. """ - disabled: Required[List[PaymentMethodTypes]] - """An array of payment method identifiers that are explicitly disabled. - - Only applies if the include_platform_defaults is true. - """ - - enabled: Required[List[PaymentMethodTypes]] - """An array of payment method identifiers that are explicitly enabled. + disabled: SequenceNotStr[str] - This means these payment methods will be shown on checkout. Example use case is - to only enable a specific payment method like cashapp, or extending the platform - defaults with additional methods. - """ + enabled: SequenceNotStr[str] - 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 - the documentation at docs.whop.com/payments. - """ + include_platform_defaults: bool diff --git a/src/whop_sdk/types/plan_list_params.py b/src/whop_sdk/types/plan_list_params.py index fc24663e..576a524e 100644 --- a/src/whop_sdk/types/plan_list_params.py +++ b/src/whop_sdk/types/plan_list_params.py @@ -2,16 +2,9 @@ from __future__ import annotations -from typing import List, Union, Optional -from datetime import datetime -from typing_extensions import Literal, Required, Annotated, TypedDict +from typing_extensions import Literal, Required, TypedDict from .._types import SequenceNotStr -from .._utils import PropertyInfo -from .shared.direction import Direction -from .shared.plan_type import PlanType -from .shared.release_method import ReleaseMethod -from .shared.visibility_filter import VisibilityFilter __all__ = ["PlanListParams"] @@ -20,38 +13,38 @@ class PlanListParams(TypedDict, total=False): company_id: Required[str] """The unique identifier of the company to list plans for.""" - after: Optional[str] - """Returns the elements in the list that come after the specified cursor.""" + after: str + """A cursor; returns plans after this position.""" - before: Optional[str] - """Returns the elements in the list that come before the specified cursor.""" + before: str + """A cursor; returns plans before this position.""" - created_after: Annotated[Union[str, datetime, None], PropertyInfo(format="iso8601")] + created_after: str """Only return plans created after this timestamp.""" - created_before: Annotated[Union[str, datetime, None], PropertyInfo(format="iso8601")] + created_before: str """Only return plans created before this timestamp.""" - direction: Optional[Direction] - """The direction of the sort.""" + direction: Literal["asc", "desc"] + """The sort direction for results. Defaults to descending.""" - first: Optional[int] - """Returns the first _n_ elements from the list.""" + first: int + """The number of plans to return (default and max 100).""" - last: Optional[int] - """Returns the last _n_ elements from the list.""" + last: int + """The number of plans to return from the end of the range.""" - order: Optional[Literal["id", "active_members_count", "created_at", "internal_notes", "expires_at"]] - """The ways a relation of Plans can be ordered""" + order: Literal["id", "active_members_count", "created_at", "internal_notes", "expires_at"] + """The field to sort results by. Defaults to created_at.""" - plan_types: Optional[List[PlanType]] + plan_types: SequenceNotStr[str] """Filter to only plans matching these billing types.""" - product_ids: Optional[SequenceNotStr[str]] + product_ids: SequenceNotStr[str] """Filter to only plans belonging to these product identifiers.""" - release_methods: Optional[List[ReleaseMethod]] + release_methods: SequenceNotStr[str] """Filter to only plans matching these release methods.""" - visibilities: Optional[List[VisibilityFilter]] + visibilities: SequenceNotStr[str] """Filter to only plans matching these visibility states.""" diff --git a/src/whop_sdk/types/plan_list_response.py b/src/whop_sdk/types/plan_list_response.py index db577cf4..3f2ce488 100644 --- a/src/whop_sdk/types/plan_list_response.py +++ b/src/whop_sdk/types/plan_list_response.py @@ -1,240 +1,114 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -from typing import Dict, List, Optional -from datetime import datetime -from typing_extensions import Literal +from typing import Optional from .._models import BaseModel -from .shared.currency import Currency -from .shared.plan_type import PlanType -from .shared.visibility import Visibility -from .payment_method_types import PaymentMethodTypes -from .shared.release_method import ReleaseMethod -__all__ = ["PlanListResponse", "Company", "Invoice", "PaymentMethodConfiguration", "Product"] - - -class Company(BaseModel): - """The company that sells this plan. - - Null for standalone invoice plans not linked to a company. - """ - - id: str - """The unique identifier for the company.""" - - title: str - """The display name of the company shown to customers.""" - - -class Invoice(BaseModel): - """The invoice this plan was generated for. - - Null if the plan was not created for a specific invoice. - """ - - id: str - """The unique identifier for the invoice.""" - - -class PaymentMethodConfiguration(BaseModel): - """ - The explicit payment method configuration specifying which payment methods are enabled or disabled for this plan. Null if the plan uses default settings. - """ - - disabled: List[PaymentMethodTypes] - """An array of payment method identifiers that are explicitly disabled. - - Only applies if the include_platform_defaults is true. - """ - - enabled: List[PaymentMethodTypes] - """An array of payment method identifiers that are explicitly enabled. - - This means these payment methods will be shown on checkout. Example use case is - to only enable a specific payment method like cashapp, or extending the platform - defaults with additional methods. - """ - - include_platform_defaults: 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 - the documentation at docs.whop.com/payments. - """ - - -class Product(BaseModel): - """The product that this plan belongs to. - - Null for standalone one-off purchases not linked to a product. - """ - - id: str - """The unique identifier for the product.""" - - title: str - """ - The display name of the product shown to customers on the product page and in - search results. - """ +__all__ = ["PlanListResponse"] class PlanListResponse(BaseModel): - """A plan defines pricing and billing terms for a checkout. - - Plans can optionally belong to a product, where they represent different pricing options such as one-time payments, recurring subscriptions, or free trials. - """ - id: str - """The unique identifier for the plan.""" + """The ID of the plan, which will look like plan\\__******\\********""" adaptive_pricing_enabled: bool - """Whether the creator has turned on adaptive pricing for this plan. + """Whether this plan accepts local currency payments via adaptive pricing""" - Raw setting — does not check processor compatibility or feature flags. - """ + billing_period: Optional[float] = None + """The number of days between recurring charges. Null for one-time plans""" - billing_period: Optional[int] = None - """The number of days between each recurring charge. + company: Optional[object] = None + """The company that sells this plan, an object with an id and title. - Null for one-time plans. For example, 30 for monthly or 365 for annual billing. + Null for standalone invoice plans """ - company: Optional[Company] = None - """The company that sells this plan. + created_at: str + """When the plan was created, as an ISO 8601 timestamp""" - Null for standalone invoice plans not linked to a company. - """ - - created_at: datetime - """The datetime the plan was created.""" - - currency: Currency - """The currency used for all prices on this plan (e.g., 'usd', 'eur'). - - All monetary amounts on the plan are denominated in this currency. - """ + currency: str + """The three-letter ISO currency code all prices on this plan are denominated in""" description: Optional[str] = None - """A text description of the plan visible to customers. + """A text description of the plan visible to customers""" - Maximum 1000 characters. Null if no description is set. - """ - - expiration_days: Optional[int] = None - """The number of days until the membership expires (for expiration-based plans). - - For example, 365 for a one-year access pass. - """ + expiration_days: Optional[float] = None + """The number of days until the membership expires, for expiration-based plans""" initial_price: float - """The initial purchase price in the plan's base_currency (e.g., 49.99 for $49.99). - - For one-time plans, this is the full price. For renewal plans, this is charged - on top of the first renewal_price. - """ + """The initial purchase price in the plan's currency""" internal_notes: Optional[str] = None - """Private notes visible only to the company owner and team members. - - Not shown to customers. Null if no notes have been added. - """ + """Private notes visible only to authorized team members""" - invoice: Optional[Invoice] = None - """The invoice this plan was generated for. + invoice: Optional[object] = None + """The invoice this plan was generated for, an object with an id. - Null if the plan was not created for a specific invoice. + Null unless the plan was created for an invoice """ - member_count: Optional[int] = None - """The number of users who currently hold an active membership through this plan. + member_count: Optional[float] = None + """The number of active memberships on this plan. - Only visible to authorized team members. + Only visible to authorized team members """ - metadata: Optional[Dict[str, object]] = None - """Custom key-value pairs stored on the plan. + metadata: Optional[object] = None + """Custom key-value pairs stored on the plan""" - Included in webhook payloads for payment and membership events. + payment_method_configuration: Optional[object] = None """ - - payment_method_configuration: Optional[PaymentMethodConfiguration] = None - """ - The explicit payment method configuration specifying which payment methods are - enabled or disabled for this plan. Null if the plan uses default settings. + The explicit payment method configuration for the plan, an object with enabled, + disabled and include_platform_defaults. Null if the plan uses default settings """ - plan_type: PlanType + plan_type: str """ The billing model for this plan: 'renewal' for recurring subscriptions or - 'one_time' for single payments. + 'one_time' for single payments """ - product: Optional[Product] = None - """The product that this plan belongs to. + product: Optional[object] = None + """The product this plan belongs to, an object with an id and title. - Null for standalone one-off purchases not linked to a product. + Null for standalone plans """ purchase_url: str - """ - The full URL where customers can purchase this plan directly, bypassing the - product page. - """ + """The full URL where customers can purchase this plan directly""" - release_method: ReleaseMethod - """ - The method used to sell this plan: 'buy_now' for immediate purchase or - 'waitlist' for waitlist-based access. - """ + release_method: str + """The method used to sell this plan, e.g. 'buy_now' or 'waitlist'""" renewal_price: float - """ - The recurring price charged every billing_period in the plan's base_currency - (e.g., 9.99 for $9.99/period). Zero for one-time plans. - """ + """The recurring price charged every billing period in the plan's currency""" - split_pay_required_payments: Optional[int] = None - """The total number of installment payments required before the subscription - pauses. - - Null if split pay is not configured. Must be greater than 1. - """ + split_pay_required_payments: Optional[float] = None + """The number of installment payments required before the subscription pauses""" - stock: Optional[int] = None + stock: Optional[float] = None """The number of units available for purchase. - Only visible to authorized team members. Null if the requester lacks permission. + Only visible to authorized team members """ - three_ds_level: Optional[Literal["mandate_challenge", "frictionless"]] = None - """The 3D Secure behavior for a plan.""" + three_ds_level: Optional[str] = None + """The 3D Secure behavior for this plan. - title: Optional[str] = None - """ - The display name of the plan shown to customers on the product page and at - checkout. Maximum 30 characters. Null if no title has been set. + Null means the plan inherits the account default """ - trial_period_days: Optional[int] = None - """The number of free trial days before the first charge on a renewal plan. + title: Optional[str] = None + """The display name of the plan shown to customers""" - Null if no trial is configured or the current user has already used a trial for - this plan. - """ + trial_period_days: Optional[float] = None + """The number of free trial days before the first charge on a recurring plan""" unlimited_stock: bool - """When true, the plan has unlimited stock (stock field is ignored). + """Whether the plan has unlimited stock""" - When false, purchases are limited by the stock field. - """ - - updated_at: datetime - """The datetime the plan was last updated.""" + updated_at: str + """When the plan was last updated, as an ISO 8601 timestamp""" - visibility: Visibility - """Controls whether the plan is visible to customers. - - When set to 'hidden', the plan is only accessible via direct link. - """ + visibility: str + """Whether the plan is visible to customers or hidden from public view""" diff --git a/src/whop_sdk/types/plan_update_params.py b/src/whop_sdk/types/plan_update_params.py index 853a890f..2e96dd6b 100644 --- a/src/whop_sdk/types/plan_update_params.py +++ b/src/whop_sdk/types/plan_update_params.py @@ -2,17 +2,12 @@ from __future__ import annotations -from typing import Dict, List, Iterable, Optional -from typing_extensions import Literal, Required, TypedDict +from typing import Iterable, Optional +from typing_extensions import Literal, TypedDict -from .checkout_font import CheckoutFont -from .checkout_shape import CheckoutShape -from .shared.currency import Currency -from .shared.tax_type import TaxType -from .shared.visibility import Visibility -from .payment_method_types import PaymentMethodTypes +from .._types import SequenceNotStr -__all__ = ["PlanUpdateParams", "CheckoutStyling", "CustomField", "Image", "PaymentMethodConfiguration"] +__all__ = ["PlanUpdateParams", "CustomField", "Image", "PaymentMethodConfiguration"] class PlanUpdateParams(TypedDict, total=False): @@ -25,34 +20,31 @@ class PlanUpdateParams(TypedDict, total=False): For example, 30 for monthly or 365 for yearly. """ - checkout_styling: Optional[CheckoutStyling] - """Checkout styling overrides for this plan. + checkout_styling: Optional[object] + """Checkout styling overrides for this plan.""" - Pass null to remove all overrides and inherit from the company default. - """ - - currency: Optional[Currency] - """The available currencies on the platform""" + currency: str + """The three-letter ISO currency code for the plan's pricing. Defaults to USD.""" custom_fields: Optional[Iterable[CustomField]] - """An array of custom field definitions to collect from customers at checkout.""" + """An array of custom field definitions to collect from customers at checkout. + + Omitting this field clears existing custom fields. + """ description: Optional[str] """A text description of the plan displayed to customers on the product page.""" expiration_days: Optional[int] - """The number of days until the membership expires and access is revoked. - - For example, 365 for one-year access. - """ + """The number of days until the membership expires and access is revoked.""" image: Optional[Image] """An image displayed on the product page to represent this plan.""" initial_price: Optional[float] - """The amount charged on the first purchase. - - Provided in the plan's currency (e.g., 10.43 for $10.43). + """ + The amount charged on the first purchase, in the plan's currency (e.g., 10.43 + for $10.43). """ internal_notes: Optional[str] @@ -61,32 +53,28 @@ class PlanUpdateParams(TypedDict, total=False): legacy_payment_method_controls: Optional[bool] """Whether this plan uses legacy payment method controls.""" - metadata: Optional[Dict[str, object]] + metadata: Optional[object] """Custom key-value pairs to store on the plan. - Included in webhook payloads for payment and membership events. Max 50 keys, 500 - chars per key, 5000 chars per value. + Included in webhook payloads for payment and membership events. """ offer_cancel_discount: Optional[bool] """Whether to offer a retention discount when a customer attempts to cancel.""" - override_tax_type: Optional[TaxType] - """ - Whether or not the tax is included in a plan's price (or if it hasn't been set - up) - """ + override_tax_type: str + """Override the default tax classification for this specific plan.""" payment_method_configuration: Optional[PaymentMethodConfiguration] """Explicit payment method configuration for the plan. - Sending null removes any custom configuration. + When not provided, the company's defaults apply. """ renewal_price: Optional[float] - """The amount charged each billing period for recurring plans. - - Provided in the plan's currency (e.g., 10.43 for $10.43). + """ + The amount charged each billing period for recurring plans, in the plan's + currency. """ stock: Optional[int] @@ -96,19 +84,13 @@ class PlanUpdateParams(TypedDict, total=False): """ strike_through_initial_price: Optional[float] - """A comparison price displayed with a strikethrough for the initial price. - - Provided in the plan's currency (e.g., 19.99 for $19.99). - """ + """A comparison price displayed with a strikethrough for the initial price.""" strike_through_renewal_price: Optional[float] - """A comparison price displayed with a strikethrough for the renewal price. + """A comparison price displayed with a strikethrough for the renewal price.""" - Provided in the plan's currency (e.g., 19.99 for $19.99). - """ - - three_ds_level: Optional[Literal["mandate_challenge", "frictionless"]] - """The 3D Secure behavior for a plan.""" + three_ds_level: Literal["mandate_challenge", "frictionless"] + """The 3D Secure behavior for this plan. Send null to inherit the account default.""" title: Optional[str] """The display name of the plan shown to customers on the product page.""" @@ -119,82 +101,46 @@ class PlanUpdateParams(TypedDict, total=False): unlimited_stock: Optional[bool] """Whether the plan has unlimited stock. When true, the stock field is ignored.""" - visibility: Optional[Visibility] - """Visibility of a resource""" - - -class CheckoutStyling(TypedDict, total=False): - """Checkout styling overrides for this plan. - - 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.""" - - button_color: Optional[str] - """A hex color code for the button color (e.g. #FF5733).""" - - font_family: Optional[CheckoutFont] - """The different font families available for checkout pages.""" + visibility: str + """Whether the plan is visible to customers or hidden from public view.""" class CustomField(TypedDict, total=False): - field_type: Required[Literal["text"]] + id: str + """The ID of the custom field (if being updated).""" + + field_type: Literal["text"] """The type of the custom field.""" - name: Required[str] + name: str """The name of the custom field.""" - id: Optional[str] - """The ID of the custom field (if being updated)""" - - order: Optional[int] + order: int """The order of the field.""" placeholder: Optional[str] - """The placeholder value of the field.""" + """An example response displayed in the input field.""" - required: Optional[bool] + required: bool """Whether or not the field is required.""" class Image(TypedDict, total=False): """An image displayed on the product page to represent this plan.""" - id: Required[str] - """The ID of an existing file object.""" + id: str + + direct_upload_id: str class PaymentMethodConfiguration(TypedDict, total=False): """Explicit payment method configuration for the plan. - Sending null removes any custom configuration. - """ - - disabled: Required[List[PaymentMethodTypes]] - """An array of payment method identifiers that are explicitly disabled. - - Only applies if the include_platform_defaults is true. + When not provided, the company's defaults apply. """ - enabled: Required[List[PaymentMethodTypes]] - """An array of payment method identifiers that are explicitly enabled. + disabled: SequenceNotStr[str] - This means these payment methods will be shown on checkout. Example use case is - to only enable a specific payment method like cashapp, or extending the platform - defaults with additional methods. - """ + enabled: SequenceNotStr[str] - 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 - the documentation at docs.whop.com/payments. - """ + include_platform_defaults: bool diff --git a/src/whop_sdk/types/shared/plan.py b/src/whop_sdk/types/shared/plan.py index cf775d35..45a4b4ed 100644 --- a/src/whop_sdk/types/shared/plan.py +++ b/src/whop_sdk/types/shared/plan.py @@ -1,281 +1,218 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -from typing import Dict, List, Optional -from datetime import datetime +from typing import List, Optional from typing_extensions import Literal -from .currency import Currency -from .tax_type import TaxType from ..._models import BaseModel -from .plan_type import PlanType -from .visibility import Visibility -from .release_method import ReleaseMethod -from ..payment_method_types import PaymentMethodTypes -__all__ = ["Plan", "Company", "CustomField", "Invoice", "PaymentMethodConfiguration", "Product"] - - -class Company(BaseModel): - """The company that sells this plan. - - Null for standalone invoice plans not linked to a company. - """ - - id: str - """The unique identifier for the company.""" - - title: str - """The display name of the company shown to customers.""" - - -class CustomField(BaseModel): - """An object representing a custom field for a plan.""" - - id: str - """The unique identifier for the custom field.""" - - field_type: Literal["text"] - """What type of input field to use.""" - - name: str - """The title/header of the custom field.""" - - order: Optional[int] = None - """How the custom field should be ordered when rendered on the checkout page.""" - - placeholder: Optional[str] = None - """An example response displayed in the input field.""" - - required: bool - """Whether or not the custom field is required.""" - - -class Invoice(BaseModel): - """The invoice this plan was generated for. - - Null if the plan was not created for a specific invoice. - """ - - id: str - """The unique identifier for the invoice.""" - - -class PaymentMethodConfiguration(BaseModel): - """ - The explicit payment method configuration specifying which payment methods are enabled or disabled for this plan. Null if the plan uses default settings. - """ - - disabled: List[PaymentMethodTypes] - """An array of payment method identifiers that are explicitly disabled. - - Only applies if the include_platform_defaults is true. - """ - - enabled: List[PaymentMethodTypes] - """An array of payment method identifiers that are explicitly enabled. - - This means these payment methods will be shown on checkout. Example use case is - to only enable a specific payment method like cashapp, or extending the platform - defaults with additional methods. - """ - - include_platform_defaults: 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 - the documentation at docs.whop.com/payments. - """ - - -class Product(BaseModel): - """The product that this plan belongs to. - - Null for standalone one-off purchases not linked to a product. - """ - - id: str - """The unique identifier for the product.""" - - title: str - """ - The display name of the product shown to customers on the product page and in - search results. - """ +__all__ = ["Plan"] class Plan(BaseModel): - """A plan defines pricing and billing terms for a checkout. - - Plans can optionally belong to a product, where they represent different pricing options such as one-time payments, recurring subscriptions, or free trials. - """ - id: str - """The unique identifier for the plan.""" + """The ID of the plan, which will look like 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. + """Whether this plan accepts local currency payments via adaptive pricing""" - Null for one-time plans. For example, 30 for monthly or 365 for annual billing. - """ + billing_period: Optional[float] = None + """The number of days between recurring charges. Null for one-time plans""" collect_tax: bool - """ - Whether tax is collected on purchases of this plan, based on the company's tax - configuration. - """ - - company: Optional[Company] = None - """The company that sells this plan. - - Null for standalone invoice plans not linked to a company. - """ - - created_at: datetime - """The datetime the plan was created.""" - - currency: Currency - """The currency used for all prices on this plan (e.g., 'usd', 'eur'). - - All monetary amounts on the plan are denominated in this currency. - """ - - custom_fields: List[CustomField] - """ - Custom input fields displayed on the checkout form that collect additional - information from the buyer. + """Whether tax is collected on purchases of this plan""" + + company: Optional[object] = None + """The company that sells this plan, an object with an id and title. + + Null for standalone invoice plans + """ + + created_at: str + """When the plan was created, as an ISO 8601 timestamp""" + + currency: Literal[ + "usd", + "sgd", + "inr", + "aud", + "brl", + "cad", + "dkk", + "eur", + "nok", + "gbp", + "sek", + "chf", + "hkd", + "huf", + "jpy", + "mxn", + "myr", + "pln", + "czk", + "nzd", + "aed", + "eth", + "ape", + "cop", + "ron", + "thb", + "bgn", + "idr", + "dop", + "php", + "try", + "krw", + "twd", + "vnd", + "pkr", + "clp", + "uyu", + "ars", + "zar", + "dzd", + "tnd", + "mad", + "kes", + "kwd", + "jod", + "all", + "xcd", + "amd", + "bsd", + "bhd", + "bob", + "bam", + "khr", + "crc", + "xof", + "egp", + "etb", + "gmd", + "ghs", + "gtq", + "gyd", + "ils", + "jmd", + "mop", + "mga", + "mur", + "mdl", + "mnt", + "nad", + "ngn", + "mkd", + "omr", + "pyg", + "pen", + "qar", + "rwf", + "sar", + "rsd", + "lkr", + "tzs", + "ttd", + "uzs", + "rub", + "btc", + "cny", + "usdt", + "kzt", + "awg", + "whop_usd", + "xau", + ] + """The three-letter ISO currency code all prices on this plan are denominated in""" + + custom_fields: List[object] + """ + Custom input fields displayed on the checkout form, objects with id, field_type, + name, order, placeholder and required """ description: Optional[str] = None - """A text description of the plan visible to customers. + """A text description of the plan visible to customers""" - Maximum 1000 characters. Null if no description is set. - """ - - expiration_days: Optional[int] = None - """The number of days until the membership expires (for expiration-based plans). - - For example, 365 for a one-year access pass. - """ + expiration_days: Optional[float] = None + """The number of days until the membership expires, for expiration-based plans""" initial_price: float - """The initial purchase price in the plan's base_currency (e.g., 49.99 for $49.99). - - For one-time plans, this is the full price. For renewal plans, this is charged - on top of the first renewal_price. - """ + """The initial purchase price in the plan's currency""" internal_notes: Optional[str] = None - """Private notes visible only to the company owner and team members. + """Private notes visible only to authorized team members""" - Not shown to customers. Null if no notes have been added. - """ - - invoice: Optional[Invoice] = None - """The invoice this plan was generated for. + invoice: Optional[object] = None + """The invoice this plan was generated for, an object with an id. - Null if the plan was not created for a specific invoice. + Null unless the plan was created for an invoice """ - member_count: Optional[int] = None - """The number of users who currently hold an active membership through this plan. + member_count: Optional[float] = None + """The number of active memberships on this plan. - Only visible to authorized team members. + Only visible to authorized team members """ - metadata: Optional[Dict[str, object]] = None - """Custom key-value pairs stored on the plan. - - Included in webhook payloads for payment and membership events. - """ + metadata: Optional[object] = None + """Custom key-value pairs stored on the plan""" - payment_method_configuration: Optional[PaymentMethodConfiguration] = None + payment_method_configuration: Optional[object] = None """ - The explicit payment method configuration specifying which payment methods are - enabled or disabled for this plan. Null if the plan uses default settings. + The explicit payment method configuration for the plan, an object with enabled, + disabled and include_platform_defaults. Null if the plan uses default settings """ - plan_type: PlanType + plan_type: Literal["renewal", "one_time"] """ The billing model for this plan: 'renewal' for recurring subscriptions or - 'one_time' for single payments. + 'one_time' for single payments """ - product: Optional[Product] = None - """The product that this plan belongs to. + product: Optional[object] = None + """The product this plan belongs to, an object with an id and title. - Null for standalone one-off purchases not linked to a product. + Null for standalone plans """ purchase_url: str - """ - The full URL where customers can purchase this plan directly, bypassing the - product page. - """ + """The full URL where customers can purchase this plan directly""" - release_method: ReleaseMethod - """ - The method used to sell this plan: 'buy_now' for immediate purchase or - 'waitlist' for waitlist-based access. - """ + release_method: Literal["buy_now", "waitlist"] + """The method used to sell this plan, e.g. 'buy_now' or 'waitlist'""" renewal_price: float - """ - The recurring price charged every billing_period in the plan's base_currency - (e.g., 9.99 for $9.99/period). Zero for one-time plans. - """ - - split_pay_required_payments: Optional[int] = None - """The total number of installment payments required before the subscription - pauses. + """The recurring price charged every billing period in the plan's currency""" - Null if split pay is not configured. Must be greater than 1. - """ + split_pay_required_payments: Optional[float] = None + """The number of installment payments required before the subscription pauses""" - stock: Optional[int] = None + stock: Optional[float] = None """The number of units available for purchase. - Only visible to authorized team members. Null if the requester lacks permission. + Only visible to authorized team members """ - tax_type: TaxType - """ - How tax is handled for this plan: 'inclusive' (tax included in price), - 'exclusive' (tax added at checkout), or 'unspecified' (tax not configured). - """ + tax_type: str + """How tax is handled for this plan: 'inclusive', 'exclusive', or 'unspecified'""" three_ds_level: Optional[Literal["mandate_challenge", "frictionless"]] = None - """The 3D Secure behavior for a plan.""" + """The 3D Secure behavior for this plan. - title: Optional[str] = None - """ - The display name of the plan shown to customers on the product page and at - checkout. Maximum 30 characters. Null if no title has been set. + Null means the plan inherits the account default """ - trial_period_days: Optional[int] = None - """The number of free trial days before the first charge on a renewal plan. + title: Optional[str] = None + """The display name of the plan shown to customers""" - Null if no trial is configured or the current user has already used a trial for - this plan. - """ + trial_period_days: Optional[float] = None + """The number of free trial days before the first charge on a recurring plan""" unlimited_stock: bool - """When true, the plan has unlimited stock (stock field is ignored). - - When false, purchases are limited by the stock field. - """ + """Whether the plan has unlimited stock""" - updated_at: datetime - """The datetime the plan was last updated.""" + updated_at: str + """When the plan was last updated, as an ISO 8601 timestamp""" - visibility: Visibility - """Controls whether the plan is visible to customers. - - When set to 'hidden', the plan is only accessible via direct link. - """ + visibility: Literal["visible", "hidden", "archived", "quick_link"] + """Whether the plan is visible to customers or hidden from public view""" diff --git a/tests/api_resources/test_plans.py b/tests/api_resources/test_plans.py index 1a92b368..2ed798c1 100644 --- a/tests/api_resources/test_plans.py +++ b/tests/api_resources/test_plans.py @@ -13,7 +13,6 @@ PlanListResponse, PlanDeleteResponse, ) -from whop_sdk._utils import parse_datetime from whop_sdk.pagination import SyncCursorPage, AsyncCursorPage from whop_sdk.types.shared import Plan @@ -27,8 +26,7 @@ class TestPlans: @parametrize def test_method_create(self, client: Whop) -> None: plan = client.plans.create( - company_id="biz_xxxxxxxxxxxxxx", - product_id="prod_xxxxxxxxxxxxx", + product_id="product_id", ) assert_matches_type(Plan, plan, path=["response"]) @@ -36,50 +34,48 @@ def test_method_create(self, client: Whop) -> None: @parametrize def test_method_create_with_all_params(self, client: Whop) -> None: plan = client.plans.create( - company_id="biz_xxxxxxxxxxxxxx", - product_id="prod_xxxxxxxxxxxxx", + product_id="product_id", adaptive_pricing_enabled=True, - billing_period=42, - checkout_styling={ - "background_color": "background_color", - "border_style": "rounded", - "button_color": "button_color", - "font_family": "system", - }, - currency="usd", + billing_period=0, + checkout_styling={}, + company_id="company_id", + currency="currency", custom_fields=[ { + "id": "id", "field_type": "text", "name": "name", - "id": "id", - "order": 42, + "order": 0, "placeholder": "placeholder", "required": True, } ], description="description", - expiration_days=42, - image={"id": "id"}, - initial_price=6.9, + expiration_days=0, + image={ + "id": "id", + "direct_upload_id": "direct_upload_id", + }, + initial_price=0, internal_notes="internal_notes", legacy_payment_method_controls=True, - metadata={"foo": "bar"}, - override_tax_type="inclusive", + metadata={}, + override_tax_type="override_tax_type", payment_method_configuration={ - "disabled": ["acss_debit"], - "enabled": ["acss_debit"], + "disabled": ["string"], + "enabled": ["string"], "include_platform_defaults": True, }, - plan_type="renewal", - release_method="buy_now", - renewal_price=6.9, - split_pay_required_payments=42, - stock=42, + plan_type="plan_type", + release_method="release_method", + renewal_price=0, + split_pay_required_payments=0, + stock=0, three_ds_level="mandate_challenge", title="title", - trial_period_days=42, + trial_period_days=0, unlimited_stock=True, - visibility="visible", + visibility="visibility", ) assert_matches_type(Plan, plan, path=["response"]) @@ -87,8 +83,7 @@ def test_method_create_with_all_params(self, client: Whop) -> None: @parametrize def test_raw_response_create(self, client: Whop) -> None: response = client.plans.with_raw_response.create( - company_id="biz_xxxxxxxxxxxxxx", - product_id="prod_xxxxxxxxxxxxx", + product_id="product_id", ) assert response.is_closed is True @@ -100,8 +95,7 @@ def test_raw_response_create(self, client: Whop) -> None: @parametrize def test_streaming_response_create(self, client: Whop) -> None: with client.plans.with_streaming_response.create( - company_id="biz_xxxxxxxxxxxxxx", - product_id="prod_xxxxxxxxxxxxx", + product_id="product_id", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -115,7 +109,7 @@ def test_streaming_response_create(self, client: Whop) -> None: @parametrize def test_method_retrieve(self, client: Whop) -> None: plan = client.plans.retrieve( - "plan_xxxxxxxxxxxxx", + "id", ) assert_matches_type(Plan, plan, path=["response"]) @@ -123,7 +117,7 @@ def test_method_retrieve(self, client: Whop) -> None: @parametrize def test_raw_response_retrieve(self, client: Whop) -> None: response = client.plans.with_raw_response.retrieve( - "plan_xxxxxxxxxxxxx", + "id", ) assert response.is_closed is True @@ -135,7 +129,7 @@ def test_raw_response_retrieve(self, client: Whop) -> None: @parametrize def test_streaming_response_retrieve(self, client: Whop) -> None: with client.plans.with_streaming_response.retrieve( - "plan_xxxxxxxxxxxxx", + "id", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -157,7 +151,7 @@ def test_path_params_retrieve(self, client: Whop) -> None: @parametrize def test_method_update(self, client: Whop) -> None: plan = client.plans.update( - id="plan_xxxxxxxxxxxxx", + id="id", ) assert_matches_type(Plan, plan, path=["response"]) @@ -165,49 +159,47 @@ def test_method_update(self, client: Whop) -> None: @parametrize def test_method_update_with_all_params(self, client: Whop) -> None: plan = client.plans.update( - id="plan_xxxxxxxxxxxxx", + id="id", adaptive_pricing_enabled=True, - billing_period=42, - checkout_styling={ - "background_color": "background_color", - "border_style": "rounded", - "button_color": "button_color", - "font_family": "system", - }, - currency="usd", + billing_period=0, + checkout_styling={}, + currency="currency", custom_fields=[ { + "id": "id", "field_type": "text", "name": "name", - "id": "id", - "order": 42, + "order": 0, "placeholder": "placeholder", "required": True, } ], description="description", - expiration_days=42, - image={"id": "id"}, - initial_price=6.9, + expiration_days=0, + image={ + "id": "id", + "direct_upload_id": "direct_upload_id", + }, + initial_price=0, internal_notes="internal_notes", legacy_payment_method_controls=True, - metadata={"foo": "bar"}, + metadata={}, offer_cancel_discount=True, - override_tax_type="inclusive", + override_tax_type="override_tax_type", payment_method_configuration={ - "disabled": ["acss_debit"], - "enabled": ["acss_debit"], + "disabled": ["string"], + "enabled": ["string"], "include_platform_defaults": True, }, - renewal_price=6.9, - stock=42, - strike_through_initial_price=6.9, - strike_through_renewal_price=6.9, + renewal_price=0, + stock=0, + strike_through_initial_price=0, + strike_through_renewal_price=0, three_ds_level="mandate_challenge", title="title", - trial_period_days=42, + trial_period_days=0, unlimited_stock=True, - visibility="visible", + visibility="visibility", ) assert_matches_type(Plan, plan, path=["response"]) @@ -215,7 +207,7 @@ def test_method_update_with_all_params(self, client: Whop) -> None: @parametrize def test_raw_response_update(self, client: Whop) -> None: response = client.plans.with_raw_response.update( - id="plan_xxxxxxxxxxxxx", + id="id", ) assert response.is_closed is True @@ -227,7 +219,7 @@ def test_raw_response_update(self, client: Whop) -> None: @parametrize def test_streaming_response_update(self, client: Whop) -> None: with client.plans.with_streaming_response.update( - id="plan_xxxxxxxxxxxxx", + id="id", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -249,7 +241,7 @@ def test_path_params_update(self, client: Whop) -> None: @parametrize def test_method_list(self, client: Whop) -> None: plan = client.plans.list( - company_id="biz_xxxxxxxxxxxxxx", + company_id="company_id", ) assert_matches_type(SyncCursorPage[PlanListResponse], plan, path=["response"]) @@ -257,19 +249,19 @@ def test_method_list(self, client: Whop) -> None: @parametrize def test_method_list_with_all_params(self, client: Whop) -> None: plan = client.plans.list( - company_id="biz_xxxxxxxxxxxxxx", + company_id="company_id", after="after", before="before", - created_after=parse_datetime("2023-12-01T05:00:00.401Z"), - created_before=parse_datetime("2023-12-01T05:00:00.401Z"), + created_after="created_after", + created_before="created_before", direction="asc", - first=42, - last=42, + first=0, + last=0, order="id", - plan_types=["renewal"], + plan_types=["string"], product_ids=["string"], - release_methods=["buy_now"], - visibilities=["visible"], + release_methods=["string"], + visibilities=["string"], ) assert_matches_type(SyncCursorPage[PlanListResponse], plan, path=["response"]) @@ -277,7 +269,7 @@ def test_method_list_with_all_params(self, client: Whop) -> None: @parametrize def test_raw_response_list(self, client: Whop) -> None: response = client.plans.with_raw_response.list( - company_id="biz_xxxxxxxxxxxxxx", + company_id="company_id", ) assert response.is_closed is True @@ -289,7 +281,7 @@ def test_raw_response_list(self, client: Whop) -> None: @parametrize def test_streaming_response_list(self, client: Whop) -> None: with client.plans.with_streaming_response.list( - company_id="biz_xxxxxxxxxxxxxx", + company_id="company_id", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -303,7 +295,7 @@ def test_streaming_response_list(self, client: Whop) -> None: @parametrize def test_method_delete(self, client: Whop) -> None: plan = client.plans.delete( - "plan_xxxxxxxxxxxxx", + "id", ) assert_matches_type(PlanDeleteResponse, plan, path=["response"]) @@ -311,7 +303,7 @@ def test_method_delete(self, client: Whop) -> None: @parametrize def test_raw_response_delete(self, client: Whop) -> None: response = client.plans.with_raw_response.delete( - "plan_xxxxxxxxxxxxx", + "id", ) assert response.is_closed is True @@ -323,7 +315,7 @@ def test_raw_response_delete(self, client: Whop) -> None: @parametrize def test_streaming_response_delete(self, client: Whop) -> None: with client.plans.with_streaming_response.delete( - "plan_xxxxxxxxxxxxx", + "id", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -351,8 +343,7 @@ class TestAsyncPlans: @parametrize async def test_method_create(self, async_client: AsyncWhop) -> None: plan = await async_client.plans.create( - company_id="biz_xxxxxxxxxxxxxx", - product_id="prod_xxxxxxxxxxxxx", + product_id="product_id", ) assert_matches_type(Plan, plan, path=["response"]) @@ -360,50 +351,48 @@ async def test_method_create(self, async_client: AsyncWhop) -> None: @parametrize async def test_method_create_with_all_params(self, async_client: AsyncWhop) -> None: plan = await async_client.plans.create( - company_id="biz_xxxxxxxxxxxxxx", - product_id="prod_xxxxxxxxxxxxx", + product_id="product_id", adaptive_pricing_enabled=True, - billing_period=42, - checkout_styling={ - "background_color": "background_color", - "border_style": "rounded", - "button_color": "button_color", - "font_family": "system", - }, - currency="usd", + billing_period=0, + checkout_styling={}, + company_id="company_id", + currency="currency", custom_fields=[ { + "id": "id", "field_type": "text", "name": "name", - "id": "id", - "order": 42, + "order": 0, "placeholder": "placeholder", "required": True, } ], description="description", - expiration_days=42, - image={"id": "id"}, - initial_price=6.9, + expiration_days=0, + image={ + "id": "id", + "direct_upload_id": "direct_upload_id", + }, + initial_price=0, internal_notes="internal_notes", legacy_payment_method_controls=True, - metadata={"foo": "bar"}, - override_tax_type="inclusive", + metadata={}, + override_tax_type="override_tax_type", payment_method_configuration={ - "disabled": ["acss_debit"], - "enabled": ["acss_debit"], + "disabled": ["string"], + "enabled": ["string"], "include_platform_defaults": True, }, - plan_type="renewal", - release_method="buy_now", - renewal_price=6.9, - split_pay_required_payments=42, - stock=42, + plan_type="plan_type", + release_method="release_method", + renewal_price=0, + split_pay_required_payments=0, + stock=0, three_ds_level="mandate_challenge", title="title", - trial_period_days=42, + trial_period_days=0, unlimited_stock=True, - visibility="visible", + visibility="visibility", ) assert_matches_type(Plan, plan, path=["response"]) @@ -411,8 +400,7 @@ async def test_method_create_with_all_params(self, async_client: AsyncWhop) -> N @parametrize async def test_raw_response_create(self, async_client: AsyncWhop) -> None: response = await async_client.plans.with_raw_response.create( - company_id="biz_xxxxxxxxxxxxxx", - product_id="prod_xxxxxxxxxxxxx", + product_id="product_id", ) assert response.is_closed is True @@ -424,8 +412,7 @@ async def test_raw_response_create(self, async_client: AsyncWhop) -> None: @parametrize async def test_streaming_response_create(self, async_client: AsyncWhop) -> None: async with async_client.plans.with_streaming_response.create( - company_id="biz_xxxxxxxxxxxxxx", - product_id="prod_xxxxxxxxxxxxx", + product_id="product_id", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -439,7 +426,7 @@ async def test_streaming_response_create(self, async_client: AsyncWhop) -> None: @parametrize async def test_method_retrieve(self, async_client: AsyncWhop) -> None: plan = await async_client.plans.retrieve( - "plan_xxxxxxxxxxxxx", + "id", ) assert_matches_type(Plan, plan, path=["response"]) @@ -447,7 +434,7 @@ async def test_method_retrieve(self, async_client: AsyncWhop) -> None: @parametrize async def test_raw_response_retrieve(self, async_client: AsyncWhop) -> None: response = await async_client.plans.with_raw_response.retrieve( - "plan_xxxxxxxxxxxxx", + "id", ) assert response.is_closed is True @@ -459,7 +446,7 @@ async def test_raw_response_retrieve(self, async_client: AsyncWhop) -> None: @parametrize async def test_streaming_response_retrieve(self, async_client: AsyncWhop) -> None: async with async_client.plans.with_streaming_response.retrieve( - "plan_xxxxxxxxxxxxx", + "id", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -481,7 +468,7 @@ async def test_path_params_retrieve(self, async_client: AsyncWhop) -> None: @parametrize async def test_method_update(self, async_client: AsyncWhop) -> None: plan = await async_client.plans.update( - id="plan_xxxxxxxxxxxxx", + id="id", ) assert_matches_type(Plan, plan, path=["response"]) @@ -489,49 +476,47 @@ async def test_method_update(self, async_client: AsyncWhop) -> None: @parametrize async def test_method_update_with_all_params(self, async_client: AsyncWhop) -> None: plan = await async_client.plans.update( - id="plan_xxxxxxxxxxxxx", + id="id", adaptive_pricing_enabled=True, - billing_period=42, - checkout_styling={ - "background_color": "background_color", - "border_style": "rounded", - "button_color": "button_color", - "font_family": "system", - }, - currency="usd", + billing_period=0, + checkout_styling={}, + currency="currency", custom_fields=[ { + "id": "id", "field_type": "text", "name": "name", - "id": "id", - "order": 42, + "order": 0, "placeholder": "placeholder", "required": True, } ], description="description", - expiration_days=42, - image={"id": "id"}, - initial_price=6.9, + expiration_days=0, + image={ + "id": "id", + "direct_upload_id": "direct_upload_id", + }, + initial_price=0, internal_notes="internal_notes", legacy_payment_method_controls=True, - metadata={"foo": "bar"}, + metadata={}, offer_cancel_discount=True, - override_tax_type="inclusive", + override_tax_type="override_tax_type", payment_method_configuration={ - "disabled": ["acss_debit"], - "enabled": ["acss_debit"], + "disabled": ["string"], + "enabled": ["string"], "include_platform_defaults": True, }, - renewal_price=6.9, - stock=42, - strike_through_initial_price=6.9, - strike_through_renewal_price=6.9, + renewal_price=0, + stock=0, + strike_through_initial_price=0, + strike_through_renewal_price=0, three_ds_level="mandate_challenge", title="title", - trial_period_days=42, + trial_period_days=0, unlimited_stock=True, - visibility="visible", + visibility="visibility", ) assert_matches_type(Plan, plan, path=["response"]) @@ -539,7 +524,7 @@ async def test_method_update_with_all_params(self, async_client: AsyncWhop) -> N @parametrize async def test_raw_response_update(self, async_client: AsyncWhop) -> None: response = await async_client.plans.with_raw_response.update( - id="plan_xxxxxxxxxxxxx", + id="id", ) assert response.is_closed is True @@ -551,7 +536,7 @@ async def test_raw_response_update(self, async_client: AsyncWhop) -> None: @parametrize async def test_streaming_response_update(self, async_client: AsyncWhop) -> None: async with async_client.plans.with_streaming_response.update( - id="plan_xxxxxxxxxxxxx", + id="id", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -573,7 +558,7 @@ async def test_path_params_update(self, async_client: AsyncWhop) -> None: @parametrize async def test_method_list(self, async_client: AsyncWhop) -> None: plan = await async_client.plans.list( - company_id="biz_xxxxxxxxxxxxxx", + company_id="company_id", ) assert_matches_type(AsyncCursorPage[PlanListResponse], plan, path=["response"]) @@ -581,19 +566,19 @@ async def test_method_list(self, async_client: AsyncWhop) -> None: @parametrize async def test_method_list_with_all_params(self, async_client: AsyncWhop) -> None: plan = await async_client.plans.list( - company_id="biz_xxxxxxxxxxxxxx", + company_id="company_id", after="after", before="before", - created_after=parse_datetime("2023-12-01T05:00:00.401Z"), - created_before=parse_datetime("2023-12-01T05:00:00.401Z"), + created_after="created_after", + created_before="created_before", direction="asc", - first=42, - last=42, + first=0, + last=0, order="id", - plan_types=["renewal"], + plan_types=["string"], product_ids=["string"], - release_methods=["buy_now"], - visibilities=["visible"], + release_methods=["string"], + visibilities=["string"], ) assert_matches_type(AsyncCursorPage[PlanListResponse], plan, path=["response"]) @@ -601,7 +586,7 @@ async def test_method_list_with_all_params(self, async_client: AsyncWhop) -> Non @parametrize async def test_raw_response_list(self, async_client: AsyncWhop) -> None: response = await async_client.plans.with_raw_response.list( - company_id="biz_xxxxxxxxxxxxxx", + company_id="company_id", ) assert response.is_closed is True @@ -613,7 +598,7 @@ async def test_raw_response_list(self, async_client: AsyncWhop) -> None: @parametrize async def test_streaming_response_list(self, async_client: AsyncWhop) -> None: async with async_client.plans.with_streaming_response.list( - company_id="biz_xxxxxxxxxxxxxx", + company_id="company_id", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -627,7 +612,7 @@ async def test_streaming_response_list(self, async_client: AsyncWhop) -> None: @parametrize async def test_method_delete(self, async_client: AsyncWhop) -> None: plan = await async_client.plans.delete( - "plan_xxxxxxxxxxxxx", + "id", ) assert_matches_type(PlanDeleteResponse, plan, path=["response"]) @@ -635,7 +620,7 @@ async def test_method_delete(self, async_client: AsyncWhop) -> None: @parametrize async def test_raw_response_delete(self, async_client: AsyncWhop) -> None: response = await async_client.plans.with_raw_response.delete( - "plan_xxxxxxxxxxxxx", + "id", ) assert response.is_closed is True @@ -647,7 +632,7 @@ async def test_raw_response_delete(self, async_client: AsyncWhop) -> None: @parametrize async def test_streaming_response_delete(self, async_client: AsyncWhop) -> None: async with async_client.plans.with_streaming_response.delete( - "plan_xxxxxxxxxxxxx", + "id", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" From c869b86b96dc80292b8915a8c77a6147b8db0259 Mon Sep 17 00:00:00 2001 From: stlc-bot Date: Wed, 10 Jun 2026 04:41:11 +0000 Subject: [PATCH 015/109] Add all-time date range to ads reporting Stainless-Generated-From: 4952bd9f64623742929f8c34cf3e9f0579fcaf0f --- src/whop_sdk/resources/ad_reports.py | 18 ++++++++---------- .../types/ad_report_retrieve_response.py | 4 ++-- src/whop_sdk/types/granularities.py | 2 +- tests/api_resources/test_ad_reports.py | 4 ++-- 4 files changed, 13 insertions(+), 15 deletions(-) diff --git a/src/whop_sdk/resources/ad_reports.py b/src/whop_sdk/resources/ad_reports.py index 22dc0a58..232bd119 100644 --- a/src/whop_sdk/resources/ad_reports.py +++ b/src/whop_sdk/resources/ad_reports.py @@ -70,11 +70,10 @@ def retrieve( """Performance report for a company, ad campaigns, ad groups, or ads. Always - returns aggregate `summary` totals summed across the scope. Set `granularity` - (`daily`/`hourly`) to additionally get a time series, or set `breakdown` - (`campaign`/`ad_group`/`ad`) to additionally get per-entity rows inside the - requested scope. Exactly one of `companyId`, `adCampaignIds`, `adGroupIds`, or - `adIds` must be provided. + returns aggregate `summary` totals summed across the scope. Set `granularity` to + additionally get a time series, or set `breakdown` (`campaign`/`ad_group`/`ad`) + to additionally get per-entity rows inside the requested scope. Exactly one of + `companyId`, `adCampaignIds`, `adGroupIds`, or `adIds` must be provided. Required permissions: @@ -183,11 +182,10 @@ async def retrieve( """Performance report for a company, ad campaigns, ad groups, or ads. Always - returns aggregate `summary` totals summed across the scope. Set `granularity` - (`daily`/`hourly`) to additionally get a time series, or set `breakdown` - (`campaign`/`ad_group`/`ad`) to additionally get per-entity rows inside the - requested scope. Exactly one of `companyId`, `adCampaignIds`, `adGroupIds`, or - `adIds` must be provided. + returns aggregate `summary` totals summed across the scope. Set `granularity` to + additionally get a time series, or set `breakdown` (`campaign`/`ad_group`/`ad`) + to additionally get per-entity rows inside the requested scope. Exactly one of + `companyId`, `adCampaignIds`, `adGroupIds`, or `adIds` must be provided. Required permissions: diff --git a/src/whop_sdk/types/ad_report_retrieve_response.py b/src/whop_sdk/types/ad_report_retrieve_response.py index 7d27474e..7157cbef 100644 --- a/src/whop_sdk/types/ad_report_retrieve_response.py +++ b/src/whop_sdk/types/ad_report_retrieve_response.py @@ -36,7 +36,7 @@ class BreakdownGranularity(BaseModel): """Clicks in this bucket.""" granularity: Granularities - """The bucket size of this row (`daily` or `hourly`).""" + """The bucket size of this row (`hourly`, `daily`, `weekly`, or `monthly`).""" impressions: int """Impressions in this bucket.""" @@ -173,7 +173,7 @@ class Granularity(BaseModel): """Clicks in this bucket.""" granularity: Granularities - """The bucket size of this row (`daily` or `hourly`).""" + """The bucket size of this row (`hourly`, `daily`, `weekly`, or `monthly`).""" impressions: int """Impressions in this bucket.""" diff --git a/src/whop_sdk/types/granularities.py b/src/whop_sdk/types/granularities.py index 1a199166..62928b01 100644 --- a/src/whop_sdk/types/granularities.py +++ b/src/whop_sdk/types/granularities.py @@ -4,4 +4,4 @@ __all__ = ["Granularities"] -Granularities: TypeAlias = Literal["daily", "hourly"] +Granularities: TypeAlias = Literal["hourly", "daily", "weekly", "monthly"] diff --git a/tests/api_resources/test_ad_reports.py b/tests/api_resources/test_ad_reports.py index da7dc2be..861f56da 100644 --- a/tests/api_resources/test_ad_reports.py +++ b/tests/api_resources/test_ad_reports.py @@ -39,7 +39,7 @@ def test_method_retrieve_with_all_params(self, client: Whop) -> None: breakdown="campaign", company_id="biz_xxxxxxxxxxxxxx", currency="currency", - granularity="daily", + granularity="hourly", ) assert_matches_type(AdReportRetrieveResponse, ad_report, path=["response"]) @@ -98,7 +98,7 @@ async def test_method_retrieve_with_all_params(self, async_client: AsyncWhop) -> breakdown="campaign", company_id="biz_xxxxxxxxxxxxxx", currency="currency", - granularity="daily", + granularity="hourly", ) assert_matches_type(AdReportRetrieveResponse, ad_report, path=["response"]) From 9bc9baf4e5ea4e15a5ae67dbe77e9c7ab27e7d82 Mon Sep 17 00:00:00 2001 From: stlc-bot Date: Wed, 10 Jun 2026 04:57:12 +0000 Subject: [PATCH 016/109] Track stablecoin withdrawals via the native financial-activity API Stainless-Generated-From: 924eacd3c5813a4d6343aea396619702c228f5fa --- .../types/financial_activity_list_response.py | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/src/whop_sdk/types/financial_activity_list_response.py b/src/whop_sdk/types/financial_activity_list_response.py index 1d684205..2a6a20e4 100644 --- a/src/whop_sdk/types/financial_activity_list_response.py +++ b/src/whop_sdk/types/financial_activity_list_response.py @@ -25,6 +25,7 @@ "DataResourceUnionMember3Card", "DataResourceUnionMember4", "DataSource", + "DataSourcePayoutDestination", "PageInfo", ] @@ -159,11 +160,49 @@ class DataResourceUnionMember4(BaseModel): ] +class DataSourcePayoutDestination(BaseModel): + """Payout destination display info (withdrawal sources only).""" + + icon_url: Optional[str] = None + + payer_name: Optional[str] = None + + class DataSource(BaseModel): id: str object: str + created_at: Optional[datetime] = None + """ + Withdrawal creation time as an ISO 8601 timestamp (withdrawal sources only; + requires payout:withdrawal:read). + """ + + estimated_arrival: Optional[datetime] = None + """ + Estimated arrival as an ISO 8601 timestamp (withdrawal sources only; requires + payout:withdrawal:read). + """ + + payer_name: Optional[str] = None + """ + Name of the entity processing the payout (withdrawal sources only; requires + payout:withdrawal:read). + """ + + payout_destination: Optional[DataSourcePayoutDestination] = None + """Payout destination display info (withdrawal sources only).""" + + payout_token_nickname: Optional[str] = None + """Saved payout destination nickname (withdrawal sources only).""" + + status: Optional[str] = None + """ + Withdrawal lifecycle status (withdrawal sources only; requires + payout:withdrawal:read). + """ + if TYPE_CHECKING: # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a # value to this field, so for compatibility we avoid doing it at runtime. @@ -183,6 +222,8 @@ class Data(BaseModel): amount: str """Signed amount in the currency's smallest precision units.""" + created_at: Optional[datetime] = None + currency: DataCurrency line_type: str From acba466a967bec3c83753540b6970087803b8109 Mon Sep 17 00:00:00 2001 From: stlc-bot Date: Wed, 10 Jun 2026 05:11:33 +0000 Subject: [PATCH 017/109] Rename plans API company fields to account Stainless-Generated-From: 2f60357d887d1d68900e25ecefc4bf5f6d30813c --- README.md | 4 +-- src/whop_sdk/_client.py | 4 +-- src/whop_sdk/resources/plans.py | 44 ++++++++++++------------ src/whop_sdk/types/plan_create_params.py | 16 ++++----- src/whop_sdk/types/plan_list_params.py | 4 +-- src/whop_sdk/types/plan_list_response.py | 12 +++---- src/whop_sdk/types/plan_update_params.py | 4 +-- src/whop_sdk/types/shared/plan.py | 12 +++---- tests/api_resources/test_plans.py | 20 +++++------ 9 files changed, 60 insertions(+), 60 deletions(-) diff --git a/README.md b/README.md index f4049761..c1218d8e 100644 --- a/README.md +++ b/README.md @@ -13,8 +13,8 @@ It is generated with [Stainless](https://www.stainless.com/). Use the Whop MCP Server to enable AI assistants to interact with this API, allowing them to explore endpoints, make test requests, and use documentation to help integrate this SDK into your application. -[![Add to Cursor](https://cursor.com/deeplink/mcp-install-dark.svg)](https://cursor.com/en-US/install-mcp?name=%40whop%2Fmcp&config=eyJjb21tYW5kIjoibnB4IiwiYXJncyI6WyIteSIsIkB3aG9wL21jcCJdLCJlbnYiOnsiV0hPUF9BUElfS0VZIjoiTXkgQVBJIEtleSIsIldIT1BfV0VCSE9PS19TRUNSRVQiOiJNeSBXZWJob29rIEtleSIsIldIT1BfQVBQX0lEIjoiYXBwX3h4eHh4eHh4eHh4eHh4IiwiV0hPUF9BUElfVkVSU0lPTiI6IjIwMjYtMDYtMDgifX0) -[![Install in VS Code](https://img.shields.io/badge/_-Add_to_VS_Code-blue?style=for-the-badge&logo=data:image/svg%2bxml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGZpbGw9Im5vbmUiIHZpZXdCb3g9IjAgMCA0MCA0MCI+PHBhdGggZmlsbD0iI0VFRSIgZmlsbC1ydWxlPSJldmVub2RkIiBkPSJNMzAuMjM1IDM5Ljg4NGEyLjQ5MSAyLjQ5MSAwIDAgMS0xLjc4MS0uNzNMMTIuNyAyNC43OGwtMy40NiAyLjYyNC0zLjQwNiAyLjU4MmExLjY2NSAxLjY2NSAwIDAgMS0xLjA4Mi4zMzggMS42NjQgMS42NjQgMCAwIDEtMS4wNDYtLjQzMWwtMi4yLTJhMS42NjYgMS42NjYgMCAwIDEgMC0yLjQ2M0w3LjQ1OCAyMCA0LjY3IDE3LjQ1MyAxLjUwNyAxNC41N2ExLjY2NSAxLjY2NSAwIDAgMSAwLTIuNDYzbDIuMi0yYTEuNjY1IDEuNjY1IDAgMCAxIDIuMTMtLjA5N2w2Ljg2MyA1LjIwOUwyOC40NTIuODQ0YTIuNDg4IDIuNDg4IDAgMCAxIDEuODQxLS43MjljLjM1MS4wMDkuNjk5LjA5MSAxLjAxOS4yNDVsOC4yMzYgMy45NjFhMi41IDIuNSAwIDAgMSAxLjQxNSAyLjI1M3YuMDk5LS4wNDVWMzMuMzd2LS4wNDUuMDk1YTIuNTAxIDIuNTAxIDAgMCAxLTEuNDE2IDIuMjU3bC04LjIzNSAzLjk2MWEyLjQ5MiAyLjQ5MiAwIDAgMS0xLjA3Ny4yNDZabS43MTYtMjguOTQ3LTExLjk0OCA5LjA2MiAxMS45NTIgOS4wNjUtLjAwNC0xOC4xMjdaIi8+PC9zdmc+)](https://vscode.stainless.com/mcp/%7B%22name%22%3A%22%40whop%2Fmcp%22%2C%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22-y%22%2C%22%40whop%2Fmcp%22%5D%2C%22env%22%3A%7B%22WHOP_API_KEY%22%3A%22My%20API%20Key%22%2C%22WHOP_WEBHOOK_SECRET%22%3A%22My%20Webhook%20Key%22%2C%22WHOP_APP_ID%22%3A%22app_xxxxxxxxxxxxxx%22%2C%22WHOP_API_VERSION%22%3A%222026-06-08%22%7D%7D) +[![Add to Cursor](https://cursor.com/deeplink/mcp-install-dark.svg)](https://cursor.com/en-US/install-mcp?name=%40whop%2Fmcp&config=eyJjb21tYW5kIjoibnB4IiwiYXJncyI6WyIteSIsIkB3aG9wL21jcCJdLCJlbnYiOnsiV0hPUF9BUElfS0VZIjoiTXkgQVBJIEtleSIsIldIT1BfV0VCSE9PS19TRUNSRVQiOiJNeSBXZWJob29rIEtleSIsIldIT1BfQVBQX0lEIjoiYXBwX3h4eHh4eHh4eHh4eHh4IiwiV0hPUF9BUElfVkVSU0lPTiI6IjIwMjYtMDYtMDkifX0) +[![Install in VS Code](https://img.shields.io/badge/_-Add_to_VS_Code-blue?style=for-the-badge&logo=data:image/svg%2bxml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGZpbGw9Im5vbmUiIHZpZXdCb3g9IjAgMCA0MCA0MCI+PHBhdGggZmlsbD0iI0VFRSIgZmlsbC1ydWxlPSJldmVub2RkIiBkPSJNMzAuMjM1IDM5Ljg4NGEyLjQ5MSAyLjQ5MSAwIDAgMS0xLjc4MS0uNzNMMTIuNyAyNC43OGwtMy40NiAyLjYyNC0zLjQwNiAyLjU4MmExLjY2NSAxLjY2NSAwIDAgMS0xLjA4Mi4zMzggMS42NjQgMS42NjQgMCAwIDEtMS4wNDYtLjQzMWwtMi4yLTJhMS42NjYgMS42NjYgMCAwIDEgMC0yLjQ2M0w3LjQ1OCAyMCA0LjY3IDE3LjQ1MyAxLjUwNyAxNC41N2ExLjY2NSAxLjY2NSAwIDAgMSAwLTIuNDYzbDIuMi0yYTEuNjY1IDEuNjY1IDAgMCAxIDIuMTMtLjA5N2w2Ljg2MyA1LjIwOUwyOC40NTIuODQ0YTIuNDg4IDIuNDg4IDAgMCAxIDEuODQxLS43MjljLjM1MS4wMDkuNjk5LjA5MSAxLjAxOS4yNDVsOC4yMzYgMy45NjFhMi41IDIuNSAwIDAgMSAxLjQxNSAyLjI1M3YuMDk5LS4wNDVWMzMuMzd2LS4wNDUuMDk1YTIuNTAxIDIuNTAxIDAgMCAxLTEuNDE2IDIuMjU3bC04LjIzNSAzLjk2MWEyLjQ5MiAyLjQ5MiAwIDAgMS0xLjA3Ny4yNDZabS43MTYtMjguOTQ3LTExLjk0OCA5LjA2MiAxMS45NTIgOS4wNjUtLjAwNC0xOC4xMjdaIi8+PC9zdmc+)](https://vscode.stainless.com/mcp/%7B%22name%22%3A%22%40whop%2Fmcp%22%2C%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22-y%22%2C%22%40whop%2Fmcp%22%5D%2C%22env%22%3A%7B%22WHOP_API_KEY%22%3A%22My%20API%20Key%22%2C%22WHOP_WEBHOOK_SECRET%22%3A%22My%20Webhook%20Key%22%2C%22WHOP_APP_ID%22%3A%22app_xxxxxxxxxxxxxx%22%2C%22WHOP_API_VERSION%22%3A%222026-06-09%22%7D%7D) > Note: You may need to set environment variables in your MCP client. diff --git a/src/whop_sdk/_client.py b/src/whop_sdk/_client.py index a12ecbe2..89aa63fc 100644 --- a/src/whop_sdk/_client.py +++ b/src/whop_sdk/_client.py @@ -233,7 +233,7 @@ def __init__( self.app_id = app_id if version is None: - version = os.environ.get("WHOP_API_VERSION") or "2026-06-08" + version = os.environ.get("WHOP_API_VERSION") or "2026-06-09" self.version = version if base_url is None: @@ -881,7 +881,7 @@ def __init__( self.app_id = app_id if version is None: - version = os.environ.get("WHOP_API_VERSION") or "2026-06-08" + version = os.environ.get("WHOP_API_VERSION") or "2026-06-09" self.version = version if base_url is None: diff --git a/src/whop_sdk/resources/plans.py b/src/whop_sdk/resources/plans.py index d11fe6d5..dab4606f 100644 --- a/src/whop_sdk/resources/plans.py +++ b/src/whop_sdk/resources/plans.py @@ -51,10 +51,10 @@ def create( self, *, product_id: str, + account_id: str | Omit = omit, adaptive_pricing_enabled: Optional[bool] | Omit = omit, billing_period: Optional[int] | Omit = omit, checkout_styling: Optional[object] | Omit = omit, - company_id: str | Omit = omit, currency: str | Omit = omit, custom_fields: Optional[Iterable[plan_create_params.CustomField]] | Omit = omit, description: Optional[str] | Omit = omit, @@ -91,6 +91,9 @@ def create( Args: product_id: The unique identifier of the product to attach this plan to. + account_id: The unique identifier of the account to create this plan for. Defaults to the + caller's account. + 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 @@ -98,9 +101,6 @@ def create( checkout_styling: Checkout styling overrides for this plan. - company_id: The unique identifier of the company to create this plan for. Defaults to the - caller's company. - currency: The three-letter ISO currency code for the plan's pricing. Defaults to USD. custom_fields: An array of custom field definitions to collect from customers at checkout. @@ -125,7 +125,7 @@ def create( override_tax_type: Override the default tax classification for this specific plan. payment_method_configuration: Explicit payment method configuration for the plan. When not provided, the - company's defaults apply. + account's defaults apply. plan_type: The billing type of the plan, such as one_time or renewal. @@ -162,10 +162,10 @@ def create( body=maybe_transform( { "product_id": product_id, + "account_id": account_id, "adaptive_pricing_enabled": adaptive_pricing_enabled, "billing_period": billing_period, "checkout_styling": checkout_styling, - "company_id": company_id, "currency": currency, "custom_fields": custom_fields, "description": description, @@ -302,7 +302,7 @@ def update( override_tax_type: Override the default tax classification for this specific plan. payment_method_configuration: Explicit payment method configuration for the plan. When not provided, the - company's defaults apply. + account's defaults apply. renewal_price: The amount charged each billing period for recurring plans, in the plan's currency. @@ -374,7 +374,7 @@ def update( def list( self, *, - company_id: str, + account_id: str, after: str | Omit = omit, before: str | Omit = omit, created_after: str | Omit = omit, @@ -395,11 +395,11 @@ def list( timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> SyncCursorPage[PlanListResponse]: """ - Returns a paginated list of plans belonging to a company, with optional + Returns a paginated list of plans belonging to an account, with optional filtering by visibility, type, release method, and product. Args: - company_id: The unique identifier of the company to list plans for. + account_id: The unique identifier of the account to list plans for. after: A cursor; returns plans after this position. @@ -443,7 +443,7 @@ def list( timeout=timeout, query=maybe_transform( { - "company_id": company_id, + "account_id": account_id, "after": after, "before": before, "created_after": created_after, @@ -523,10 +523,10 @@ async def create( self, *, product_id: str, + account_id: str | Omit = omit, adaptive_pricing_enabled: Optional[bool] | Omit = omit, billing_period: Optional[int] | Omit = omit, checkout_styling: Optional[object] | Omit = omit, - company_id: str | Omit = omit, currency: str | Omit = omit, custom_fields: Optional[Iterable[plan_create_params.CustomField]] | Omit = omit, description: Optional[str] | Omit = omit, @@ -563,6 +563,9 @@ async def create( Args: product_id: The unique identifier of the product to attach this plan to. + account_id: The unique identifier of the account to create this plan for. Defaults to the + caller's account. + 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 @@ -570,9 +573,6 @@ async def create( checkout_styling: Checkout styling overrides for this plan. - company_id: The unique identifier of the company to create this plan for. Defaults to the - caller's company. - currency: The three-letter ISO currency code for the plan's pricing. Defaults to USD. custom_fields: An array of custom field definitions to collect from customers at checkout. @@ -597,7 +597,7 @@ async def create( override_tax_type: Override the default tax classification for this specific plan. payment_method_configuration: Explicit payment method configuration for the plan. When not provided, the - company's defaults apply. + account's defaults apply. plan_type: The billing type of the plan, such as one_time or renewal. @@ -634,10 +634,10 @@ async def create( body=await async_maybe_transform( { "product_id": product_id, + "account_id": account_id, "adaptive_pricing_enabled": adaptive_pricing_enabled, "billing_period": billing_period, "checkout_styling": checkout_styling, - "company_id": company_id, "currency": currency, "custom_fields": custom_fields, "description": description, @@ -774,7 +774,7 @@ async def update( override_tax_type: Override the default tax classification for this specific plan. payment_method_configuration: Explicit payment method configuration for the plan. When not provided, the - company's defaults apply. + account's defaults apply. renewal_price: The amount charged each billing period for recurring plans, in the plan's currency. @@ -846,7 +846,7 @@ async def update( def list( self, *, - company_id: str, + account_id: str, after: str | Omit = omit, before: str | Omit = omit, created_after: str | Omit = omit, @@ -867,11 +867,11 @@ def list( timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> AsyncPaginator[PlanListResponse, AsyncCursorPage[PlanListResponse]]: """ - Returns a paginated list of plans belonging to a company, with optional + Returns a paginated list of plans belonging to an account, with optional filtering by visibility, type, release method, and product. Args: - company_id: The unique identifier of the company to list plans for. + account_id: The unique identifier of the account to list plans for. after: A cursor; returns plans after this position. @@ -915,7 +915,7 @@ def list( timeout=timeout, query=maybe_transform( { - "company_id": company_id, + "account_id": account_id, "after": after, "before": before, "created_after": created_after, diff --git a/src/whop_sdk/types/plan_create_params.py b/src/whop_sdk/types/plan_create_params.py index 221ddb1c..eee58fe6 100644 --- a/src/whop_sdk/types/plan_create_params.py +++ b/src/whop_sdk/types/plan_create_params.py @@ -14,6 +14,12 @@ class PlanCreateParams(TypedDict, total=False): product_id: Required[str] """The unique identifier of the product to attach this plan to.""" + account_id: str + """The unique identifier of the account to create this plan for. + + Defaults to the caller's account. + """ + adaptive_pricing_enabled: Optional[bool] """Whether this plan accepts local currency payments via adaptive pricing.""" @@ -26,12 +32,6 @@ class PlanCreateParams(TypedDict, total=False): checkout_styling: Optional[object] """Checkout styling overrides for this plan.""" - company_id: str - """The unique identifier of the company to create this plan for. - - Defaults to the caller's company. - """ - currency: str """The three-letter ISO currency code for the plan's pricing. Defaults to USD.""" @@ -74,7 +74,7 @@ class PlanCreateParams(TypedDict, total=False): payment_method_configuration: Optional[PaymentMethodConfiguration] """Explicit payment method configuration for the plan. - When not provided, the company's defaults apply. + When not provided, the account's defaults apply. """ plan_type: str @@ -145,7 +145,7 @@ class Image(TypedDict, total=False): class PaymentMethodConfiguration(TypedDict, total=False): """Explicit payment method configuration for the plan. - When not provided, the company's defaults apply. + When not provided, the account's defaults apply. """ disabled: SequenceNotStr[str] diff --git a/src/whop_sdk/types/plan_list_params.py b/src/whop_sdk/types/plan_list_params.py index 576a524e..22581252 100644 --- a/src/whop_sdk/types/plan_list_params.py +++ b/src/whop_sdk/types/plan_list_params.py @@ -10,8 +10,8 @@ class PlanListParams(TypedDict, total=False): - company_id: Required[str] - """The unique identifier of the company to list plans for.""" + account_id: Required[str] + """The unique identifier of the account to list plans for.""" after: str """A cursor; returns plans after this position.""" diff --git a/src/whop_sdk/types/plan_list_response.py b/src/whop_sdk/types/plan_list_response.py index 3f2ce488..ac7af37a 100644 --- a/src/whop_sdk/types/plan_list_response.py +++ b/src/whop_sdk/types/plan_list_response.py @@ -11,18 +11,18 @@ class PlanListResponse(BaseModel): id: str """The ID of the plan, which will look like plan\\__******\\********""" + account: Optional[object] = None + """The account that sells this plan, an object with an id and title. + + Null for standalone invoice plans + """ + adaptive_pricing_enabled: bool """Whether this plan accepts local currency payments via adaptive pricing""" billing_period: Optional[float] = None """The number of days between recurring charges. Null for one-time plans""" - company: Optional[object] = None - """The company that sells this plan, an object with an id and title. - - Null for standalone invoice plans - """ - created_at: str """When the plan was created, as an ISO 8601 timestamp""" diff --git a/src/whop_sdk/types/plan_update_params.py b/src/whop_sdk/types/plan_update_params.py index 2e96dd6b..c6e1d0ed 100644 --- a/src/whop_sdk/types/plan_update_params.py +++ b/src/whop_sdk/types/plan_update_params.py @@ -68,7 +68,7 @@ class PlanUpdateParams(TypedDict, total=False): payment_method_configuration: Optional[PaymentMethodConfiguration] """Explicit payment method configuration for the plan. - When not provided, the company's defaults apply. + When not provided, the account's defaults apply. """ renewal_price: Optional[float] @@ -136,7 +136,7 @@ class Image(TypedDict, total=False): class PaymentMethodConfiguration(TypedDict, total=False): """Explicit payment method configuration for the plan. - When not provided, the company's defaults apply. + When not provided, the account's defaults apply. """ disabled: SequenceNotStr[str] diff --git a/src/whop_sdk/types/shared/plan.py b/src/whop_sdk/types/shared/plan.py index 45a4b4ed..e708fec9 100644 --- a/src/whop_sdk/types/shared/plan.py +++ b/src/whop_sdk/types/shared/plan.py @@ -12,6 +12,12 @@ class Plan(BaseModel): id: str """The ID of the plan, which will look like plan\\__******\\********""" + account: Optional[object] = None + """The account that sells this plan, an object with an id and title. + + Null for standalone invoice plans + """ + adaptive_pricing_enabled: bool """Whether this plan accepts local currency payments via adaptive pricing""" @@ -21,12 +27,6 @@ class Plan(BaseModel): collect_tax: bool """Whether tax is collected on purchases of this plan""" - company: Optional[object] = None - """The company that sells this plan, an object with an id and title. - - Null for standalone invoice plans - """ - created_at: str """When the plan was created, as an ISO 8601 timestamp""" diff --git a/tests/api_resources/test_plans.py b/tests/api_resources/test_plans.py index 2ed798c1..e0ebeaaf 100644 --- a/tests/api_resources/test_plans.py +++ b/tests/api_resources/test_plans.py @@ -35,10 +35,10 @@ def test_method_create(self, client: Whop) -> None: def test_method_create_with_all_params(self, client: Whop) -> None: plan = client.plans.create( product_id="product_id", + account_id="account_id", adaptive_pricing_enabled=True, billing_period=0, checkout_styling={}, - company_id="company_id", currency="currency", custom_fields=[ { @@ -241,7 +241,7 @@ def test_path_params_update(self, client: Whop) -> None: @parametrize def test_method_list(self, client: Whop) -> None: plan = client.plans.list( - company_id="company_id", + account_id="account_id", ) assert_matches_type(SyncCursorPage[PlanListResponse], plan, path=["response"]) @@ -249,7 +249,7 @@ def test_method_list(self, client: Whop) -> None: @parametrize def test_method_list_with_all_params(self, client: Whop) -> None: plan = client.plans.list( - company_id="company_id", + account_id="account_id", after="after", before="before", created_after="created_after", @@ -269,7 +269,7 @@ def test_method_list_with_all_params(self, client: Whop) -> None: @parametrize def test_raw_response_list(self, client: Whop) -> None: response = client.plans.with_raw_response.list( - company_id="company_id", + account_id="account_id", ) assert response.is_closed is True @@ -281,7 +281,7 @@ def test_raw_response_list(self, client: Whop) -> None: @parametrize def test_streaming_response_list(self, client: Whop) -> None: with client.plans.with_streaming_response.list( - company_id="company_id", + account_id="account_id", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -352,10 +352,10 @@ async def test_method_create(self, async_client: AsyncWhop) -> None: async def test_method_create_with_all_params(self, async_client: AsyncWhop) -> None: plan = await async_client.plans.create( product_id="product_id", + account_id="account_id", adaptive_pricing_enabled=True, billing_period=0, checkout_styling={}, - company_id="company_id", currency="currency", custom_fields=[ { @@ -558,7 +558,7 @@ async def test_path_params_update(self, async_client: AsyncWhop) -> None: @parametrize async def test_method_list(self, async_client: AsyncWhop) -> None: plan = await async_client.plans.list( - company_id="company_id", + account_id="account_id", ) assert_matches_type(AsyncCursorPage[PlanListResponse], plan, path=["response"]) @@ -566,7 +566,7 @@ async def test_method_list(self, async_client: AsyncWhop) -> None: @parametrize async def test_method_list_with_all_params(self, async_client: AsyncWhop) -> None: plan = await async_client.plans.list( - company_id="company_id", + account_id="account_id", after="after", before="before", created_after="created_after", @@ -586,7 +586,7 @@ async def test_method_list_with_all_params(self, async_client: AsyncWhop) -> Non @parametrize async def test_raw_response_list(self, async_client: AsyncWhop) -> None: response = await async_client.plans.with_raw_response.list( - company_id="company_id", + account_id="account_id", ) assert response.is_closed is True @@ -598,7 +598,7 @@ async def test_raw_response_list(self, async_client: AsyncWhop) -> None: @parametrize async def test_streaming_response_list(self, async_client: AsyncWhop) -> None: async with async_client.plans.with_streaming_response.list( - company_id="company_id", + account_id="account_id", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" From 646d14efa035968387f45dce64f77c313b9a54c5 Mon Sep 17 00:00:00 2001 From: stlc-bot Date: Wed, 10 Jun 2026 05:40:12 +0000 Subject: [PATCH 018/109] feat(swaps): accept token symbols + add swap id to public swaps API Stainless-Generated-From: e841cfa5aa2de4c5fceb11eb77319972b12ed361 --- .stats.yml | 2 +- api.md | 10 +- src/whop_sdk/resources/swaps.py | 121 +++++++++++++++--- src/whop_sdk/types/__init__.py | 3 +- src/whop_sdk/types/swap_create_params.py | 4 +- .../types/swap_create_quote_params.py | 4 +- src/whop_sdk/types/swap_create_response.py | 3 + ...retrieve_params.py => swap_list_params.py} | 4 +- src/whop_sdk/types/swap_list_response.py | 26 ++++ src/whop_sdk/types/swap_retrieve_response.py | 2 + tests/api_resources/test_swaps.py | 97 +++++++++++++- 11 files changed, 240 insertions(+), 36 deletions(-) rename src/whop_sdk/types/{swap_retrieve_params.py => swap_list_params.py} (76%) create mode 100644 src/whop_sdk/types/swap_list_response.py diff --git a/.stats.yml b/.stats.yml index cd5b9cf9..ceccbf4a 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1 +1 @@ -configured_endpoints: 230 +configured_endpoints: 231 diff --git a/api.md b/api.md index 401d5f4f..3606425f 100644 --- a/api.md +++ b/api.md @@ -756,13 +756,19 @@ Methods: Types: ```python -from whop_sdk.types import SwapCreateResponse, SwapRetrieveResponse, SwapCreateQuoteResponse +from whop_sdk.types import ( + SwapCreateResponse, + SwapRetrieveResponse, + SwapListResponse, + SwapCreateQuoteResponse, +) ``` Methods: - client.swaps.create(\*\*params) -> SwapCreateResponse -- client.swaps.retrieve(\*\*params) -> SwapRetrieveResponse +- client.swaps.retrieve(id) -> SwapRetrieveResponse +- client.swaps.list(\*\*params) -> SwapListResponse - client.swaps.create_quote(\*\*params) -> SwapCreateQuoteResponse # Deposits diff --git a/src/whop_sdk/resources/swaps.py b/src/whop_sdk/resources/swaps.py index 771bf068..858cdb6c 100644 --- a/src/whop_sdk/resources/swaps.py +++ b/src/whop_sdk/resources/swaps.py @@ -6,9 +6,9 @@ import httpx -from ..types import swap_create_params, swap_retrieve_params, swap_create_quote_params +from ..types import swap_list_params, swap_create_params, swap_create_quote_params from .._types import Body, Omit, Query, Headers, NotGiven, omit, not_given -from .._utils import maybe_transform, async_maybe_transform +from .._utils import path_template, maybe_transform, async_maybe_transform from .._compat import cached_property from .._resource import SyncAPIResource, AsyncAPIResource from .._response import ( @@ -18,6 +18,7 @@ async_to_streamed_response_wrapper, ) from .._base_client import make_request_options +from ..types.swap_list_response import SwapListResponse from ..types.swap_create_response import SwapCreateResponse from ..types.swap_retrieve_response import SwapRetrieveResponse from ..types.swap_create_quote_response import SwapCreateQuoteResponse @@ -65,16 +66,16 @@ def create( """Executes a swap from the account's wallet. Runs asynchronously — poll GET - /swaps?account_id=... for status. + /swaps/{id} for status. Args: account_id: Business or user account ID (biz*\\** / user*\\**). amount: Input token amount. - from_token: Source token contract address. + from_token: Source token, by contract address or ticker symbol (e.g. "USDT"). - to_token: Destination token contract address. + to_token: Destination token, by contract address or ticker symbol (e.g. "XAUT"). extra_headers: Send extra headers @@ -106,8 +107,8 @@ def create( def retrieve( self, + id: str, *, - account_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, @@ -116,7 +117,41 @@ def retrieve( timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> SwapRetrieveResponse: """ - Returns the status of the account's in-flight or most recent swap. + Returns the status of a specific swap, by the id returned from POST /swaps. + + 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("/swaps/{id}", id=id), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=SwapRetrieveResponse, + ) + + def list( + self, + *, + account_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, + ) -> SwapListResponse: + """ + Lists the account's swaps — currently its in-flight or most recent swap, so zero + or one rows. Args: account_id: Business or user account ID (biz*\\** / user*\\**). @@ -136,9 +171,9 @@ def retrieve( extra_query=extra_query, extra_body=extra_body, timeout=timeout, - query=maybe_transform({"account_id": account_id}, swap_retrieve_params.SwapRetrieveParams), + query=maybe_transform({"account_id": account_id}, swap_list_params.SwapListParams), ), - cast_to=SwapRetrieveResponse, + cast_to=SwapListResponse, ) def create_quote( @@ -167,9 +202,9 @@ def create_quote( Args: amount: Input token amount. - from_token: Source token contract address. + from_token: Source token, by contract address or ticker symbol (e.g. "USDT"). - to_token: Destination token contract address. + to_token: Destination token, by contract address or ticker symbol (e.g. "XAUT"). extra_headers: Send extra headers @@ -242,16 +277,16 @@ async def create( """Executes a swap from the account's wallet. Runs asynchronously — poll GET - /swaps?account_id=... for status. + /swaps/{id} for status. Args: account_id: Business or user account ID (biz*\\** / user*\\**). amount: Input token amount. - from_token: Source token contract address. + from_token: Source token, by contract address or ticker symbol (e.g. "USDT"). - to_token: Destination token contract address. + to_token: Destination token, by contract address or ticker symbol (e.g. "XAUT"). extra_headers: Send extra headers @@ -283,8 +318,8 @@ async def create( async def retrieve( self, + id: str, *, - account_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, @@ -293,7 +328,41 @@ async def retrieve( timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> SwapRetrieveResponse: """ - Returns the status of the account's in-flight or most recent swap. + Returns the status of a specific swap, by the id returned from POST /swaps. + + 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("/swaps/{id}", id=id), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=SwapRetrieveResponse, + ) + + async def list( + self, + *, + account_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, + ) -> SwapListResponse: + """ + Lists the account's swaps — currently its in-flight or most recent swap, so zero + or one rows. Args: account_id: Business or user account ID (biz*\\** / user*\\**). @@ -313,9 +382,9 @@ async def retrieve( extra_query=extra_query, extra_body=extra_body, timeout=timeout, - query=await async_maybe_transform({"account_id": account_id}, swap_retrieve_params.SwapRetrieveParams), + query=await async_maybe_transform({"account_id": account_id}, swap_list_params.SwapListParams), ), - cast_to=SwapRetrieveResponse, + cast_to=SwapListResponse, ) async def create_quote( @@ -344,9 +413,9 @@ async def create_quote( Args: amount: Input token amount. - from_token: Source token contract address. + from_token: Source token, by contract address or ticker symbol (e.g. "USDT"). - to_token: Destination token contract address. + to_token: Destination token, by contract address or ticker symbol (e.g. "XAUT"). extra_headers: Send extra headers @@ -389,6 +458,9 @@ def __init__(self, swaps: SwapsResource) -> None: self.retrieve = to_raw_response_wrapper( swaps.retrieve, ) + self.list = to_raw_response_wrapper( + swaps.list, + ) self.create_quote = to_raw_response_wrapper( swaps.create_quote, ) @@ -404,6 +476,9 @@ def __init__(self, swaps: AsyncSwapsResource) -> None: self.retrieve = async_to_raw_response_wrapper( swaps.retrieve, ) + self.list = async_to_raw_response_wrapper( + swaps.list, + ) self.create_quote = async_to_raw_response_wrapper( swaps.create_quote, ) @@ -419,6 +494,9 @@ def __init__(self, swaps: SwapsResource) -> None: self.retrieve = to_streamed_response_wrapper( swaps.retrieve, ) + self.list = to_streamed_response_wrapper( + swaps.list, + ) self.create_quote = to_streamed_response_wrapper( swaps.create_quote, ) @@ -434,6 +512,9 @@ def __init__(self, swaps: AsyncSwapsResource) -> None: self.retrieve = async_to_streamed_response_wrapper( swaps.retrieve, ) + self.list = async_to_streamed_response_wrapper( + swaps.list, + ) self.create_quote = async_to_streamed_response_wrapper( swaps.create_quote, ) diff --git a/src/whop_sdk/types/__init__.py b/src/whop_sdk/types/__init__.py index 9e70ccd3..45aabfc3 100644 --- a/src/whop_sdk/types/__init__.py +++ b/src/whop_sdk/types/__init__.py @@ -111,6 +111,7 @@ from .lead_list_params import LeadListParams as LeadListParams from .payment_provider import PaymentProvider as PaymentProvider from .plan_list_params import PlanListParams as PlanListParams +from .swap_list_params import SwapListParams as SwapListParams from .user_list_params import UserListParams as UserListParams from .app_create_params import AppCreateParams as AppCreateParams from .app_list_response import AppListResponse as AppListResponse @@ -138,6 +139,7 @@ from .refund_list_params import RefundListParams as RefundListParams from .review_list_params import ReviewListParams as ReviewListParams from .swap_create_params import SwapCreateParams as SwapCreateParams +from .swap_list_response import SwapListResponse as SwapListResponse from .user_update_params import UserUpdateParams as UserUpdateParams from .wallet_send_params import WalletSendParams as WalletSendParams from .account_list_params import AccountListParams as AccountListParams @@ -177,7 +179,6 @@ from .shipment_list_params import ShipmentListParams as ShipmentListParams from .social_link_websites import SocialLinkWebsites as SocialLinkWebsites from .swap_create_response import SwapCreateResponse as SwapCreateResponse -from .swap_retrieve_params import SwapRetrieveParams as SwapRetrieveParams from .transfer_list_params import TransferListParams as TransferListParams from .unwrap_webhook_event import UnwrapWebhookEvent as UnwrapWebhookEvent from .user_retrieve_params import UserRetrieveParams as UserRetrieveParams diff --git a/src/whop_sdk/types/swap_create_params.py b/src/whop_sdk/types/swap_create_params.py index ab28ef99..f8335f49 100644 --- a/src/whop_sdk/types/swap_create_params.py +++ b/src/whop_sdk/types/swap_create_params.py @@ -16,10 +16,10 @@ class SwapCreateParams(TypedDict, total=False): """Input token amount.""" from_token: Required[str] - """Source token contract address.""" + """Source token, by contract address or ticker symbol (e.g. "USDT").""" to_token: Required[str] - """Destination token contract address.""" + """Destination token, by contract address or ticker symbol (e.g. "XAUT").""" from_chain: Union[str, int, None] diff --git a/src/whop_sdk/types/swap_create_quote_params.py b/src/whop_sdk/types/swap_create_quote_params.py index 9fc72c96..6343bbde 100644 --- a/src/whop_sdk/types/swap_create_quote_params.py +++ b/src/whop_sdk/types/swap_create_quote_params.py @@ -13,10 +13,10 @@ class SwapCreateQuoteParams(TypedDict, total=False): """Input token amount.""" from_token: Required[str] - """Source token contract address.""" + """Source token, by contract address or ticker symbol (e.g. "USDT").""" to_token: Required[str] - """Destination token contract address.""" + """Destination token, by contract address or ticker symbol (e.g. "XAUT").""" from_address: Optional[str] diff --git a/src/whop_sdk/types/swap_create_response.py b/src/whop_sdk/types/swap_create_response.py index 8829f7b9..10a62e85 100644 --- a/src/whop_sdk/types/swap_create_response.py +++ b/src/whop_sdk/types/swap_create_response.py @@ -9,6 +9,9 @@ class SwapCreateResponse(BaseModel): + id: str + """Swap ID — poll GET /swaps/{id} for status.""" + account_id: str object: Literal["swap"] diff --git a/src/whop_sdk/types/swap_retrieve_params.py b/src/whop_sdk/types/swap_list_params.py similarity index 76% rename from src/whop_sdk/types/swap_retrieve_params.py rename to src/whop_sdk/types/swap_list_params.py index 8607fab8..8a5123a2 100644 --- a/src/whop_sdk/types/swap_retrieve_params.py +++ b/src/whop_sdk/types/swap_list_params.py @@ -4,9 +4,9 @@ from typing_extensions import Required, TypedDict -__all__ = ["SwapRetrieveParams"] +__all__ = ["SwapListParams"] -class SwapRetrieveParams(TypedDict, total=False): +class SwapListParams(TypedDict, total=False): account_id: Required[str] """Business or user account ID (biz*\\** / user*\\**).""" diff --git a/src/whop_sdk/types/swap_list_response.py b/src/whop_sdk/types/swap_list_response.py new file mode 100644 index 00000000..e1c02d5f --- /dev/null +++ b/src/whop_sdk/types/swap_list_response.py @@ -0,0 +1,26 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Optional +from typing_extensions import Literal + +from .._models import BaseModel + +__all__ = ["SwapListResponse", "Data"] + + +class Data(BaseModel): + id: str + + account_id: str + + object: Literal["swap"] + + status: str + + tx_hashes: List[str] + + error: Optional[str] = None + + +class SwapListResponse(BaseModel): + data: List[Data] diff --git a/src/whop_sdk/types/swap_retrieve_response.py b/src/whop_sdk/types/swap_retrieve_response.py index ca351983..cd161b34 100644 --- a/src/whop_sdk/types/swap_retrieve_response.py +++ b/src/whop_sdk/types/swap_retrieve_response.py @@ -9,6 +9,8 @@ class SwapRetrieveResponse(BaseModel): + id: str + account_id: str object: Literal["swap"] diff --git a/tests/api_resources/test_swaps.py b/tests/api_resources/test_swaps.py index 1814d7b7..9972efc2 100644 --- a/tests/api_resources/test_swaps.py +++ b/tests/api_resources/test_swaps.py @@ -10,6 +10,7 @@ from whop_sdk import Whop, AsyncWhop from tests.utils import assert_matches_type from whop_sdk.types import ( + SwapListResponse, SwapCreateResponse, SwapRetrieveResponse, SwapCreateQuoteResponse, @@ -82,7 +83,7 @@ def test_streaming_response_create(self, client: Whop) -> None: @parametrize def test_method_retrieve(self, client: Whop) -> None: swap = client.swaps.retrieve( - account_id="account_id", + "id", ) assert_matches_type(SwapRetrieveResponse, swap, path=["response"]) @@ -90,7 +91,7 @@ def test_method_retrieve(self, client: Whop) -> None: @parametrize def test_raw_response_retrieve(self, client: Whop) -> None: response = client.swaps.with_raw_response.retrieve( - account_id="account_id", + "id", ) assert response.is_closed is True @@ -102,7 +103,7 @@ def test_raw_response_retrieve(self, client: Whop) -> None: @parametrize def test_streaming_response_retrieve(self, client: Whop) -> None: with client.swaps.with_streaming_response.retrieve( - account_id="account_id", + "id", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -112,6 +113,48 @@ def test_streaming_response_retrieve(self, client: Whop) -> None: 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.swaps.with_raw_response.retrieve( + "", + ) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_list(self, client: Whop) -> None: + swap = client.swaps.list( + account_id="account_id", + ) + assert_matches_type(SwapListResponse, swap, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_raw_response_list(self, client: Whop) -> None: + response = client.swaps.with_raw_response.list( + account_id="account_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + swap = response.parse() + assert_matches_type(SwapListResponse, swap, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_streaming_response_list(self, client: Whop) -> None: + with client.swaps.with_streaming_response.list( + account_id="account_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + swap = response.parse() + assert_matches_type(SwapListResponse, swap, path=["response"]) + + assert cast(Any, response.is_closed) is True + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_method_create_quote(self, client: Whop) -> None: @@ -235,7 +278,7 @@ async def test_streaming_response_create(self, async_client: AsyncWhop) -> None: @parametrize async def test_method_retrieve(self, async_client: AsyncWhop) -> None: swap = await async_client.swaps.retrieve( - account_id="account_id", + "id", ) assert_matches_type(SwapRetrieveResponse, swap, path=["response"]) @@ -243,7 +286,7 @@ async def test_method_retrieve(self, async_client: AsyncWhop) -> None: @parametrize async def test_raw_response_retrieve(self, async_client: AsyncWhop) -> None: response = await async_client.swaps.with_raw_response.retrieve( - account_id="account_id", + "id", ) assert response.is_closed is True @@ -255,7 +298,7 @@ async def test_raw_response_retrieve(self, async_client: AsyncWhop) -> None: @parametrize async def test_streaming_response_retrieve(self, async_client: AsyncWhop) -> None: async with async_client.swaps.with_streaming_response.retrieve( - account_id="account_id", + "id", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -265,6 +308,48 @@ async def test_streaming_response_retrieve(self, async_client: AsyncWhop) -> Non 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.swaps.with_raw_response.retrieve( + "", + ) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_list(self, async_client: AsyncWhop) -> None: + swap = await async_client.swaps.list( + account_id="account_id", + ) + assert_matches_type(SwapListResponse, swap, 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.swaps.with_raw_response.list( + account_id="account_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + swap = await response.parse() + assert_matches_type(SwapListResponse, swap, 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.swaps.with_streaming_response.list( + account_id="account_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + swap = await response.parse() + assert_matches_type(SwapListResponse, swap, 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_quote(self, async_client: AsyncWhop) -> None: From 1acf284d03b34327299578e8da0d747027f73935 Mon Sep 17 00:00:00 2001 From: stlc-bot Date: Wed, 10 Jun 2026 22:03:14 +0000 Subject: [PATCH 019/109] Serve real Fragment ledger balances on the wallet balance API for stablecoin-rails accounts Stainless-Generated-From: f578c7baf7826e79595c30adbc175506c51cf0d9 --- src/whop_sdk/types/wallet_balance_response.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/whop_sdk/types/wallet_balance_response.py b/src/whop_sdk/types/wallet_balance_response.py index c88cc393..8ca6b314 100644 --- a/src/whop_sdk/types/wallet_balance_response.py +++ b/src/whop_sdk/types/wallet_balance_response.py @@ -15,13 +15,13 @@ class Token(BaseModel): name: str - price_usd: float + price_usd: Optional[float] = None symbol: str token_address: Optional[str] = None - value_usd: str + value_usd: Optional[str] = None class WalletBalanceResponse(BaseModel): From 8c803e3b10713ed2d24811554bfb2d7ada2df21e Mon Sep 17 00:00:00 2001 From: stlc-bot Date: Wed, 10 Jun 2026 22:39:52 +0000 Subject: [PATCH 020/109] feat(payments): add POST /api/v1/plans/:id/calculate_tax endpoint for tax previews Stainless-Generated-From: 32623223dfc418114b3349f5a17eae6187cc225e --- .stats.yml | 2 +- api.md | 9 +- src/whop_sdk/resources/plans.py | 119 +++++++++++++++- src/whop_sdk/types/__init__.py | 2 + .../types/plan_calculate_tax_params.py | 55 ++++++++ .../types/plan_calculate_tax_response.py | 41 ++++++ tests/api_resources/test_plans.py | 131 ++++++++++++++++++ 7 files changed, 356 insertions(+), 3 deletions(-) create mode 100644 src/whop_sdk/types/plan_calculate_tax_params.py create mode 100644 src/whop_sdk/types/plan_calculate_tax_response.py diff --git a/.stats.yml b/.stats.yml index ceccbf4a..9d0edd97 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1 +1 @@ -configured_endpoints: 231 +configured_endpoints: 232 diff --git a/api.md b/api.md index 3606425f..b0a8d4e5 100644 --- a/api.md +++ b/api.md @@ -208,7 +208,13 @@ Methods: Types: ```python -from whop_sdk.types import CheckoutFont, CheckoutShape, PlanListResponse, PlanDeleteResponse +from whop_sdk.types import ( + CheckoutFont, + CheckoutShape, + PlanListResponse, + PlanDeleteResponse, + PlanCalculateTaxResponse, +) ``` Methods: @@ -218,6 +224,7 @@ Methods: - client.plans.update(id, \*\*params) -> Plan - client.plans.list(\*\*params) -> SyncCursorPage[PlanListResponse] - client.plans.delete(id) -> PlanDeleteResponse +- client.plans.calculate_tax(id, \*\*params) -> PlanCalculateTaxResponse # Entries diff --git a/src/whop_sdk/resources/plans.py b/src/whop_sdk/resources/plans.py index dab4606f..8b6eb4ae 100644 --- a/src/whop_sdk/resources/plans.py +++ b/src/whop_sdk/resources/plans.py @@ -7,7 +7,7 @@ import httpx -from ..types import plan_list_params, plan_create_params, plan_update_params +from ..types import plan_list_params, plan_create_params, plan_update_params, plan_calculate_tax_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 @@ -23,6 +23,7 @@ from ..types.shared.plan import Plan from ..types.plan_list_response import PlanListResponse from ..types.plan_delete_response import PlanDeleteResponse +from ..types.plan_calculate_tax_response import PlanCalculateTaxResponse __all__ = ["PlansResource", "AsyncPlansResource"] @@ -498,6 +499,58 @@ def delete( cast_to=PlanDeleteResponse, ) + def calculate_tax( + self, + id: str, + *, + address: Optional[plan_calculate_tax_params.Address] | Omit = omit, + ip_address: str | Omit = omit, + tax_ids: Optional[Iterable[plan_calculate_tax_params.TaxID]] | 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, + ) -> PlanCalculateTaxResponse: + """ + Calculates the tax owed on a plan based on the buyer's location. + + Args: + address: The buyer's billing address. Provide this or ip_address. + + ip_address: The buyer's IP address, used to resolve their location when no address is + provided. + + tax_ids: The buyer's tax IDs, such as a VAT number, used to apply B2B reverse-charge + exemptions. + + 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("/plans/{id}/calculate_tax", id=id), + body=maybe_transform( + { + "address": address, + "ip_address": ip_address, + "tax_ids": tax_ids, + }, + plan_calculate_tax_params.PlanCalculateTaxParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=PlanCalculateTaxResponse, + ) + class AsyncPlansResource(AsyncAPIResource): @cached_property @@ -970,6 +1023,58 @@ async def delete( cast_to=PlanDeleteResponse, ) + async def calculate_tax( + self, + id: str, + *, + address: Optional[plan_calculate_tax_params.Address] | Omit = omit, + ip_address: str | Omit = omit, + tax_ids: Optional[Iterable[plan_calculate_tax_params.TaxID]] | 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, + ) -> PlanCalculateTaxResponse: + """ + Calculates the tax owed on a plan based on the buyer's location. + + Args: + address: The buyer's billing address. Provide this or ip_address. + + ip_address: The buyer's IP address, used to resolve their location when no address is + provided. + + tax_ids: The buyer's tax IDs, such as a VAT number, used to apply B2B reverse-charge + exemptions. + + 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("/plans/{id}/calculate_tax", id=id), + body=await async_maybe_transform( + { + "address": address, + "ip_address": ip_address, + "tax_ids": tax_ids, + }, + plan_calculate_tax_params.PlanCalculateTaxParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=PlanCalculateTaxResponse, + ) + class PlansResourceWithRawResponse: def __init__(self, plans: PlansResource) -> None: @@ -990,6 +1095,9 @@ def __init__(self, plans: PlansResource) -> None: self.delete = to_raw_response_wrapper( plans.delete, ) + self.calculate_tax = to_raw_response_wrapper( + plans.calculate_tax, + ) class AsyncPlansResourceWithRawResponse: @@ -1011,6 +1119,9 @@ def __init__(self, plans: AsyncPlansResource) -> None: self.delete = async_to_raw_response_wrapper( plans.delete, ) + self.calculate_tax = async_to_raw_response_wrapper( + plans.calculate_tax, + ) class PlansResourceWithStreamingResponse: @@ -1032,6 +1143,9 @@ def __init__(self, plans: PlansResource) -> None: self.delete = to_streamed_response_wrapper( plans.delete, ) + self.calculate_tax = to_streamed_response_wrapper( + plans.calculate_tax, + ) class AsyncPlansResourceWithStreamingResponse: @@ -1053,3 +1167,6 @@ def __init__(self, plans: AsyncPlansResource) -> None: self.delete = async_to_streamed_response_wrapper( plans.delete, ) + self.calculate_tax = async_to_streamed_response_wrapper( + plans.calculate_tax, + ) diff --git a/src/whop_sdk/types/__init__.py b/src/whop_sdk/types/__init__.py index 45aabfc3..30ef4c5d 100644 --- a/src/whop_sdk/types/__init__.py +++ b/src/whop_sdk/types/__init__.py @@ -301,6 +301,7 @@ from .dispute_alert_list_params import DisputeAlertListParams as DisputeAlertListParams from .dm_member_delete_response import DmMemberDeleteResponse as DmMemberDeleteResponse from .payout_method_list_params import PayoutMethodListParams as PayoutMethodListParams +from .plan_calculate_tax_params import PlanCalculateTaxParams as PlanCalculateTaxParams from .access_token_create_params import AccessTokenCreateParams as AccessTokenCreateParams from .account_link_create_params import AccountLinkCreateParams as AccountLinkCreateParams from .affiliate_archive_response import AffiliateArchiveResponse as AffiliateArchiveResponse @@ -337,6 +338,7 @@ from .experience_duplicate_params import ExperienceDuplicateParams as ExperienceDuplicateParams from .payout_destination_category import PayoutDestinationCategory as PayoutDestinationCategory from .payout_method_list_response import PayoutMethodListResponse as PayoutMethodListResponse +from .plan_calculate_tax_response import PlanCalculateTaxResponse as PlanCalculateTaxResponse 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 diff --git a/src/whop_sdk/types/plan_calculate_tax_params.py b/src/whop_sdk/types/plan_calculate_tax_params.py new file mode 100644 index 00000000..d1b31405 --- /dev/null +++ b/src/whop_sdk/types/plan_calculate_tax_params.py @@ -0,0 +1,55 @@ +# 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 Required, TypedDict + +__all__ = ["PlanCalculateTaxParams", "Address", "TaxID"] + + +class PlanCalculateTaxParams(TypedDict, total=False): + address: Optional[Address] + """The buyer's billing address. Provide this or ip_address.""" + + ip_address: str + """ + The buyer's IP address, used to resolve their location when no address is + provided. + """ + + tax_ids: Optional[Iterable[TaxID]] + """ + The buyer's tax IDs, such as a VAT number, used to apply B2B reverse-charge + exemptions. + """ + + +class Address(TypedDict, total=False): + """The buyer's billing address. Provide this or ip_address.""" + + country: Required[str] + """The two-letter ISO 3166-1 country code, for example US, DE, or GB.""" + + city: Optional[str] + """The city name.""" + + line1: Optional[str] + """The first line of the street address.""" + + line2: Optional[str] + """The second line of the street address.""" + + postal_code: Optional[str] + """The postal or ZIP code.""" + + state: Optional[str] + """The state, province, or region code, for example CA.""" + + +class TaxID(TypedDict, total=False): + type: str + """The tax ID type, for example eu_vat.""" + + value: str + """The tax ID number, for example DE123456789.""" diff --git a/src/whop_sdk/types/plan_calculate_tax_response.py b/src/whop_sdk/types/plan_calculate_tax_response.py new file mode 100644 index 00000000..7201920e --- /dev/null +++ b/src/whop_sdk/types/plan_calculate_tax_response.py @@ -0,0 +1,41 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from .._models import BaseModel + +__all__ = ["PlanCalculateTaxResponse"] + + +class PlanCalculateTaxResponse(BaseModel): + currency: str + """The three-letter ISO 4217 currency code of the amounts.""" + + status: Literal["calculated", "not_calculated"] + """Whether tax was successfully calculated. + + Returns not_calculated when tax could not be determined. + """ + + subtotal: int + """The plan price in the currency's smallest unit, for example cents. + + For exclusive tax this is the pre-tax amount; for inclusive tax it already + contains the tax and equals the total. + """ + + tax_amount: int + """The tax owed, in the currency's smallest unit. + + For exclusive tax it is added on top of the subtotal; for inclusive tax it is + the portion already contained in the subtotal. + """ + + tax_behavior: Literal["exclusive", "inclusive"] + """ + Whether tax is added on top of the price (exclusive) or already included in it + (inclusive). + """ + + total: int + """The total amount the buyer pays, in the currency's smallest unit.""" diff --git a/tests/api_resources/test_plans.py b/tests/api_resources/test_plans.py index e0ebeaaf..d573b6df 100644 --- a/tests/api_resources/test_plans.py +++ b/tests/api_resources/test_plans.py @@ -12,6 +12,7 @@ from whop_sdk.types import ( PlanListResponse, PlanDeleteResponse, + PlanCalculateTaxResponse, ) from whop_sdk.pagination import SyncCursorPage, AsyncCursorPage from whop_sdk.types.shared import Plan @@ -333,6 +334,71 @@ def test_path_params_delete(self, client: Whop) -> None: "", ) + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_calculate_tax(self, client: Whop) -> None: + plan = client.plans.calculate_tax( + id="id", + ) + assert_matches_type(PlanCalculateTaxResponse, plan, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_calculate_tax_with_all_params(self, client: Whop) -> None: + plan = client.plans.calculate_tax( + id="id", + address={ + "country": "country", + "city": "city", + "line1": "line1", + "line2": "line2", + "postal_code": "postal_code", + "state": "state", + }, + ip_address="ip_address", + tax_ids=[ + { + "type": "type", + "value": "value", + } + ], + ) + assert_matches_type(PlanCalculateTaxResponse, plan, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_raw_response_calculate_tax(self, client: Whop) -> None: + response = client.plans.with_raw_response.calculate_tax( + id="id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + plan = response.parse() + assert_matches_type(PlanCalculateTaxResponse, plan, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_streaming_response_calculate_tax(self, client: Whop) -> None: + with client.plans.with_streaming_response.calculate_tax( + id="id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + plan = response.parse() + assert_matches_type(PlanCalculateTaxResponse, plan, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_path_params_calculate_tax(self, client: Whop) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + client.plans.with_raw_response.calculate_tax( + id="", + ) + class TestAsyncPlans: parametrize = pytest.mark.parametrize( @@ -649,3 +715,68 @@ async def test_path_params_delete(self, async_client: AsyncWhop) -> None: await async_client.plans.with_raw_response.delete( "", ) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_calculate_tax(self, async_client: AsyncWhop) -> None: + plan = await async_client.plans.calculate_tax( + id="id", + ) + assert_matches_type(PlanCalculateTaxResponse, plan, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_calculate_tax_with_all_params(self, async_client: AsyncWhop) -> None: + plan = await async_client.plans.calculate_tax( + id="id", + address={ + "country": "country", + "city": "city", + "line1": "line1", + "line2": "line2", + "postal_code": "postal_code", + "state": "state", + }, + ip_address="ip_address", + tax_ids=[ + { + "type": "type", + "value": "value", + } + ], + ) + assert_matches_type(PlanCalculateTaxResponse, plan, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_raw_response_calculate_tax(self, async_client: AsyncWhop) -> None: + response = await async_client.plans.with_raw_response.calculate_tax( + id="id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + plan = await response.parse() + assert_matches_type(PlanCalculateTaxResponse, plan, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_streaming_response_calculate_tax(self, async_client: AsyncWhop) -> None: + async with async_client.plans.with_streaming_response.calculate_tax( + id="id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + plan = await response.parse() + assert_matches_type(PlanCalculateTaxResponse, plan, 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_calculate_tax(self, async_client: AsyncWhop) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + await async_client.plans.with_raw_response.calculate_tax( + id="", + ) From 35829b102255f60e88884d73dec75be0bf795f93 Mon Sep 17 00:00:00 2001 From: stlc-bot Date: Thu, 11 Jun 2026 00:05:58 +0000 Subject: [PATCH 021/109] Show crypto and wire deposits in the balance page top-ups tab Stainless-Generated-From: f742edd83eee4b29d9b4dfd54ff3edf79d2e5116 --- src/whop_sdk/resources/financial_activity.py | 8 ++++++-- src/whop_sdk/types/financial_activity_list_params.py | 7 ++++++- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/src/whop_sdk/resources/financial_activity.py b/src/whop_sdk/resources/financial_activity.py index e866edc6..9867d947 100644 --- a/src/whop_sdk/resources/financial_activity.py +++ b/src/whop_sdk/resources/financial_activity.py @@ -78,7 +78,9 @@ def list( limit: Maximum number of rows to return. - line_types: Optional ledger line categories to include. + line_types: Optional ledger line categories to include. Some categories (for example + onchain_deposit, which covers inbound crypto deposits such as MoonPay onramps) + are only returned when explicitly requested here. posted_after: Only include rows posted after this ISO 8601 timestamp. @@ -173,7 +175,9 @@ async def list( limit: Maximum number of rows to return. - line_types: Optional ledger line categories to include. + line_types: Optional ledger line categories to include. Some categories (for example + onchain_deposit, which covers inbound crypto deposits such as MoonPay onramps) + are only returned when explicitly requested here. posted_after: Only include rows posted after this ISO 8601 timestamp. diff --git a/src/whop_sdk/types/financial_activity_list_params.py b/src/whop_sdk/types/financial_activity_list_params.py index 352c468d..8d1b4392 100644 --- a/src/whop_sdk/types/financial_activity_list_params.py +++ b/src/whop_sdk/types/financial_activity_list_params.py @@ -26,7 +26,12 @@ class FinancialActivityListParams(TypedDict, total=False): """Maximum number of rows to return.""" line_types: SequenceNotStr[str] - """Optional ledger line categories to include.""" + """Optional ledger line categories to include. + + Some categories (for example onchain_deposit, which covers inbound crypto + deposits such as MoonPay onramps) are only returned when explicitly requested + here. + """ posted_after: Annotated[Union[str, datetime], PropertyInfo(format="iso8601")] """Only include rows posted after this ISO 8601 timestamp.""" From f51bbd1692f81cc1e2deb5a01d3b019bc05a1c8b Mon Sep 17 00:00:00 2001 From: stlc-bot Date: Thu, 11 Jun 2026 00:39:35 +0000 Subject: [PATCH 022/109] feat(bounties): add business goal type to bounties, scaffold ios analytics Stainless-Generated-From: 07ef97515144d1765b2805d3643bf592829e49e2 --- src/whop_sdk/resources/bounties.py | 16 ++++++++++++++++ src/whop_sdk/types/bounty_create_params.py | 11 ++++++++++- tests/api_resources/test_bounties.py | 2 ++ 3 files changed, 28 insertions(+), 1 deletion(-) diff --git a/src/whop_sdk/resources/bounties.py b/src/whop_sdk/resources/bounties.py index ea396695..640a450c 100644 --- a/src/whop_sdk/resources/bounties.py +++ b/src/whop_sdk/resources/bounties.py @@ -60,6 +60,10 @@ def create( title: str, accepted_submissions_limit: Optional[int] | Omit = omit, allowed_country_codes: Optional[SequenceNotStr[str]] | Omit = omit, + business_goal_type: Optional[ + Literal["clipping", "post_engagement", "owned_account_growth", "ugc_content", "local_activation", "other"] + ] + | Omit = omit, experience_id: Optional[str] | Omit = omit, origin_account_id: Optional[str] | Omit = omit, post_markdown_content: Optional[str] | Omit = omit, @@ -94,6 +98,9 @@ def create( allowed_country_codes: The ISO3166 country codes where this bounty should be visible. Empty means globally visible. + business_goal_type: What the poster is trying to accomplish with a workforce bounty. Used for + product taxonomy and analytics, separate from the bounty's implementation type. + 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. @@ -124,6 +131,7 @@ def create( "title": title, "accepted_submissions_limit": accepted_submissions_limit, "allowed_country_codes": allowed_country_codes, + "business_goal_type": business_goal_type, "experience_id": experience_id, "origin_account_id": origin_account_id, "post_markdown_content": post_markdown_content, @@ -273,6 +281,10 @@ async def create( title: str, accepted_submissions_limit: Optional[int] | Omit = omit, allowed_country_codes: Optional[SequenceNotStr[str]] | Omit = omit, + business_goal_type: Optional[ + Literal["clipping", "post_engagement", "owned_account_growth", "ugc_content", "local_activation", "other"] + ] + | Omit = omit, experience_id: Optional[str] | Omit = omit, origin_account_id: Optional[str] | Omit = omit, post_markdown_content: Optional[str] | Omit = omit, @@ -307,6 +319,9 @@ async def create( allowed_country_codes: The ISO3166 country codes where this bounty should be visible. Empty means globally visible. + business_goal_type: What the poster is trying to accomplish with a workforce bounty. Used for + product taxonomy and analytics, separate from the bounty's implementation type. + 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. @@ -337,6 +352,7 @@ async def create( "title": title, "accepted_submissions_limit": accepted_submissions_limit, "allowed_country_codes": allowed_country_codes, + "business_goal_type": business_goal_type, "experience_id": experience_id, "origin_account_id": origin_account_id, "post_markdown_content": post_markdown_content, diff --git a/src/whop_sdk/types/bounty_create_params.py b/src/whop_sdk/types/bounty_create_params.py index de0e0fdb..583854de 100644 --- a/src/whop_sdk/types/bounty_create_params.py +++ b/src/whop_sdk/types/bounty_create_params.py @@ -3,7 +3,7 @@ from __future__ import annotations from typing import Optional -from typing_extensions import Required, TypedDict +from typing_extensions import Literal, Required, TypedDict from .._types import SequenceNotStr from .shared.currency import Currency @@ -39,6 +39,15 @@ class BountyCreateParams(TypedDict, total=False): Empty means globally visible. """ + business_goal_type: Optional[ + Literal["clipping", "post_engagement", "owned_account_growth", "ugc_content", "local_activation", "other"] + ] + """What the poster is trying to accomplish with a workforce bounty. + + Used for product taxonomy and analytics, separate from the bounty's + implementation type. + """ + experience_id: Optional[str] """An optional experience to scope the bounty to.""" diff --git a/tests/api_resources/test_bounties.py b/tests/api_resources/test_bounties.py index d76ec837..d81b572b 100644 --- a/tests/api_resources/test_bounties.py +++ b/tests/api_resources/test_bounties.py @@ -43,6 +43,7 @@ def test_method_create_with_all_params(self, client: Whop) -> None: title="title", accepted_submissions_limit=42, allowed_country_codes=["string"], + business_goal_type="clipping", experience_id="exp_xxxxxxxxxxxxxx", origin_account_id="origin_account_id", post_markdown_content="post_markdown_content", @@ -193,6 +194,7 @@ async def test_method_create_with_all_params(self, async_client: AsyncWhop) -> N title="title", accepted_submissions_limit=42, allowed_country_codes=["string"], + business_goal_type="clipping", experience_id="exp_xxxxxxxxxxxxxx", origin_account_id="origin_account_id", post_markdown_content="post_markdown_content", From 82fb96f3b42ee4dd51747f388a86f72ff04ac9f7 Mon Sep 17 00:00:00 2001 From: stlc-bot Date: Thu, 11 Jun 2026 00:47:37 +0000 Subject: [PATCH 023/109] Create claim links through the wallet Send API Stainless-Generated-From: 7f3fcce301b985a2669045d9c9a27cdc8509820c --- src/whop_sdk/resources/wallets.py | 135 ++++++++++++++------- src/whop_sdk/types/wallet_send_params.py | 30 ++++- src/whop_sdk/types/wallet_send_response.py | 49 ++++++-- tests/api_resources/test_wallets.py | 29 ++++- 4 files changed, 185 insertions(+), 58 deletions(-) diff --git a/src/whop_sdk/resources/wallets.py b/src/whop_sdk/resources/wallets.py index 3bc4c9d4..d7cb79dc 100644 --- a/src/whop_sdk/resources/wallets.py +++ b/src/whop_sdk/resources/wallets.py @@ -2,10 +2,13 @@ from __future__ import annotations +from typing import Any, Union, cast +from datetime import datetime + import httpx from ..types import wallet_send_params, wallet_balance_params -from .._types import Body, Query, Headers, NotGiven, not_given +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 @@ -104,7 +107,10 @@ def send( *, account_id: str, amount: str, - to: str, + expires_at: Union[str, datetime] | Omit = omit, + link: bool | Omit = omit, + redeemable_count: int | Omit = omit, + to: 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, @@ -112,15 +118,27 @@ def send( extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> WalletSendResponse: - """ - Sends USDT from an account's wallet to another Whop user or business. + """Sends USDT from an account's wallet to another Whop user or business. + + With link: + true instead of to, funds a claim link anyone with the URL can redeem (requires + the airdrop_link:manage scope) and returns its claim_url. Args: account_id: The sending account ID. - amount: USDT amount to send. + amount: USDT amount to send — or the per-claim USD amount when link is true. + + expires_at: Claim-link expiry as an ISO 8601 timestamp (link mode only). Defaults to 24 + hours from creation. - to: Recipient user ID, business account ID, ledger account ID, or email. + link: When true, creates a claim link instead of sending to a recipient. Mutually + exclusive with to. Requires the airdrop_link:manage scope. + + redeemable_count: How many different users can claim the link (link mode only). Defaults to 1. + + to: Recipient user ID, business account ID, ledger account ID, or email. Omit when + link is true. extra_headers: Send extra headers @@ -130,23 +148,31 @@ def send( timeout: Override the client-level default timeout for this request, in seconds """ - return self._post( - "/wallets/send", - body=maybe_transform( - { - "amount": amount, - "to": to, - }, - wallet_send_params.WalletSendParams, - ), - options=make_request_options( - extra_headers=extra_headers, - extra_query=extra_query, - extra_body=extra_body, - timeout=timeout, - query=maybe_transform({"account_id": account_id}, wallet_send_params.WalletSendParams), + return cast( + WalletSendResponse, + self._post( + "/wallets/send", + body=maybe_transform( + { + "amount": amount, + "expires_at": expires_at, + "link": link, + "redeemable_count": redeemable_count, + "to": to, + }, + wallet_send_params.WalletSendParams, + ), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform({"account_id": account_id}, wallet_send_params.WalletSendParams), + ), + cast_to=cast( + Any, WalletSendResponse + ), # Union types cannot be passed in as arguments in the type system ), - cast_to=WalletSendResponse, ) @@ -233,7 +259,10 @@ async def send( *, account_id: str, amount: str, - to: str, + expires_at: Union[str, datetime] | Omit = omit, + link: bool | Omit = omit, + redeemable_count: int | Omit = omit, + to: 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, @@ -241,15 +270,27 @@ async def send( extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> WalletSendResponse: - """ - Sends USDT from an account's wallet to another Whop user or business. + """Sends USDT from an account's wallet to another Whop user or business. + + With link: + true instead of to, funds a claim link anyone with the URL can redeem (requires + the airdrop_link:manage scope) and returns its claim_url. Args: account_id: The sending account ID. - amount: USDT amount to send. + amount: USDT amount to send — or the per-claim USD amount when link is true. + + expires_at: Claim-link expiry as an ISO 8601 timestamp (link mode only). Defaults to 24 + hours from creation. + + link: When true, creates a claim link instead of sending to a recipient. Mutually + exclusive with to. Requires the airdrop_link:manage scope. - to: Recipient user ID, business account ID, ledger account ID, or email. + redeemable_count: How many different users can claim the link (link mode only). Defaults to 1. + + to: Recipient user ID, business account ID, ledger account ID, or email. Omit when + link is true. extra_headers: Send extra headers @@ -259,23 +300,31 @@ async def send( timeout: Override the client-level default timeout for this request, in seconds """ - return await self._post( - "/wallets/send", - body=await async_maybe_transform( - { - "amount": amount, - "to": to, - }, - wallet_send_params.WalletSendParams, - ), - options=make_request_options( - extra_headers=extra_headers, - extra_query=extra_query, - extra_body=extra_body, - timeout=timeout, - query=await async_maybe_transform({"account_id": account_id}, wallet_send_params.WalletSendParams), + return cast( + WalletSendResponse, + await self._post( + "/wallets/send", + body=await async_maybe_transform( + { + "amount": amount, + "expires_at": expires_at, + "link": link, + "redeemable_count": redeemable_count, + "to": to, + }, + wallet_send_params.WalletSendParams, + ), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=await async_maybe_transform({"account_id": account_id}, wallet_send_params.WalletSendParams), + ), + cast_to=cast( + Any, WalletSendResponse + ), # Union types cannot be passed in as arguments in the type system ), - cast_to=WalletSendResponse, ) diff --git a/src/whop_sdk/types/wallet_send_params.py b/src/whop_sdk/types/wallet_send_params.py index 79978b9e..99928859 100644 --- a/src/whop_sdk/types/wallet_send_params.py +++ b/src/whop_sdk/types/wallet_send_params.py @@ -2,7 +2,11 @@ from __future__ import annotations -from typing_extensions import Required, TypedDict +from typing import Union +from datetime import datetime +from typing_extensions import Required, Annotated, TypedDict + +from .._utils import PropertyInfo __all__ = ["WalletSendParams"] @@ -12,7 +16,25 @@ class WalletSendParams(TypedDict, total=False): """The sending account ID.""" amount: Required[str] - """USDT amount to send.""" + """USDT amount to send — or the per-claim USD amount when link is true.""" + + expires_at: Annotated[Union[str, datetime], PropertyInfo(format="iso8601")] + """Claim-link expiry as an ISO 8601 timestamp (link mode only). + + Defaults to 24 hours from creation. + """ + + link: bool + """When true, creates a claim link instead of sending to a recipient. + + Mutually exclusive with to. Requires the airdrop_link:manage scope. + """ + + redeemable_count: int + """How many different users can claim the link (link mode only). Defaults to 1.""" + + to: str + """Recipient user ID, business account ID, ledger account ID, or email. - to: Required[str] - """Recipient user ID, business account ID, ledger account ID, or email.""" + Omit when link is true. + """ diff --git a/src/whop_sdk/types/wallet_send_response.py b/src/whop_sdk/types/wallet_send_response.py index 5db26b85..0d02ef87 100644 --- a/src/whop_sdk/types/wallet_send_response.py +++ b/src/whop_sdk/types/wallet_send_response.py @@ -1,33 +1,68 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -from typing_extensions import Literal +from typing import Union, Optional +from datetime import datetime +from typing_extensions import Literal, TypeAlias from .._models import BaseModel -__all__ = ["WalletSendResponse", "Destination", "Source"] +__all__ = ["WalletSendResponse", "Send", "SendDestination", "SendSource", "ClaimLink", "ClaimLinkSource"] -class Destination(BaseModel): +class SendDestination(BaseModel): account_id: str address: str -class Source(BaseModel): +class SendSource(BaseModel): account_id: str address: str -class WalletSendResponse(BaseModel): +class Send(BaseModel): + """Returned when sending to a recipient (`to`).""" + amount: str currency: str - destination: Destination + destination: SendDestination object: Literal["send"] - source: Source + source: SendSource tx_hash: str + + +class ClaimLinkSource(BaseModel): + account_id: str + + +class ClaimLink(BaseModel): + """Returned when creating a claim link (`link: true`).""" + + id: str + + amount: str + """Per-claim amount; a multi-claim link debits amount × redeemable_count.""" + + claim_url: str + """Shareable URL anyone can open to claim the funds.""" + + currency: str + + expires_at: Optional[datetime] = None + + object: Literal["claim_link"] + + redeemable_count: int + + source: ClaimLinkSource + + status: str + + +WalletSendResponse: TypeAlias = Union[Send, ClaimLink] diff --git a/tests/api_resources/test_wallets.py b/tests/api_resources/test_wallets.py index 9627b766..acb5beb3 100644 --- a/tests/api_resources/test_wallets.py +++ b/tests/api_resources/test_wallets.py @@ -14,6 +14,7 @@ WalletSendResponse, WalletBalanceResponse, ) +from whop_sdk._utils import parse_datetime base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") @@ -89,6 +90,18 @@ def test_method_send(self, client: Whop) -> None: wallet = client.wallets.send( account_id="account_id", amount="amount", + ) + assert_matches_type(WalletSendResponse, wallet, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_send_with_all_params(self, client: Whop) -> None: + wallet = client.wallets.send( + account_id="account_id", + amount="amount", + expires_at=parse_datetime("2019-12-27T18:11:19.117Z"), + link=True, + redeemable_count=0, to="to", ) assert_matches_type(WalletSendResponse, wallet, path=["response"]) @@ -99,7 +112,6 @@ def test_raw_response_send(self, client: Whop) -> None: response = client.wallets.with_raw_response.send( account_id="account_id", amount="amount", - to="to", ) assert response.is_closed is True @@ -113,7 +125,6 @@ def test_streaming_response_send(self, client: Whop) -> None: with client.wallets.with_streaming_response.send( account_id="account_id", amount="amount", - to="to", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -197,6 +208,18 @@ async def test_method_send(self, async_client: AsyncWhop) -> None: wallet = await async_client.wallets.send( account_id="account_id", amount="amount", + ) + assert_matches_type(WalletSendResponse, wallet, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_send_with_all_params(self, async_client: AsyncWhop) -> None: + wallet = await async_client.wallets.send( + account_id="account_id", + amount="amount", + expires_at=parse_datetime("2019-12-27T18:11:19.117Z"), + link=True, + redeemable_count=0, to="to", ) assert_matches_type(WalletSendResponse, wallet, path=["response"]) @@ -207,7 +230,6 @@ async def test_raw_response_send(self, async_client: AsyncWhop) -> None: response = await async_client.wallets.with_raw_response.send( account_id="account_id", amount="amount", - to="to", ) assert response.is_closed is True @@ -221,7 +243,6 @@ async def test_streaming_response_send(self, async_client: AsyncWhop) -> None: async with async_client.wallets.with_streaming_response.send( account_id="account_id", amount="amount", - to="to", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" From 61a9ac3f3cb3c7af8a1ae633fa9ae9286bb9419e Mon Sep 17 00:00:00 2001 From: stlc-bot Date: Thu, 11 Jun 2026 03:00:25 +0000 Subject: [PATCH 024/109] Add bank deposit data to deposits API Stainless-Generated-From: 41e7e1d09691428635d07420f2a67783bd390c61 --- .stats.yml | 2 +- api.md | 3 +- src/whop_sdk/resources/deposits.py | 109 ++++++++++++++++-- src/whop_sdk/types/__init__.py | 2 + src/whop_sdk/types/deposit_create_params.py | 6 +- src/whop_sdk/types/deposit_create_response.py | 46 +++++++- src/whop_sdk/types/deposit_list_params.py | 12 ++ src/whop_sdk/types/deposit_list_response.py | 33 ++++++ tests/api_resources/test_deposits.py | 80 +++++++++++-- 9 files changed, 267 insertions(+), 26 deletions(-) create mode 100644 src/whop_sdk/types/deposit_list_params.py create mode 100644 src/whop_sdk/types/deposit_list_response.py diff --git a/.stats.yml b/.stats.yml index 9d0edd97..5aab750e 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1 +1 @@ -configured_endpoints: 232 +configured_endpoints: 233 diff --git a/api.md b/api.md index b0a8d4e5..6486a56e 100644 --- a/api.md +++ b/api.md @@ -783,12 +783,13 @@ Methods: Types: ```python -from whop_sdk.types import DepositCreateResponse +from whop_sdk.types import DepositCreateResponse, DepositListResponse ``` Methods: - client.deposits.create(\*\*params) -> DepositCreateResponse +- client.deposits.list(\*\*params) -> DepositListResponse # SetupIntents diff --git a/src/whop_sdk/resources/deposits.py b/src/whop_sdk/resources/deposits.py index 9d32c93d..6e7a31a9 100644 --- a/src/whop_sdk/resources/deposits.py +++ b/src/whop_sdk/resources/deposits.py @@ -6,7 +6,7 @@ import httpx -from ..types import deposit_create_params +from ..types import deposit_list_params, deposit_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 @@ -18,6 +18,7 @@ async_to_streamed_response_wrapper, ) from .._base_client import make_request_options +from ..types.deposit_list_response import DepositListResponse from ..types.deposit_create_response import DepositCreateResponse __all__ = ["DepositsResource", "AsyncDepositsResource"] @@ -46,8 +47,8 @@ def with_streaming_response(self) -> DepositsResourceWithStreamingResponse: def create( self, *, - amount: float, destination: deposit_create_params.Destination, + amount: float | Omit = omit, metadata: Dict[str, object] | Omit = omit, network: Optional[str] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. @@ -62,11 +63,11 @@ def create( it. Args: - amount: Amount to deposit. - destination: Destination account ID or wallet address. Object form is supported for compatibility. + amount: Optional amount to deposit. + metadata: Arbitrary metadata echoed in the response. network: Optional destination network override. @@ -83,8 +84,8 @@ def create( "/deposits", body=maybe_transform( { - "amount": amount, "destination": destination, + "amount": amount, "metadata": metadata, "network": network, }, @@ -96,6 +97,45 @@ def create( cast_to=DepositCreateResponse, ) + def list( + self, + *, + account_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, + ) -> DepositListResponse: + """Returns deposit transactions for a business account. + + Bank deposit transactions + are nested under the bank field. + + Args: + account_id: Business account ID (biz\\__\\**). + + 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( + "/deposits", + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform({"account_id": account_id}, deposit_list_params.DepositListParams), + ), + cast_to=DepositListResponse, + ) + class AsyncDepositsResource(AsyncAPIResource): @cached_property @@ -120,8 +160,8 @@ def with_streaming_response(self) -> AsyncDepositsResourceWithStreamingResponse: async def create( self, *, - amount: float, destination: deposit_create_params.Destination, + amount: float | Omit = omit, metadata: Dict[str, object] | Omit = omit, network: Optional[str] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. @@ -136,11 +176,11 @@ async def create( it. Args: - amount: Amount to deposit. - destination: Destination account ID or wallet address. Object form is supported for compatibility. + amount: Optional amount to deposit. + metadata: Arbitrary metadata echoed in the response. network: Optional destination network override. @@ -157,8 +197,8 @@ async def create( "/deposits", body=await async_maybe_transform( { - "amount": amount, "destination": destination, + "amount": amount, "metadata": metadata, "network": network, }, @@ -170,6 +210,45 @@ async def create( cast_to=DepositCreateResponse, ) + async def list( + self, + *, + account_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, + ) -> DepositListResponse: + """Returns deposit transactions for a business account. + + Bank deposit transactions + are nested under the bank field. + + Args: + account_id: Business account ID (biz\\__\\**). + + 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( + "/deposits", + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=await async_maybe_transform({"account_id": account_id}, deposit_list_params.DepositListParams), + ), + cast_to=DepositListResponse, + ) + class DepositsResourceWithRawResponse: def __init__(self, deposits: DepositsResource) -> None: @@ -178,6 +257,9 @@ def __init__(self, deposits: DepositsResource) -> None: self.create = to_raw_response_wrapper( deposits.create, ) + self.list = to_raw_response_wrapper( + deposits.list, + ) class AsyncDepositsResourceWithRawResponse: @@ -187,6 +269,9 @@ def __init__(self, deposits: AsyncDepositsResource) -> None: self.create = async_to_raw_response_wrapper( deposits.create, ) + self.list = async_to_raw_response_wrapper( + deposits.list, + ) class DepositsResourceWithStreamingResponse: @@ -196,6 +281,9 @@ def __init__(self, deposits: DepositsResource) -> None: self.create = to_streamed_response_wrapper( deposits.create, ) + self.list = to_streamed_response_wrapper( + deposits.list, + ) class AsyncDepositsResourceWithStreamingResponse: @@ -205,3 +293,6 @@ def __init__(self, deposits: AsyncDepositsResource) -> None: self.create = async_to_streamed_response_wrapper( deposits.create, ) + self.list = async_to_streamed_response_wrapper( + deposits.list, + ) diff --git a/src/whop_sdk/types/__init__.py b/src/whop_sdk/types/__init__.py index 30ef4c5d..ad6f8fb4 100644 --- a/src/whop_sdk/types/__init__.py +++ b/src/whop_sdk/types/__init__.py @@ -147,6 +147,7 @@ from .ai_chat_list_params import AIChatListParams as AIChatListParams from .company_list_params import CompanyListParams as CompanyListParams from .course_visibilities import CourseVisibilities as CourseVisibilities +from .deposit_list_params import DepositListParams as DepositListParams from .dispute_list_params import DisputeListParams as DisputeListParams from .entry_list_response import EntryListResponse as EntryListResponse from .forum_list_response import ForumListResponse as ForumListResponse @@ -197,6 +198,7 @@ from .company_list_response import CompanyListResponse as CompanyListResponse from .company_update_params import CompanyUpdateParams as CompanyUpdateParams from .deposit_create_params import DepositCreateParams as DepositCreateParams +from .deposit_list_response import DepositListResponse as DepositListResponse from .dispute_list_response import DisputeListResponse as DisputeListResponse from .dm_member_list_params import DmMemberListParams as DmMemberListParams from .invoice_create_params import InvoiceCreateParams as InvoiceCreateParams diff --git a/src/whop_sdk/types/deposit_create_params.py b/src/whop_sdk/types/deposit_create_params.py index 6f6da819..164d1b72 100644 --- a/src/whop_sdk/types/deposit_create_params.py +++ b/src/whop_sdk/types/deposit_create_params.py @@ -9,15 +9,15 @@ class DepositCreateParams(TypedDict, total=False): - amount: Required[float] - """Amount to deposit.""" - destination: Required[Destination] """Destination account ID or wallet address. Object form is supported for compatibility. """ + amount: float + """Optional amount to deposit.""" + metadata: Dict[str, object] """Arbitrary metadata echoed in the response.""" diff --git a/src/whop_sdk/types/deposit_create_response.py b/src/whop_sdk/types/deposit_create_response.py index f0221451..f630bc3a 100644 --- a/src/whop_sdk/types/deposit_create_response.py +++ b/src/whop_sdk/types/deposit_create_response.py @@ -1,11 +1,49 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -from typing import Dict, Optional +from typing import Dict, List, Optional from typing_extensions import Literal from .._models import BaseModel -__all__ = ["DepositCreateResponse", "DepositAddress", "Destination"] +__all__ = ["DepositCreateResponse", "Bank", "BankInstruction", "BankMethod", "DepositAddress", "Destination"] + + +class BankInstruction(BaseModel): + account_number: Optional[str] = None + + currency: str + + deposit_bank_name: Optional[str] = None + + deposit_beneficiary_name: Optional[str] = None + + deposit_reference: Optional[str] = None + + routing_number: Optional[str] = None + + +class BankMethod(BaseModel): + currency: str + + rail: str + + +class Bank(BaseModel): + instructions: Optional[List[BankInstruction]] = None + + methods: List[BankMethod] + + onboarding_link: Optional[str] = None + + status: Literal[ + "not_started", + "pending_identification", + "pending_review", + "requires_signing", + "active", + "user_required", + "suspended", + ] class DepositAddress(BaseModel): @@ -25,7 +63,7 @@ class Destination(BaseModel): class DepositCreateResponse(BaseModel): - amount: str + bank: Optional[Bank] = None deposit_address: DepositAddress @@ -36,3 +74,5 @@ class DepositCreateResponse(BaseModel): metadata: Dict[str, object] object: Literal["deposit"] + + amount: Optional[str] = None diff --git a/src/whop_sdk/types/deposit_list_params.py b/src/whop_sdk/types/deposit_list_params.py new file mode 100644 index 00000000..4b74a28d --- /dev/null +++ b/src/whop_sdk/types/deposit_list_params.py @@ -0,0 +1,12 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Required, TypedDict + +__all__ = ["DepositListParams"] + + +class DepositListParams(TypedDict, total=False): + account_id: Required[str] + """Business account ID (biz\\__\\**).""" diff --git a/src/whop_sdk/types/deposit_list_response.py b/src/whop_sdk/types/deposit_list_response.py new file mode 100644 index 00000000..d72522c2 --- /dev/null +++ b/src/whop_sdk/types/deposit_list_response.py @@ -0,0 +1,33 @@ +# 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__ = ["DepositListResponse", "Bank"] + + +class Bank(BaseModel): + id: str + + created_at: datetime + + destination_amount: Optional[str] = None + + destination_currency: Optional[str] = None + + source_amount: str + + source_currency: str + + status: str + + +class DepositListResponse(BaseModel): + account_id: str + + bank: List[Bank] + + object: Literal["deposits"] diff --git a/tests/api_resources/test_deposits.py b/tests/api_resources/test_deposits.py index 7786dbc8..e26f749e 100644 --- a/tests/api_resources/test_deposits.py +++ b/tests/api_resources/test_deposits.py @@ -9,7 +9,7 @@ from whop_sdk import Whop, AsyncWhop from tests.utils import assert_matches_type -from whop_sdk.types import DepositCreateResponse +from whop_sdk.types import DepositListResponse, DepositCreateResponse base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") @@ -21,7 +21,6 @@ class TestDeposits: @parametrize def test_method_create(self, client: Whop) -> None: deposit = client.deposits.create( - amount=0, destination="string", ) assert_matches_type(DepositCreateResponse, deposit, path=["response"]) @@ -30,8 +29,8 @@ def test_method_create(self, client: Whop) -> None: @parametrize def test_method_create_with_all_params(self, client: Whop) -> None: deposit = client.deposits.create( - amount=0, destination="string", + amount=0, metadata={"foo": "bar"}, network="network", ) @@ -41,7 +40,6 @@ def test_method_create_with_all_params(self, client: Whop) -> None: @parametrize def test_raw_response_create(self, client: Whop) -> None: response = client.deposits.with_raw_response.create( - amount=0, destination="string", ) @@ -54,7 +52,6 @@ def test_raw_response_create(self, client: Whop) -> None: @parametrize def test_streaming_response_create(self, client: Whop) -> None: with client.deposits.with_streaming_response.create( - amount=0, destination="string", ) as response: assert not response.is_closed @@ -65,6 +62,40 @@ def test_streaming_response_create(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_list(self, client: Whop) -> None: + deposit = client.deposits.list( + account_id="account_id", + ) + assert_matches_type(DepositListResponse, deposit, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_raw_response_list(self, client: Whop) -> None: + response = client.deposits.with_raw_response.list( + account_id="account_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + deposit = response.parse() + assert_matches_type(DepositListResponse, deposit, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_streaming_response_list(self, client: Whop) -> None: + with client.deposits.with_streaming_response.list( + account_id="account_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + deposit = response.parse() + assert_matches_type(DepositListResponse, deposit, path=["response"]) + + assert cast(Any, response.is_closed) is True + class TestAsyncDeposits: parametrize = pytest.mark.parametrize( @@ -75,7 +106,6 @@ class TestAsyncDeposits: @parametrize async def test_method_create(self, async_client: AsyncWhop) -> None: deposit = await async_client.deposits.create( - amount=0, destination="string", ) assert_matches_type(DepositCreateResponse, deposit, path=["response"]) @@ -84,8 +114,8 @@ async def test_method_create(self, async_client: AsyncWhop) -> None: @parametrize async def test_method_create_with_all_params(self, async_client: AsyncWhop) -> None: deposit = await async_client.deposits.create( - amount=0, destination="string", + amount=0, metadata={"foo": "bar"}, network="network", ) @@ -95,7 +125,6 @@ async def test_method_create_with_all_params(self, async_client: AsyncWhop) -> N @parametrize async def test_raw_response_create(self, async_client: AsyncWhop) -> None: response = await async_client.deposits.with_raw_response.create( - amount=0, destination="string", ) @@ -108,7 +137,6 @@ async def test_raw_response_create(self, async_client: AsyncWhop) -> None: @parametrize async def test_streaming_response_create(self, async_client: AsyncWhop) -> None: async with async_client.deposits.with_streaming_response.create( - amount=0, destination="string", ) as response: assert not response.is_closed @@ -118,3 +146,37 @@ async def test_streaming_response_create(self, async_client: AsyncWhop) -> None: assert_matches_type(DepositCreateResponse, deposit, path=["response"]) assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_list(self, async_client: AsyncWhop) -> None: + deposit = await async_client.deposits.list( + account_id="account_id", + ) + assert_matches_type(DepositListResponse, deposit, 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.deposits.with_raw_response.list( + account_id="account_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + deposit = await response.parse() + assert_matches_type(DepositListResponse, deposit, 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.deposits.with_streaming_response.list( + account_id="account_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + deposit = await response.parse() + assert_matches_type(DepositListResponse, deposit, path=["response"]) + + assert cast(Any, response.is_closed) is True From e9d984c57ed8e089e734e262ce51caed7dbc1962 Mon Sep 17 00:00:00 2001 From: stlc-bot Date: Thu, 11 Jun 2026 03:38:26 +0000 Subject: [PATCH 025/109] Add transaction details to crypto deposit rows in the financial-activity API Stainless-Generated-From: c2e156a26ec95e6d45a7eba4886ee7d5732e3a28 --- .../types/financial_activity_list_response.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/whop_sdk/types/financial_activity_list_response.py b/src/whop_sdk/types/financial_activity_list_response.py index 2a6a20e4..3340175a 100644 --- a/src/whop_sdk/types/financial_activity_list_response.py +++ b/src/whop_sdk/types/financial_activity_list_response.py @@ -173,6 +173,12 @@ class DataSource(BaseModel): object: str + chain: Optional[str] = None + """ + Chain the deposit landed on, for example plasma (onchain_transaction sources + only). + """ + created_at: Optional[datetime] = None """ Withdrawal creation time as an ISO 8601 timestamp (withdrawal sources only; @@ -197,12 +203,21 @@ class DataSource(BaseModel): payout_token_nickname: Optional[str] = None """Saved payout destination nickname (withdrawal sources only).""" + sender_address: Optional[str] = None + """ + Sender wallet address or onramp provider identifier (onchain_transaction sources + only). + """ + status: Optional[str] = None """ Withdrawal lifecycle status (withdrawal sources only; requires payout:withdrawal:read). """ + tx_hash: Optional[str] = None + """On-chain transaction hash (onchain_transaction sources only).""" + if TYPE_CHECKING: # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a # value to this field, so for compatibility we avoid doing it at runtime. From f1e3a440717f8297322361156e4e851c6998dc2e Mon Sep 17 00:00:00 2001 From: stlc-bot Date: Thu, 11 Jun 2026 03:46:15 +0000 Subject: [PATCH 026/109] Products endpoints to v1 API Stainless-Generated-From: 7f880c1489a4691a755f7a6cb389f5679507bc53 --- src/whop_sdk/resources/products.py | 459 +++++------------- src/whop_sdk/types/product_create_params.py | 141 +----- src/whop_sdk/types/product_list_params.py | 45 +- src/whop_sdk/types/product_update_params.py | 105 +--- src/whop_sdk/types/shared_params/__init__.py | 3 - .../types/shared_params/access_pass_type.py | 9 - .../types/shared_params/custom_cta.py | 23 - .../types/shared_params/visibility_filter.py | 11 - tests/api_resources/test_products.py | 211 +++----- 9 files changed, 227 insertions(+), 780 deletions(-) delete mode 100644 src/whop_sdk/types/shared_params/access_pass_type.py delete mode 100644 src/whop_sdk/types/shared_params/custom_cta.py delete mode 100644 src/whop_sdk/types/shared_params/visibility_filter.py diff --git a/src/whop_sdk/resources/products.py b/src/whop_sdk/resources/products.py index 505de8e1..5bb4f3b4 100644 --- a/src/whop_sdk/resources/products.py +++ b/src/whop_sdk/resources/products.py @@ -2,8 +2,7 @@ from __future__ import annotations -from typing import Dict, List, Union, Iterable, Optional -from datetime import datetime +from typing import Optional from typing_extensions import Literal import httpx @@ -22,14 +21,8 @@ from ..pagination import SyncCursorPage, AsyncCursorPage from .._base_client import AsyncPaginator, make_request_options from ..types.shared.product import Product -from ..types.shared.direction import Direction -from ..types.shared.custom_cta import CustomCta -from ..types.shared.visibility import Visibility from ..types.product_delete_response import ProductDeleteResponse -from ..types.shared.access_pass_type import AccessPassType from ..types.shared.product_list_item import ProductListItem -from ..types.shared.visibility_filter import VisibilityFilter -from ..types.shared.global_affiliate_status import GlobalAffiliateStatus __all__ = ["ProductsResource", "AsyncProductsResource"] @@ -59,26 +52,23 @@ def with_streaming_response(self) -> ProductsResourceWithStreamingResponse: def create( self, *, - company_id: str, title: str, collect_shipping_address: Optional[bool] | Omit = omit, - custom_cta: Optional[CustomCta] | Omit = omit, + company_id: str | Omit = omit, + custom_cta: Optional[str] | Omit = omit, custom_cta_url: Optional[str] | Omit = omit, custom_statement_descriptor: Optional[str] | Omit = omit, description: Optional[str] | Omit = omit, - experience_ids: Optional[SequenceNotStr[str]] | Omit = omit, global_affiliate_percentage: Optional[float] | Omit = omit, - global_affiliate_status: Optional[GlobalAffiliateStatus] | Omit = omit, + global_affiliate_status: str | Omit = omit, headline: Optional[str] | Omit = omit, member_affiliate_percentage: Optional[float] | Omit = omit, - member_affiliate_status: Optional[GlobalAffiliateStatus] | Omit = omit, - metadata: Optional[Dict[str, object]] | Omit = omit, - plan_options: Optional[product_create_params.PlanOptions] | Omit = omit, + member_affiliate_status: str | Omit = omit, + metadata: Optional[object] | Omit = omit, product_tax_code_id: Optional[str] | Omit = omit, redirect_purchase_url: Optional[str] | Omit = omit, route: Optional[str] | Omit = omit, - send_welcome_message: Optional[bool] | Omit = omit, - visibility: Optional[Visibility] | Omit = omit, + visibility: 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, @@ -86,64 +76,43 @@ def create( extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> Product: - """Create a new product for a company. - - The product serves as the top-level - container for plans and experiences. - - Required permissions: - - - `access_pass:create` - - `access_pass:basic:read` + """ + Creates a new product for a company. Args: - company_id: The unique identifier of the company to create this product for. - title: The display name of the product. Maximum 80 characters. - collect_shipping_address: Whether the checkout flow collects a shipping address from the customer. - - custom_cta: The different types of custom CTAs that can be selected. + collect_shipping_address: Whether to collect a shipping address at checkout. - custom_cta_url: A URL that the call-to-action button links to instead of the default checkout - flow. + company_id: The unique identifier of the company to create this product for. - custom_statement_descriptor: A custom text label that appears on the customer's bank statement. Must be 5-22 - characters, contain at least one letter, and not contain <, >, \\,, ', or " - characters. + custom_cta: The call-to-action button label. - description: A written description of the product displayed on its product page. + custom_cta_url: A URL the call-to-action button links to. - experience_ids: The unique identifiers of experiences to connect to this product. + custom_statement_descriptor: Custom bank statement descriptor. Must start with WHOP\\**. - global_affiliate_percentage: The commission rate as a percentage that affiliates earn through the global - affiliate program. + description: A written description displayed on the product page. - global_affiliate_status: The different statuses of the global affiliate program for a product. + global_affiliate_percentage: The commission rate affiliates earn. - headline: A short marketing headline displayed prominently on the product page. + global_affiliate_status: The enrollment status in the global affiliate program. - member_affiliate_percentage: The commission rate as a percentage that members earn through the member - affiliate program. + headline: A short marketing headline for the product page. - member_affiliate_status: The different statuses of the global affiliate program for a product. + member_affiliate_percentage: The commission rate members earn. - metadata: Custom key-value pairs to store on the product. Included in webhook payloads for - payment and membership events. Max 50 keys, 500 chars per key, 5000 chars per - value. + member_affiliate_status: The enrollment status in the member affiliate program. - plan_options: Configuration for an automatically generated plan to attach to this product. + metadata: Custom key-value pairs to store on the product. - product_tax_code_id: The unique identifier of the tax classification code to apply to this product. + product_tax_code_id: The unique identifier of the tax classification code. - redirect_purchase_url: A URL to redirect the customer to after completing a purchase. + redirect_purchase_url: A URL to redirect the customer to after purchase. route: The URL slug for the product's public link. - send_welcome_message: Whether to send an automated welcome message via support chat when a user joins - this product. Defaults to true. - - visibility: Visibility of a resource + visibility: Whether the product is visible to customers. extra_headers: Send extra headers @@ -157,25 +126,22 @@ def create( "/products", body=maybe_transform( { - "company_id": company_id, "title": title, "collect_shipping_address": collect_shipping_address, + "company_id": company_id, "custom_cta": custom_cta, "custom_cta_url": custom_cta_url, "custom_statement_descriptor": custom_statement_descriptor, "description": description, - "experience_ids": experience_ids, "global_affiliate_percentage": global_affiliate_percentage, "global_affiliate_status": global_affiliate_status, "headline": headline, "member_affiliate_percentage": member_affiliate_percentage, "member_affiliate_status": member_affiliate_status, "metadata": metadata, - "plan_options": plan_options, "product_tax_code_id": product_tax_code_id, "redirect_purchase_url": redirect_purchase_url, "route": route, - "send_welcome_message": send_welcome_message, "visibility": visibility, }, product_create_params.ProductCreateParams, @@ -197,12 +163,10 @@ def retrieve( extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> Product: - """ - Retrieves the details of an existing product. - - Required permissions: + """Retrieves the details of an existing product. - - `access_pass:basic:read` + This endpoint is publicly + accessible. Args: extra_headers: Send extra headers @@ -227,25 +191,11 @@ def update( self, id: str, *, - collect_shipping_address: Optional[bool] | Omit = omit, - custom_cta: Optional[CustomCta] | Omit = omit, - custom_cta_url: Optional[str] | Omit = omit, - custom_statement_descriptor: Optional[str] | Omit = omit, description: Optional[str] | Omit = omit, - gallery_images: Optional[Iterable[product_update_params.GalleryImage]] | Omit = omit, - global_affiliate_percentage: Optional[float] | Omit = omit, - global_affiliate_status: Optional[GlobalAffiliateStatus] | Omit = omit, headline: Optional[str] | Omit = omit, - member_affiliate_percentage: Optional[float] | Omit = omit, - member_affiliate_status: Optional[GlobalAffiliateStatus] | Omit = omit, - metadata: Optional[Dict[str, object]] | Omit = omit, - product_tax_code_id: Optional[str] | Omit = omit, - redirect_purchase_url: Optional[str] | Omit = omit, - route: Optional[str] | Omit = omit, - send_welcome_message: Optional[bool] | Omit = omit, - store_page_config: Optional[product_update_params.StorePageConfig] | Omit = omit, - title: Optional[str] | Omit = omit, - visibility: Optional[Visibility] | Omit = omit, + metadata: Optional[object] | Omit = omit, + title: str | Omit = omit, + visibility: 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, @@ -254,59 +204,18 @@ def update( timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> Product: """ - Update a product's title, description, visibility, and other settings. - - Required permissions: - - - `access_pass:update` - - `access_pass:basic:read` + Updates an existing product. Args: - collect_shipping_address: Whether the checkout flow collects a shipping address from the customer. - - custom_cta: The different types of custom CTAs that can be selected. - - custom_cta_url: A URL that the call-to-action button links to instead of the default checkout - flow. - - custom_statement_descriptor: A custom text label that appears on the customer's bank statement. Must be 5-22 - characters, contain at least one letter, and not contain <, >, \\,, ', or " - characters. - - description: A written description of the product displayed on its product page. - - gallery_images: The gallery images for the product. + description: A written description displayed on the product page. - global_affiliate_percentage: The commission rate as a percentage that affiliates earn through the global - affiliate program. + headline: A short marketing headline for the product page. - global_affiliate_status: The different statuses of the global affiliate program for a product. + metadata: Custom key-value pairs to store on the product. - headline: A short marketing headline displayed prominently on the product page. + title: The display name of the product. - member_affiliate_percentage: The commission rate as a percentage that members earn through the member - affiliate program. - - member_affiliate_status: The different statuses of the global affiliate program for a product. - - metadata: Custom key-value pairs to store on the product. Included in webhook payloads for - payment and membership events. Max 50 keys, 500 chars per key, 5000 chars per - value. - - product_tax_code_id: The unique identifier of the tax classification code to apply to this product. - - redirect_purchase_url: A URL to redirect the customer to after completing a purchase. - - route: The URL slug for the product's public link. - - send_welcome_message: Whether to send an automated welcome message via support chat when a user joins - this product. - - store_page_config: Layout and display configuration for this product on the company's store page. - - title: The display name of the product. Maximum 80 characters. - - visibility: Visibility of a resource + visibility: Whether the product is visible to customers. extra_headers: Send extra headers @@ -322,23 +231,9 @@ def update( path_template("/products/{id}", id=id), body=maybe_transform( { - "collect_shipping_address": collect_shipping_address, - "custom_cta": custom_cta, - "custom_cta_url": custom_cta_url, - "custom_statement_descriptor": custom_statement_descriptor, "description": description, - "gallery_images": gallery_images, - "global_affiliate_percentage": global_affiliate_percentage, - "global_affiliate_status": global_affiliate_status, "headline": headline, - "member_affiliate_percentage": member_affiliate_percentage, - "member_affiliate_status": member_affiliate_status, "metadata": metadata, - "product_tax_code_id": product_tax_code_id, - "redirect_purchase_url": redirect_purchase_url, - "route": route, - "send_welcome_message": send_welcome_message, - "store_page_config": store_page_config, "title": title, "visibility": visibility, }, @@ -354,16 +249,14 @@ 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, - direction: Optional[Direction] | Omit = omit, - first: Optional[int] | Omit = omit, - last: Optional[int] | Omit = omit, - order: Optional[Literal["active_memberships_count", "created_at", "usd_gmv", "usd_gmv_30_days"]] | Omit = omit, - product_types: Optional[List[AccessPassType]] | Omit = omit, - visibilities: Optional[List[VisibilityFilter]] | Omit = omit, + access_pass_types: SequenceNotStr[str] | Omit = omit, + after: str | Omit = omit, + before: str | Omit = omit, + direction: Literal["asc", "desc"] | Omit = omit, + first: int | Omit = omit, + last: int | Omit = omit, + order: str | Omit = omit, + visibilities: 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, @@ -372,33 +265,24 @@ def list( timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> SyncCursorPage[ProductListItem]: """ - Returns a paginated list of products belonging to a company, with optional - filtering by type, visibility, and creation date. - - Required permissions: - - - `access_pass:basic:read` + Returns a paginated list of products belonging to a company. Args: company_id: The unique identifier of the company to list products 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. + access_pass_types: Filter to only products matching these types. - created_after: Only return products created after this timestamp. + after: A cursor; returns products after this position. - created_before: Only return products created before this timestamp. + before: A cursor; returns products before this position. - direction: The direction of the sort. + direction: The sort direction for results. Defaults to descending. - first: Returns the first _n_ elements from the list. + first: The number of products to return (default and max 100). - last: Returns the last _n_ elements from the list. + last: The number of products to return from the end of the range. - order: The ways a relation of AccessPasses can be ordered - - product_types: Filter to only products matching these type classifications. + order: The field to sort results by. Defaults to created_at. visibilities: Filter to only products matching these visibility states. @@ -421,15 +305,13 @@ def list( query=maybe_transform( { "company_id": company_id, + "access_pass_types": access_pass_types, "after": after, "before": before, - "created_after": created_after, - "created_before": created_before, "direction": direction, "first": first, "last": last, "order": order, - "product_types": product_types, "visibilities": visibilities, }, product_list_params.ProductListParams, @@ -449,12 +331,10 @@ def delete( extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> ProductDeleteResponse: - """ - Permanently delete a product and remove it from the company's catalog. - - Required permissions: + """Deletes a product. - - `access_pass:delete` + Only products with no memberships, entries, reviews, or + invoices can be deleted. Args: extra_headers: Send extra headers @@ -501,26 +381,23 @@ def with_streaming_response(self) -> AsyncProductsResourceWithStreamingResponse: async def create( self, *, - company_id: str, title: str, collect_shipping_address: Optional[bool] | Omit = omit, - custom_cta: Optional[CustomCta] | Omit = omit, + company_id: str | Omit = omit, + custom_cta: Optional[str] | Omit = omit, custom_cta_url: Optional[str] | Omit = omit, custom_statement_descriptor: Optional[str] | Omit = omit, description: Optional[str] | Omit = omit, - experience_ids: Optional[SequenceNotStr[str]] | Omit = omit, global_affiliate_percentage: Optional[float] | Omit = omit, - global_affiliate_status: Optional[GlobalAffiliateStatus] | Omit = omit, + global_affiliate_status: str | Omit = omit, headline: Optional[str] | Omit = omit, member_affiliate_percentage: Optional[float] | Omit = omit, - member_affiliate_status: Optional[GlobalAffiliateStatus] | Omit = omit, - metadata: Optional[Dict[str, object]] | Omit = omit, - plan_options: Optional[product_create_params.PlanOptions] | Omit = omit, + member_affiliate_status: str | Omit = omit, + metadata: Optional[object] | Omit = omit, product_tax_code_id: Optional[str] | Omit = omit, redirect_purchase_url: Optional[str] | Omit = omit, route: Optional[str] | Omit = omit, - send_welcome_message: Optional[bool] | Omit = omit, - visibility: Optional[Visibility] | Omit = omit, + visibility: 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, @@ -528,64 +405,43 @@ async def create( extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> Product: - """Create a new product for a company. - - The product serves as the top-level - container for plans and experiences. - - Required permissions: - - - `access_pass:create` - - `access_pass:basic:read` + """ + Creates a new product for a company. Args: - company_id: The unique identifier of the company to create this product for. - title: The display name of the product. Maximum 80 characters. - collect_shipping_address: Whether the checkout flow collects a shipping address from the customer. - - custom_cta: The different types of custom CTAs that can be selected. + collect_shipping_address: Whether to collect a shipping address at checkout. - custom_cta_url: A URL that the call-to-action button links to instead of the default checkout - flow. + company_id: The unique identifier of the company to create this product for. - custom_statement_descriptor: A custom text label that appears on the customer's bank statement. Must be 5-22 - characters, contain at least one letter, and not contain <, >, \\,, ', or " - characters. + custom_cta: The call-to-action button label. - description: A written description of the product displayed on its product page. + custom_cta_url: A URL the call-to-action button links to. - experience_ids: The unique identifiers of experiences to connect to this product. + custom_statement_descriptor: Custom bank statement descriptor. Must start with WHOP\\**. - global_affiliate_percentage: The commission rate as a percentage that affiliates earn through the global - affiliate program. + description: A written description displayed on the product page. - global_affiliate_status: The different statuses of the global affiliate program for a product. + global_affiliate_percentage: The commission rate affiliates earn. - headline: A short marketing headline displayed prominently on the product page. + global_affiliate_status: The enrollment status in the global affiliate program. - member_affiliate_percentage: The commission rate as a percentage that members earn through the member - affiliate program. + headline: A short marketing headline for the product page. - member_affiliate_status: The different statuses of the global affiliate program for a product. + member_affiliate_percentage: The commission rate members earn. - metadata: Custom key-value pairs to store on the product. Included in webhook payloads for - payment and membership events. Max 50 keys, 500 chars per key, 5000 chars per - value. + member_affiliate_status: The enrollment status in the member affiliate program. - plan_options: Configuration for an automatically generated plan to attach to this product. + metadata: Custom key-value pairs to store on the product. - product_tax_code_id: The unique identifier of the tax classification code to apply to this product. + product_tax_code_id: The unique identifier of the tax classification code. - redirect_purchase_url: A URL to redirect the customer to after completing a purchase. + redirect_purchase_url: A URL to redirect the customer to after purchase. route: The URL slug for the product's public link. - send_welcome_message: Whether to send an automated welcome message via support chat when a user joins - this product. Defaults to true. - - visibility: Visibility of a resource + visibility: Whether the product is visible to customers. extra_headers: Send extra headers @@ -599,25 +455,22 @@ async def create( "/products", body=await async_maybe_transform( { - "company_id": company_id, "title": title, "collect_shipping_address": collect_shipping_address, + "company_id": company_id, "custom_cta": custom_cta, "custom_cta_url": custom_cta_url, "custom_statement_descriptor": custom_statement_descriptor, "description": description, - "experience_ids": experience_ids, "global_affiliate_percentage": global_affiliate_percentage, "global_affiliate_status": global_affiliate_status, "headline": headline, "member_affiliate_percentage": member_affiliate_percentage, "member_affiliate_status": member_affiliate_status, "metadata": metadata, - "plan_options": plan_options, "product_tax_code_id": product_tax_code_id, "redirect_purchase_url": redirect_purchase_url, "route": route, - "send_welcome_message": send_welcome_message, "visibility": visibility, }, product_create_params.ProductCreateParams, @@ -639,12 +492,10 @@ async def retrieve( extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> Product: - """ - Retrieves the details of an existing product. - - Required permissions: + """Retrieves the details of an existing product. - - `access_pass:basic:read` + This endpoint is publicly + accessible. Args: extra_headers: Send extra headers @@ -669,25 +520,11 @@ async def update( self, id: str, *, - collect_shipping_address: Optional[bool] | Omit = omit, - custom_cta: Optional[CustomCta] | Omit = omit, - custom_cta_url: Optional[str] | Omit = omit, - custom_statement_descriptor: Optional[str] | Omit = omit, description: Optional[str] | Omit = omit, - gallery_images: Optional[Iterable[product_update_params.GalleryImage]] | Omit = omit, - global_affiliate_percentage: Optional[float] | Omit = omit, - global_affiliate_status: Optional[GlobalAffiliateStatus] | Omit = omit, headline: Optional[str] | Omit = omit, - member_affiliate_percentage: Optional[float] | Omit = omit, - member_affiliate_status: Optional[GlobalAffiliateStatus] | Omit = omit, - metadata: Optional[Dict[str, object]] | Omit = omit, - product_tax_code_id: Optional[str] | Omit = omit, - redirect_purchase_url: Optional[str] | Omit = omit, - route: Optional[str] | Omit = omit, - send_welcome_message: Optional[bool] | Omit = omit, - store_page_config: Optional[product_update_params.StorePageConfig] | Omit = omit, - title: Optional[str] | Omit = omit, - visibility: Optional[Visibility] | Omit = omit, + metadata: Optional[object] | Omit = omit, + title: str | Omit = omit, + visibility: 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, @@ -696,59 +533,18 @@ async def update( timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> Product: """ - Update a product's title, description, visibility, and other settings. - - Required permissions: - - - `access_pass:update` - - `access_pass:basic:read` + Updates an existing product. Args: - collect_shipping_address: Whether the checkout flow collects a shipping address from the customer. - - custom_cta: The different types of custom CTAs that can be selected. - - custom_cta_url: A URL that the call-to-action button links to instead of the default checkout - flow. - - custom_statement_descriptor: A custom text label that appears on the customer's bank statement. Must be 5-22 - characters, contain at least one letter, and not contain <, >, \\,, ', or " - characters. - - description: A written description of the product displayed on its product page. - - gallery_images: The gallery images for the product. + description: A written description displayed on the product page. - global_affiliate_percentage: The commission rate as a percentage that affiliates earn through the global - affiliate program. + headline: A short marketing headline for the product page. - global_affiliate_status: The different statuses of the global affiliate program for a product. + metadata: Custom key-value pairs to store on the product. - headline: A short marketing headline displayed prominently on the product page. + title: The display name of the product. - member_affiliate_percentage: The commission rate as a percentage that members earn through the member - affiliate program. - - member_affiliate_status: The different statuses of the global affiliate program for a product. - - metadata: Custom key-value pairs to store on the product. Included in webhook payloads for - payment and membership events. Max 50 keys, 500 chars per key, 5000 chars per - value. - - product_tax_code_id: The unique identifier of the tax classification code to apply to this product. - - redirect_purchase_url: A URL to redirect the customer to after completing a purchase. - - route: The URL slug for the product's public link. - - send_welcome_message: Whether to send an automated welcome message via support chat when a user joins - this product. - - store_page_config: Layout and display configuration for this product on the company's store page. - - title: The display name of the product. Maximum 80 characters. - - visibility: Visibility of a resource + visibility: Whether the product is visible to customers. extra_headers: Send extra headers @@ -764,23 +560,9 @@ async def update( path_template("/products/{id}", id=id), body=await async_maybe_transform( { - "collect_shipping_address": collect_shipping_address, - "custom_cta": custom_cta, - "custom_cta_url": custom_cta_url, - "custom_statement_descriptor": custom_statement_descriptor, "description": description, - "gallery_images": gallery_images, - "global_affiliate_percentage": global_affiliate_percentage, - "global_affiliate_status": global_affiliate_status, "headline": headline, - "member_affiliate_percentage": member_affiliate_percentage, - "member_affiliate_status": member_affiliate_status, "metadata": metadata, - "product_tax_code_id": product_tax_code_id, - "redirect_purchase_url": redirect_purchase_url, - "route": route, - "send_welcome_message": send_welcome_message, - "store_page_config": store_page_config, "title": title, "visibility": visibility, }, @@ -796,16 +578,14 @@ 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, - direction: Optional[Direction] | Omit = omit, - first: Optional[int] | Omit = omit, - last: Optional[int] | Omit = omit, - order: Optional[Literal["active_memberships_count", "created_at", "usd_gmv", "usd_gmv_30_days"]] | Omit = omit, - product_types: Optional[List[AccessPassType]] | Omit = omit, - visibilities: Optional[List[VisibilityFilter]] | Omit = omit, + access_pass_types: SequenceNotStr[str] | Omit = omit, + after: str | Omit = omit, + before: str | Omit = omit, + direction: Literal["asc", "desc"] | Omit = omit, + first: int | Omit = omit, + last: int | Omit = omit, + order: str | Omit = omit, + visibilities: 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, @@ -814,33 +594,24 @@ def list( timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> AsyncPaginator[ProductListItem, AsyncCursorPage[ProductListItem]]: """ - Returns a paginated list of products belonging to a company, with optional - filtering by type, visibility, and creation date. - - Required permissions: - - - `access_pass:basic:read` + Returns a paginated list of products belonging to a company. Args: company_id: The unique identifier of the company to list products 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. + access_pass_types: Filter to only products matching these types. - created_after: Only return products created after this timestamp. + after: A cursor; returns products after this position. - created_before: Only return products created before this timestamp. + before: A cursor; returns products before this position. - direction: The direction of the sort. + direction: The sort direction for results. Defaults to descending. - first: Returns the first _n_ elements from the list. + first: The number of products to return (default and max 100). - last: Returns the last _n_ elements from the list. + last: The number of products to return from the end of the range. - order: The ways a relation of AccessPasses can be ordered - - product_types: Filter to only products matching these type classifications. + order: The field to sort results by. Defaults to created_at. visibilities: Filter to only products matching these visibility states. @@ -863,15 +634,13 @@ def list( query=maybe_transform( { "company_id": company_id, + "access_pass_types": access_pass_types, "after": after, "before": before, - "created_after": created_after, - "created_before": created_before, "direction": direction, "first": first, "last": last, "order": order, - "product_types": product_types, "visibilities": visibilities, }, product_list_params.ProductListParams, @@ -891,12 +660,10 @@ async def delete( extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> ProductDeleteResponse: - """ - Permanently delete a product and remove it from the company's catalog. - - Required permissions: + """Deletes a product. - - `access_pass:delete` + Only products with no memberships, entries, reviews, or + invoices can be deleted. Args: extra_headers: Send extra headers diff --git a/src/whop_sdk/types/product_create_params.py b/src/whop_sdk/types/product_create_params.py index 103fbc3d..29597ede 100644 --- a/src/whop_sdk/types/product_create_params.py +++ b/src/whop_sdk/types/product_create_params.py @@ -2,151 +2,60 @@ from __future__ import annotations -from typing import Dict, Iterable, Optional -from typing_extensions import Literal, Required, TypedDict +from typing import Optional +from typing_extensions import Required, TypedDict -from .._types import SequenceNotStr -from .shared.currency import Currency -from .shared.plan_type import PlanType -from .shared.custom_cta import CustomCta -from .shared.visibility import Visibility -from .shared.release_method import ReleaseMethod -from .shared.global_affiliate_status import GlobalAffiliateStatus - -__all__ = ["ProductCreateParams", "PlanOptions", "PlanOptionsCustomField"] +__all__ = ["ProductCreateParams"] class ProductCreateParams(TypedDict, total=False): - company_id: Required[str] - """The unique identifier of the company to create this product for.""" - title: Required[str] """The display name of the product. Maximum 80 characters.""" collect_shipping_address: Optional[bool] - """Whether the checkout flow collects a shipping address from the customer.""" + """Whether to collect a shipping address at checkout.""" + + company_id: str + """The unique identifier of the company to create this product for.""" - custom_cta: Optional[CustomCta] - """The different types of custom CTAs that can be selected.""" + custom_cta: Optional[str] + """The call-to-action button label.""" custom_cta_url: Optional[str] - """ - A URL that the call-to-action button links to instead of the default checkout - flow. - """ + """A URL the call-to-action button links to.""" custom_statement_descriptor: Optional[str] - """A custom text label that appears on the customer's bank statement. - - Must be 5-22 characters, contain at least one letter, and not contain <, >, \\,, - ', or " characters. - """ + """Custom bank statement descriptor. Must start with WHOP\\**.""" description: Optional[str] - """A written description of the product displayed on its product page.""" - - experience_ids: Optional[SequenceNotStr[str]] - """The unique identifiers of experiences to connect to this product.""" + """A written description displayed on the product page.""" global_affiliate_percentage: Optional[float] - """ - The commission rate as a percentage that affiliates earn through the global - affiliate program. - """ + """The commission rate affiliates earn.""" - global_affiliate_status: Optional[GlobalAffiliateStatus] - """The different statuses of the global affiliate program for a product.""" + global_affiliate_status: str + """The enrollment status in the global affiliate program.""" headline: Optional[str] - """A short marketing headline displayed prominently on the product page.""" + """A short marketing headline for the product page.""" member_affiliate_percentage: Optional[float] - """ - The commission rate as a percentage that members earn through the member - affiliate program. - """ - - member_affiliate_status: Optional[GlobalAffiliateStatus] - """The different statuses of the global affiliate program for a product.""" + """The commission rate members earn.""" - metadata: Optional[Dict[str, object]] - """Custom key-value pairs to store on the product. + member_affiliate_status: str + """The enrollment status in the member affiliate program.""" - Included in webhook payloads for payment and membership events. Max 50 keys, 500 - chars per key, 5000 chars per value. - """ - - plan_options: Optional[PlanOptions] - """Configuration for an automatically generated plan to attach to this product.""" + metadata: Optional[object] + """Custom key-value pairs to store on the product.""" product_tax_code_id: Optional[str] - """The unique identifier of the tax classification code to apply to this product.""" + """The unique identifier of the tax classification code.""" redirect_purchase_url: Optional[str] - """A URL to redirect the customer to after completing a purchase.""" + """A URL to redirect the customer to after purchase.""" route: Optional[str] """The URL slug for the product's public link.""" - send_welcome_message: Optional[bool] - """ - Whether to send an automated welcome message via support chat when a user joins - this product. Defaults to true. - """ - - visibility: Optional[Visibility] - """Visibility of a resource""" - - -class PlanOptionsCustomField(TypedDict, total=False): - field_type: Required[Literal["text"]] - """The type of the custom field.""" - - name: Required[str] - """The name of the custom field.""" - - id: Optional[str] - """The ID of the custom field (if being updated)""" - - order: Optional[int] - """The order of the field.""" - - placeholder: Optional[str] - """The placeholder value of the field.""" - - required: Optional[bool] - """Whether or not the field is required.""" - - -class PlanOptions(TypedDict, total=False): - """Configuration for an automatically generated plan to attach to this product.""" - - base_currency: Optional[Currency] - """The available currencies on the platform""" - - billing_period: Optional[int] - """The interval at which the plan charges (renewal plans).""" - - custom_fields: Optional[Iterable[PlanOptionsCustomField]] - """An array of custom field objects.""" - - initial_price: Optional[float] - """An additional amount charged upon first purchase. - - Provided as a number in the specified currency. Eg: 10.43 for $10.43 USD. - """ - - plan_type: Optional[PlanType] - """The type of plan that can be attached to a product""" - - release_method: Optional[ReleaseMethod] - """The methods of how a plan can be released.""" - - renewal_price: Optional[float] - """The amount the customer is charged every billing period. - - Provided as a number in the specified currency. Eg: 10.43 for $10.43 USD. - """ - - visibility: Optional[Visibility] - """Visibility of a resource""" + visibility: str + """Whether the product is visible to customers.""" diff --git a/src/whop_sdk/types/product_list_params.py b/src/whop_sdk/types/product_list_params.py index 47862a61..5b666c38 100644 --- a/src/whop_sdk/types/product_list_params.py +++ b/src/whop_sdk/types/product_list_params.py @@ -2,14 +2,9 @@ from __future__ import annotations -from typing import List, Union, Optional -from datetime import datetime -from typing_extensions import Literal, Required, Annotated, TypedDict +from typing_extensions import Literal, Required, TypedDict -from .._utils import PropertyInfo -from .shared.direction import Direction -from .shared.access_pass_type import AccessPassType -from .shared.visibility_filter import VisibilityFilter +from .._types import SequenceNotStr __all__ = ["ProductListParams"] @@ -18,32 +13,26 @@ class ProductListParams(TypedDict, total=False): company_id: Required[str] """The unique identifier of the company to list products for.""" - after: Optional[str] - """Returns the elements in the list that come after the specified cursor.""" + access_pass_types: SequenceNotStr[str] + """Filter to only products matching these types.""" - before: Optional[str] - """Returns the elements in the list that come before the specified cursor.""" + after: str + """A cursor; returns products after this position.""" - created_after: Annotated[Union[str, datetime, None], PropertyInfo(format="iso8601")] - """Only return products created after this timestamp.""" + before: str + """A cursor; returns products before this position.""" - created_before: Annotated[Union[str, datetime, None], PropertyInfo(format="iso8601")] - """Only return products created before this timestamp.""" + direction: Literal["asc", "desc"] + """The sort direction for results. Defaults to descending.""" - direction: Optional[Direction] - """The direction of the sort.""" + first: int + """The number of products to return (default and max 100).""" - first: Optional[int] - """Returns the first _n_ elements from the list.""" + last: int + """The number of products to return from the end of the range.""" - last: Optional[int] - """Returns the last _n_ elements from the list.""" + order: str + """The field to sort results by. Defaults to created_at.""" - order: Optional[Literal["active_memberships_count", "created_at", "usd_gmv", "usd_gmv_30_days"]] - """The ways a relation of AccessPasses can be ordered""" - - product_types: Optional[List[AccessPassType]] - """Filter to only products matching these type classifications.""" - - visibilities: Optional[List[VisibilityFilter]] + visibilities: SequenceNotStr[str] """Filter to only products matching these visibility states.""" diff --git a/src/whop_sdk/types/product_update_params.py b/src/whop_sdk/types/product_update_params.py index 64a7c8c1..d9d48096 100644 --- a/src/whop_sdk/types/product_update_params.py +++ b/src/whop_sdk/types/product_update_params.py @@ -2,107 +2,24 @@ from __future__ import annotations -from typing import Dict, Iterable, Optional -from typing_extensions import Required, TypedDict +from typing import Optional +from typing_extensions import TypedDict -from .shared.custom_cta import CustomCta -from .shared.visibility import Visibility -from .shared.global_affiliate_status import GlobalAffiliateStatus - -__all__ = ["ProductUpdateParams", "GalleryImage", "StorePageConfig"] +__all__ = ["ProductUpdateParams"] class ProductUpdateParams(TypedDict, total=False): - collect_shipping_address: Optional[bool] - """Whether the checkout flow collects a shipping address from the customer.""" - - custom_cta: Optional[CustomCta] - """The different types of custom CTAs that can be selected.""" - - custom_cta_url: Optional[str] - """ - A URL that the call-to-action button links to instead of the default checkout - flow. - """ - - custom_statement_descriptor: Optional[str] - """A custom text label that appears on the customer's bank statement. - - Must be 5-22 characters, contain at least one letter, and not contain <, >, \\,, - ', or " characters. - """ - description: Optional[str] - """A written description of the product displayed on its product page.""" - - gallery_images: Optional[Iterable[GalleryImage]] - """The gallery images for the product.""" - - global_affiliate_percentage: Optional[float] - """ - The commission rate as a percentage that affiliates earn through the global - affiliate program. - """ - - global_affiliate_status: Optional[GlobalAffiliateStatus] - """The different statuses of the global affiliate program for a product.""" + """A written description displayed on the product page.""" headline: Optional[str] - """A short marketing headline displayed prominently on the product page.""" - - member_affiliate_percentage: Optional[float] - """ - The commission rate as a percentage that members earn through the member - affiliate program. - """ - - member_affiliate_status: Optional[GlobalAffiliateStatus] - """The different statuses of the global affiliate program for a product.""" - - metadata: Optional[Dict[str, object]] - """Custom key-value pairs to store on the product. - - Included in webhook payloads for payment and membership events. Max 50 keys, 500 - chars per key, 5000 chars per value. - """ - - product_tax_code_id: Optional[str] - """The unique identifier of the tax classification code to apply to this product.""" - - redirect_purchase_url: Optional[str] - """A URL to redirect the customer to after completing a purchase.""" - - route: Optional[str] - """The URL slug for the product's public link.""" - - send_welcome_message: Optional[bool] - """ - Whether to send an automated welcome message via support chat when a user joins - this product. - """ - - store_page_config: Optional[StorePageConfig] - """Layout and display configuration for this product on the company's store page.""" - - title: Optional[str] - """The display name of the product. Maximum 80 characters.""" - - visibility: Optional[Visibility] - """Visibility of a resource""" - - -class GalleryImage(TypedDict, total=False): - """Input for an attachment""" - - id: Required[str] - """The ID of an existing file object.""" - + """A short marketing headline for the product page.""" -class StorePageConfig(TypedDict, total=False): - """Layout and display configuration for this product on the company's store page.""" + metadata: Optional[object] + """Custom key-value pairs to store on the product.""" - custom_cta: Optional[str] - """Custom call-to-action text for the product's store page.""" + title: str + """The display name of the product.""" - show_price: Optional[bool] - """Whether or not to show the price on the product's store page.""" + visibility: str + """Whether the product is visible to customers.""" diff --git a/src/whop_sdk/types/shared_params/__init__.py b/src/whop_sdk/types/shared_params/__init__.py index eb838c16..4ae01cc7 100644 --- a/src/whop_sdk/types/shared_params/__init__.py +++ b/src/whop_sdk/types/shared_params/__init__.py @@ -4,7 +4,6 @@ from .tax_type import TaxType as TaxType from .direction import Direction as Direction from .plan_type import PlanType as PlanType -from .custom_cta import CustomCta as CustomCta from .promo_type import PromoType as PromoType from .visibility import Visibility as Visibility from .access_level import AccessLevel as AccessLevel @@ -17,10 +16,8 @@ from .receipt_status import ReceiptStatus as ReceiptStatus from .release_method import ReleaseMethod as ReleaseMethod from .member_statuses import MemberStatuses as MemberStatuses -from .access_pass_type import AccessPassType as AccessPassType from .collection_method import CollectionMethod as CollectionMethod from .membership_status import MembershipStatus as MembershipStatus -from .visibility_filter import VisibilityFilter as VisibilityFilter from .app_build_statuses import AppBuildStatuses as AppBuildStatuses from .who_can_post_types import WhoCanPostTypes as WhoCanPostTypes from .app_build_platforms import AppBuildPlatforms as AppBuildPlatforms diff --git a/src/whop_sdk/types/shared_params/access_pass_type.py b/src/whop_sdk/types/shared_params/access_pass_type.py deleted file mode 100644 index 3f760c67..00000000 --- a/src/whop_sdk/types/shared_params/access_pass_type.py +++ /dev/null @@ -1,9 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from __future__ import annotations - -from typing_extensions import Literal, TypeAlias - -__all__ = ["AccessPassType"] - -AccessPassType: TypeAlias = Literal["regular", "app", "experience_upsell", "api_only"] diff --git a/src/whop_sdk/types/shared_params/custom_cta.py b/src/whop_sdk/types/shared_params/custom_cta.py deleted file mode 100644 index 0c2f5d19..00000000 --- a/src/whop_sdk/types/shared_params/custom_cta.py +++ /dev/null @@ -1,23 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from __future__ import annotations - -from typing_extensions import Literal, TypeAlias - -__all__ = ["CustomCta"] - -CustomCta: TypeAlias = Literal[ - "get_access", - "join", - "order_now", - "shop_now", - "call_now", - "donate_now", - "contact_us", - "sign_up", - "subscribe", - "purchase", - "get_offer", - "apply_now", - "complete_order", -] diff --git a/src/whop_sdk/types/shared_params/visibility_filter.py b/src/whop_sdk/types/shared_params/visibility_filter.py deleted file mode 100644 index d01e6cb1..00000000 --- a/src/whop_sdk/types/shared_params/visibility_filter.py +++ /dev/null @@ -1,11 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from __future__ import annotations - -from typing_extensions import Literal, TypeAlias - -__all__ = ["VisibilityFilter"] - -VisibilityFilter: TypeAlias = Literal[ - "visible", "hidden", "archived", "quick_link", "all", "not_quick_link", "not_archived" -] diff --git a/tests/api_resources/test_products.py b/tests/api_resources/test_products.py index b9a1c329..10201f78 100644 --- a/tests/api_resources/test_products.py +++ b/tests/api_resources/test_products.py @@ -9,10 +9,7 @@ from whop_sdk import Whop, AsyncWhop from tests.utils import assert_matches_type -from whop_sdk.types import ( - ProductDeleteResponse, -) -from whop_sdk._utils import parse_datetime +from whop_sdk.types import ProductDeleteResponse from whop_sdk.pagination import SyncCursorPage, AsyncCursorPage from whop_sdk.types.shared import Product, ProductListItem @@ -26,7 +23,6 @@ class TestProducts: @parametrize def test_method_create(self, client: Whop) -> None: product = client.products.create( - company_id="biz_xxxxxxxxxxxxxx", title="title", ) assert_matches_type(Product, product, path=["response"]) @@ -35,44 +31,23 @@ def test_method_create(self, client: Whop) -> None: @parametrize def test_method_create_with_all_params(self, client: Whop) -> None: product = client.products.create( - company_id="biz_xxxxxxxxxxxxxx", title="title", collect_shipping_address=True, - custom_cta="get_access", + company_id="company_id", + custom_cta="custom_cta", custom_cta_url="custom_cta_url", custom_statement_descriptor="custom_statement_descriptor", description="description", - experience_ids=["string"], - global_affiliate_percentage=6.9, - global_affiliate_status="enabled", + global_affiliate_percentage=0, + global_affiliate_status="global_affiliate_status", headline="headline", - member_affiliate_percentage=6.9, - member_affiliate_status="enabled", - metadata={"foo": "bar"}, - plan_options={ - "base_currency": "usd", - "billing_period": 42, - "custom_fields": [ - { - "field_type": "text", - "name": "name", - "id": "id", - "order": 42, - "placeholder": "placeholder", - "required": True, - } - ], - "initial_price": 6.9, - "plan_type": "renewal", - "release_method": "buy_now", - "renewal_price": 6.9, - "visibility": "visible", - }, - product_tax_code_id="ptc_xxxxxxxxxxxxxx", + member_affiliate_percentage=0, + member_affiliate_status="member_affiliate_status", + metadata={}, + product_tax_code_id="product_tax_code_id", redirect_purchase_url="redirect_purchase_url", route="route", - send_welcome_message=True, - visibility="visible", + visibility="visibility", ) assert_matches_type(Product, product, path=["response"]) @@ -80,7 +55,6 @@ def test_method_create_with_all_params(self, client: Whop) -> None: @parametrize def test_raw_response_create(self, client: Whop) -> None: response = client.products.with_raw_response.create( - company_id="biz_xxxxxxxxxxxxxx", title="title", ) @@ -93,7 +67,6 @@ def test_raw_response_create(self, client: Whop) -> None: @parametrize def test_streaming_response_create(self, client: Whop) -> None: with client.products.with_streaming_response.create( - company_id="biz_xxxxxxxxxxxxxx", title="title", ) as response: assert not response.is_closed @@ -108,7 +81,7 @@ def test_streaming_response_create(self, client: Whop) -> None: @parametrize def test_method_retrieve(self, client: Whop) -> None: product = client.products.retrieve( - "prod_xxxxxxxxxxxxx", + "id", ) assert_matches_type(Product, product, path=["response"]) @@ -116,7 +89,7 @@ def test_method_retrieve(self, client: Whop) -> None: @parametrize def test_raw_response_retrieve(self, client: Whop) -> None: response = client.products.with_raw_response.retrieve( - "prod_xxxxxxxxxxxxx", + "id", ) assert response.is_closed is True @@ -128,7 +101,7 @@ def test_raw_response_retrieve(self, client: Whop) -> None: @parametrize def test_streaming_response_retrieve(self, client: Whop) -> None: with client.products.with_streaming_response.retrieve( - "prod_xxxxxxxxxxxxx", + "id", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -150,7 +123,7 @@ def test_path_params_retrieve(self, client: Whop) -> None: @parametrize def test_method_update(self, client: Whop) -> None: product = client.products.update( - id="prod_xxxxxxxxxxxxx", + id="id", ) assert_matches_type(Product, product, path=["response"]) @@ -158,29 +131,12 @@ def test_method_update(self, client: Whop) -> None: @parametrize def test_method_update_with_all_params(self, client: Whop) -> None: product = client.products.update( - id="prod_xxxxxxxxxxxxx", - collect_shipping_address=True, - custom_cta="get_access", - custom_cta_url="custom_cta_url", - custom_statement_descriptor="custom_statement_descriptor", + id="id", description="description", - gallery_images=[{"id": "id"}], - global_affiliate_percentage=6.9, - global_affiliate_status="enabled", headline="headline", - member_affiliate_percentage=6.9, - member_affiliate_status="enabled", - metadata={"foo": "bar"}, - product_tax_code_id="ptc_xxxxxxxxxxxxxx", - redirect_purchase_url="redirect_purchase_url", - route="route", - send_welcome_message=True, - store_page_config={ - "custom_cta": "custom_cta", - "show_price": True, - }, + metadata={}, title="title", - visibility="visible", + visibility="visibility", ) assert_matches_type(Product, product, path=["response"]) @@ -188,7 +144,7 @@ def test_method_update_with_all_params(self, client: Whop) -> None: @parametrize def test_raw_response_update(self, client: Whop) -> None: response = client.products.with_raw_response.update( - id="prod_xxxxxxxxxxxxx", + id="id", ) assert response.is_closed is True @@ -200,7 +156,7 @@ def test_raw_response_update(self, client: Whop) -> None: @parametrize def test_streaming_response_update(self, client: Whop) -> None: with client.products.with_streaming_response.update( - id="prod_xxxxxxxxxxxxx", + id="id", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -222,7 +178,7 @@ def test_path_params_update(self, client: Whop) -> None: @parametrize def test_method_list(self, client: Whop) -> None: product = client.products.list( - company_id="biz_xxxxxxxxxxxxxx", + company_id="company_id", ) assert_matches_type(SyncCursorPage[ProductListItem], product, path=["response"]) @@ -230,17 +186,15 @@ def test_method_list(self, client: Whop) -> None: @parametrize def test_method_list_with_all_params(self, client: Whop) -> None: product = client.products.list( - company_id="biz_xxxxxxxxxxxxxx", + company_id="company_id", + access_pass_types=["string"], after="after", before="before", - created_after=parse_datetime("2023-12-01T05:00:00.401Z"), - created_before=parse_datetime("2023-12-01T05:00:00.401Z"), direction="asc", - first=42, - last=42, - order="active_memberships_count", - product_types=["regular"], - visibilities=["visible"], + first=0, + last=0, + order="order", + visibilities=["string"], ) assert_matches_type(SyncCursorPage[ProductListItem], product, path=["response"]) @@ -248,7 +202,7 @@ def test_method_list_with_all_params(self, client: Whop) -> None: @parametrize def test_raw_response_list(self, client: Whop) -> None: response = client.products.with_raw_response.list( - company_id="biz_xxxxxxxxxxxxxx", + company_id="company_id", ) assert response.is_closed is True @@ -260,7 +214,7 @@ def test_raw_response_list(self, client: Whop) -> None: @parametrize def test_streaming_response_list(self, client: Whop) -> None: with client.products.with_streaming_response.list( - company_id="biz_xxxxxxxxxxxxxx", + company_id="company_id", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -274,7 +228,7 @@ def test_streaming_response_list(self, client: Whop) -> None: @parametrize def test_method_delete(self, client: Whop) -> None: product = client.products.delete( - "prod_xxxxxxxxxxxxx", + "id", ) assert_matches_type(ProductDeleteResponse, product, path=["response"]) @@ -282,7 +236,7 @@ def test_method_delete(self, client: Whop) -> None: @parametrize def test_raw_response_delete(self, client: Whop) -> None: response = client.products.with_raw_response.delete( - "prod_xxxxxxxxxxxxx", + "id", ) assert response.is_closed is True @@ -294,7 +248,7 @@ def test_raw_response_delete(self, client: Whop) -> None: @parametrize def test_streaming_response_delete(self, client: Whop) -> None: with client.products.with_streaming_response.delete( - "prod_xxxxxxxxxxxxx", + "id", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -322,7 +276,6 @@ class TestAsyncProducts: @parametrize async def test_method_create(self, async_client: AsyncWhop) -> None: product = await async_client.products.create( - company_id="biz_xxxxxxxxxxxxxx", title="title", ) assert_matches_type(Product, product, path=["response"]) @@ -331,44 +284,23 @@ async def test_method_create(self, async_client: AsyncWhop) -> None: @parametrize async def test_method_create_with_all_params(self, async_client: AsyncWhop) -> None: product = await async_client.products.create( - company_id="biz_xxxxxxxxxxxxxx", title="title", collect_shipping_address=True, - custom_cta="get_access", + company_id="company_id", + custom_cta="custom_cta", custom_cta_url="custom_cta_url", custom_statement_descriptor="custom_statement_descriptor", description="description", - experience_ids=["string"], - global_affiliate_percentage=6.9, - global_affiliate_status="enabled", + global_affiliate_percentage=0, + global_affiliate_status="global_affiliate_status", headline="headline", - member_affiliate_percentage=6.9, - member_affiliate_status="enabled", - metadata={"foo": "bar"}, - plan_options={ - "base_currency": "usd", - "billing_period": 42, - "custom_fields": [ - { - "field_type": "text", - "name": "name", - "id": "id", - "order": 42, - "placeholder": "placeholder", - "required": True, - } - ], - "initial_price": 6.9, - "plan_type": "renewal", - "release_method": "buy_now", - "renewal_price": 6.9, - "visibility": "visible", - }, - product_tax_code_id="ptc_xxxxxxxxxxxxxx", + member_affiliate_percentage=0, + member_affiliate_status="member_affiliate_status", + metadata={}, + product_tax_code_id="product_tax_code_id", redirect_purchase_url="redirect_purchase_url", route="route", - send_welcome_message=True, - visibility="visible", + visibility="visibility", ) assert_matches_type(Product, product, path=["response"]) @@ -376,7 +308,6 @@ async def test_method_create_with_all_params(self, async_client: AsyncWhop) -> N @parametrize async def test_raw_response_create(self, async_client: AsyncWhop) -> None: response = await async_client.products.with_raw_response.create( - company_id="biz_xxxxxxxxxxxxxx", title="title", ) @@ -389,7 +320,6 @@ async def test_raw_response_create(self, async_client: AsyncWhop) -> None: @parametrize async def test_streaming_response_create(self, async_client: AsyncWhop) -> None: async with async_client.products.with_streaming_response.create( - company_id="biz_xxxxxxxxxxxxxx", title="title", ) as response: assert not response.is_closed @@ -404,7 +334,7 @@ async def test_streaming_response_create(self, async_client: AsyncWhop) -> None: @parametrize async def test_method_retrieve(self, async_client: AsyncWhop) -> None: product = await async_client.products.retrieve( - "prod_xxxxxxxxxxxxx", + "id", ) assert_matches_type(Product, product, path=["response"]) @@ -412,7 +342,7 @@ async def test_method_retrieve(self, async_client: AsyncWhop) -> None: @parametrize async def test_raw_response_retrieve(self, async_client: AsyncWhop) -> None: response = await async_client.products.with_raw_response.retrieve( - "prod_xxxxxxxxxxxxx", + "id", ) assert response.is_closed is True @@ -424,7 +354,7 @@ async def test_raw_response_retrieve(self, async_client: AsyncWhop) -> None: @parametrize async def test_streaming_response_retrieve(self, async_client: AsyncWhop) -> None: async with async_client.products.with_streaming_response.retrieve( - "prod_xxxxxxxxxxxxx", + "id", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -446,7 +376,7 @@ async def test_path_params_retrieve(self, async_client: AsyncWhop) -> None: @parametrize async def test_method_update(self, async_client: AsyncWhop) -> None: product = await async_client.products.update( - id="prod_xxxxxxxxxxxxx", + id="id", ) assert_matches_type(Product, product, path=["response"]) @@ -454,29 +384,12 @@ async def test_method_update(self, async_client: AsyncWhop) -> None: @parametrize async def test_method_update_with_all_params(self, async_client: AsyncWhop) -> None: product = await async_client.products.update( - id="prod_xxxxxxxxxxxxx", - collect_shipping_address=True, - custom_cta="get_access", - custom_cta_url="custom_cta_url", - custom_statement_descriptor="custom_statement_descriptor", + id="id", description="description", - gallery_images=[{"id": "id"}], - global_affiliate_percentage=6.9, - global_affiliate_status="enabled", headline="headline", - member_affiliate_percentage=6.9, - member_affiliate_status="enabled", - metadata={"foo": "bar"}, - product_tax_code_id="ptc_xxxxxxxxxxxxxx", - redirect_purchase_url="redirect_purchase_url", - route="route", - send_welcome_message=True, - store_page_config={ - "custom_cta": "custom_cta", - "show_price": True, - }, + metadata={}, title="title", - visibility="visible", + visibility="visibility", ) assert_matches_type(Product, product, path=["response"]) @@ -484,7 +397,7 @@ async def test_method_update_with_all_params(self, async_client: AsyncWhop) -> N @parametrize async def test_raw_response_update(self, async_client: AsyncWhop) -> None: response = await async_client.products.with_raw_response.update( - id="prod_xxxxxxxxxxxxx", + id="id", ) assert response.is_closed is True @@ -496,7 +409,7 @@ async def test_raw_response_update(self, async_client: AsyncWhop) -> None: @parametrize async def test_streaming_response_update(self, async_client: AsyncWhop) -> None: async with async_client.products.with_streaming_response.update( - id="prod_xxxxxxxxxxxxx", + id="id", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -518,7 +431,7 @@ async def test_path_params_update(self, async_client: AsyncWhop) -> None: @parametrize async def test_method_list(self, async_client: AsyncWhop) -> None: product = await async_client.products.list( - company_id="biz_xxxxxxxxxxxxxx", + company_id="company_id", ) assert_matches_type(AsyncCursorPage[ProductListItem], product, path=["response"]) @@ -526,17 +439,15 @@ async def test_method_list(self, async_client: AsyncWhop) -> None: @parametrize async def test_method_list_with_all_params(self, async_client: AsyncWhop) -> None: product = await async_client.products.list( - company_id="biz_xxxxxxxxxxxxxx", + company_id="company_id", + access_pass_types=["string"], after="after", before="before", - created_after=parse_datetime("2023-12-01T05:00:00.401Z"), - created_before=parse_datetime("2023-12-01T05:00:00.401Z"), direction="asc", - first=42, - last=42, - order="active_memberships_count", - product_types=["regular"], - visibilities=["visible"], + first=0, + last=0, + order="order", + visibilities=["string"], ) assert_matches_type(AsyncCursorPage[ProductListItem], product, path=["response"]) @@ -544,7 +455,7 @@ async def test_method_list_with_all_params(self, async_client: AsyncWhop) -> Non @parametrize async def test_raw_response_list(self, async_client: AsyncWhop) -> None: response = await async_client.products.with_raw_response.list( - company_id="biz_xxxxxxxxxxxxxx", + company_id="company_id", ) assert response.is_closed is True @@ -556,7 +467,7 @@ async def test_raw_response_list(self, async_client: AsyncWhop) -> None: @parametrize async def test_streaming_response_list(self, async_client: AsyncWhop) -> None: async with async_client.products.with_streaming_response.list( - company_id="biz_xxxxxxxxxxxxxx", + company_id="company_id", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -570,7 +481,7 @@ async def test_streaming_response_list(self, async_client: AsyncWhop) -> None: @parametrize async def test_method_delete(self, async_client: AsyncWhop) -> None: product = await async_client.products.delete( - "prod_xxxxxxxxxxxxx", + "id", ) assert_matches_type(ProductDeleteResponse, product, path=["response"]) @@ -578,7 +489,7 @@ async def test_method_delete(self, async_client: AsyncWhop) -> None: @parametrize async def test_raw_response_delete(self, async_client: AsyncWhop) -> None: response = await async_client.products.with_raw_response.delete( - "prod_xxxxxxxxxxxxx", + "id", ) assert response.is_closed is True @@ -590,7 +501,7 @@ async def test_raw_response_delete(self, async_client: AsyncWhop) -> None: @parametrize async def test_streaming_response_delete(self, async_client: AsyncWhop) -> None: async with async_client.products.with_streaming_response.delete( - "prod_xxxxxxxxxxxxx", + "id", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" From 68881f0fd979daa83f1e5fed44dfc3e8610044c4 Mon Sep 17 00:00:00 2001 From: stlc-bot Date: Thu, 11 Jun 2026 05:51:55 +0000 Subject: [PATCH 027/109] Move wallet balance onto the Account and User objects Stainless-Generated-From: 5f7d841d8f6473f35d1893d2e9fc8c2fc76f373c --- .stats.yml | 2 +- api.md | 8 +- src/whop_sdk/resources/wallets.py | 91 +------------------ src/whop_sdk/types/__init__.py | 2 - src/whop_sdk/types/account.py | 47 +++++++++- src/whop_sdk/types/user.py | 49 +++++++++- src/whop_sdk/types/wallet_balance_params.py | 12 --- src/whop_sdk/types/wallet_balance_response.py | 32 ------- tests/api_resources/test_wallets.py | 74 +-------------- 9 files changed, 97 insertions(+), 220 deletions(-) delete mode 100644 src/whop_sdk/types/wallet_balance_params.py delete mode 100644 src/whop_sdk/types/wallet_balance_response.py diff --git a/.stats.yml b/.stats.yml index 5aab750e..9d0edd97 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1 +1 @@ -configured_endpoints: 233 +configured_endpoints: 232 diff --git a/api.md b/api.md index 6486a56e..cb8ce14e 100644 --- a/api.md +++ b/api.md @@ -732,18 +732,12 @@ Methods: Types: ```python -from whop_sdk.types import ( - AccountWallet, - WalletListResponse, - WalletBalanceResponse, - WalletSendResponse, -) +from whop_sdk.types import AccountWallet, WalletListResponse, WalletSendResponse ``` Methods: - client.wallets.list() -> WalletListResponse -- client.wallets.balance(\*\*params) -> WalletBalanceResponse - client.wallets.send(\*\*params) -> WalletSendResponse # FinancialActivity diff --git a/src/whop_sdk/resources/wallets.py b/src/whop_sdk/resources/wallets.py index d7cb79dc..7ead30a7 100644 --- a/src/whop_sdk/resources/wallets.py +++ b/src/whop_sdk/resources/wallets.py @@ -7,7 +7,7 @@ import httpx -from ..types import wallet_send_params, wallet_balance_params +from ..types import wallet_send_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 @@ -21,7 +21,6 @@ from .._base_client import make_request_options from ..types.wallet_list_response import WalletListResponse from ..types.wallet_send_response import WalletSendResponse -from ..types.wallet_balance_response import WalletBalanceResponse __all__ = ["WalletsResource", "AsyncWalletsResource"] @@ -65,43 +64,6 @@ def list( cast_to=WalletListResponse, ) - def balance( - self, - *, - account_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, - ) -> WalletBalanceResponse: - """ - Returns per-token balances held in an account's wallet. - - Args: - account_id: The business or user account ID whose wallet balance should be returned. - - 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( - "/wallets/balance", - options=make_request_options( - extra_headers=extra_headers, - extra_query=extra_query, - extra_body=extra_body, - timeout=timeout, - query=maybe_transform({"account_id": account_id}, wallet_balance_params.WalletBalanceParams), - ), - cast_to=WalletBalanceResponse, - ) - def send( self, *, @@ -215,45 +177,6 @@ async def list( cast_to=WalletListResponse, ) - async def balance( - self, - *, - account_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, - ) -> WalletBalanceResponse: - """ - Returns per-token balances held in an account's wallet. - - Args: - account_id: The business or user account ID whose wallet balance should be returned. - - 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( - "/wallets/balance", - options=make_request_options( - extra_headers=extra_headers, - extra_query=extra_query, - extra_body=extra_body, - timeout=timeout, - query=await async_maybe_transform( - {"account_id": account_id}, wallet_balance_params.WalletBalanceParams - ), - ), - cast_to=WalletBalanceResponse, - ) - async def send( self, *, @@ -335,9 +258,6 @@ def __init__(self, wallets: WalletsResource) -> None: self.list = to_raw_response_wrapper( wallets.list, ) - self.balance = to_raw_response_wrapper( - wallets.balance, - ) self.send = to_raw_response_wrapper( wallets.send, ) @@ -350,9 +270,6 @@ def __init__(self, wallets: AsyncWalletsResource) -> None: self.list = async_to_raw_response_wrapper( wallets.list, ) - self.balance = async_to_raw_response_wrapper( - wallets.balance, - ) self.send = async_to_raw_response_wrapper( wallets.send, ) @@ -365,9 +282,6 @@ def __init__(self, wallets: WalletsResource) -> None: self.list = to_streamed_response_wrapper( wallets.list, ) - self.balance = to_streamed_response_wrapper( - wallets.balance, - ) self.send = to_streamed_response_wrapper( wallets.send, ) @@ -380,9 +294,6 @@ def __init__(self, wallets: AsyncWalletsResource) -> None: self.list = async_to_streamed_response_wrapper( wallets.list, ) - self.balance = async_to_streamed_response_wrapper( - wallets.balance, - ) self.send = async_to_streamed_response_wrapper( wallets.send, ) diff --git a/src/whop_sdk/types/__init__.py b/src/whop_sdk/types/__init__.py index ad6f8fb4..d04af606 100644 --- a/src/whop_sdk/types/__init__.py +++ b/src/whop_sdk/types/__init__.py @@ -215,7 +215,6 @@ from .refund_reference_type import RefundReferenceType as RefundReferenceType from .topup_create_response import TopupCreateResponse as TopupCreateResponse from .user_update_me_params import UserUpdateMeParams as UserUpdateMeParams -from .wallet_balance_params import WalletBalanceParams as WalletBalanceParams from .webhook_create_params import WebhookCreateParams as WebhookCreateParams from .webhook_list_response import WebhookListResponse as WebhookListResponse from .webhook_update_params import WebhookUpdateParams as WebhookUpdateParams @@ -257,7 +256,6 @@ from .product_delete_response import ProductDeleteResponse as ProductDeleteResponse from .refund_reference_status import RefundReferenceStatus as RefundReferenceStatus from .verification_error_code import VerificationErrorCode as VerificationErrorCode -from .wallet_balance_response import WalletBalanceResponse as WalletBalanceResponse from .webhook_create_response import WebhookCreateResponse as WebhookCreateResponse from .webhook_delete_response import WebhookDeleteResponse as WebhookDeleteResponse from .ad_group_delete_response import AdGroupDeleteResponse as AdGroupDeleteResponse diff --git a/src/whop_sdk/types/account.py b/src/whop_sdk/types/account.py index 62430ec7..7ff5abaf 100644 --- a/src/whop_sdk/types/account.py +++ b/src/whop_sdk/types/account.py @@ -6,13 +6,58 @@ from .account_wallet import AccountWallet from .account_social_link import AccountSocialLink -__all__ = ["Account"] +__all__ = ["Account", "Balance", "BalanceToken"] + + +class BalanceToken(BaseModel): + """The account's per-token holdings""" + + balance: str + """The token amount in native units, as a decimal string""" + + icon_url: Optional[str] = None + """The URL of the token icon, when available""" + + name: str + """The token's display name""" + + price_usd: Optional[float] = None + """The USD price per token, or null when no exchange rate is available""" + + symbol: str + """The token's display symbol, e.g. USDT or cbBTC""" + + token_address: Optional[str] = None + """The token contract address, when the holding maps to a single contract""" + + value_usd: Optional[str] = None + """The USD value of the holding, or null when no exchange rate is available""" + + +class Balance(BaseModel): + """The account's balance by token. + + Only computed on single-account reads (retrieve and me); null on list responses, on writes, when the caller's token lacks the balance-read permission for the account, and when the balance source is unavailable + """ + + tokens: List[BalanceToken] + + total_usd: str + """The total USD value across all tokens with a known exchange rate""" class Account(BaseModel): id: str """The ID of the account, which will look like biz\\__******\\********""" + balance: Optional[Balance] = None + """The account's balance by token. + + Only computed on single-account reads (retrieve and me); null on list responses, + on writes, when the caller's token lacks the balance-read permission for the + account, and when the balance source is unavailable + """ + banner_image_url: Optional[str] = None """The URL of the account banner image""" diff --git a/src/whop_sdk/types/user.py b/src/whop_sdk/types/user.py index 9e5491e4..a1078fd6 100644 --- a/src/whop_sdk/types/user.py +++ b/src/whop_sdk/types/user.py @@ -1,16 +1,61 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -from typing import Optional +from typing import List, Optional from .._models import BaseModel -__all__ = ["User"] +__all__ = ["User", "Balance", "BalanceToken"] + + +class BalanceToken(BaseModel): + """The account's per-token holdings""" + + balance: str + """The token amount in native units, as a decimal string""" + + icon_url: Optional[str] = None + """The URL of the token icon, when available""" + + name: str + """The token's display name""" + + price_usd: Optional[float] = None + """The USD price per token, or null when no exchange rate is available""" + + symbol: str + """The token's display symbol, e.g. USDT or cbBTC""" + + token_address: Optional[str] = None + """The token contract address, when the holding maps to a single contract""" + + value_usd: Optional[str] = None + """The USD value of the holding, or null when no exchange rate is available""" + + +class Balance(BaseModel): + """The user's balance by token. + + Only computed on the self-view (GET /users/me) for callers with the company:balance:read scope; null otherwise and when the balance source is unavailable + """ + + tokens: List[BalanceToken] + + total_usd: str + """The total USD value across all tokens with a known exchange rate""" class User(BaseModel): id: str """The ID of the user, which will look like user\\__******\\********""" + balance: Optional[Balance] = None + """The user's balance by token. + + Only computed on the self-view (GET /users/me) for callers with the + company:balance:read scope; null otherwise and when the balance source is + unavailable + """ + bio: Optional[str] = None """The user's biography""" diff --git a/src/whop_sdk/types/wallet_balance_params.py b/src/whop_sdk/types/wallet_balance_params.py deleted file mode 100644 index 0c92e9fa..00000000 --- a/src/whop_sdk/types/wallet_balance_params.py +++ /dev/null @@ -1,12 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from __future__ import annotations - -from typing_extensions import Required, TypedDict - -__all__ = ["WalletBalanceParams"] - - -class WalletBalanceParams(TypedDict, total=False): - account_id: Required[str] - """The business or user account ID whose wallet balance should be returned.""" diff --git a/src/whop_sdk/types/wallet_balance_response.py b/src/whop_sdk/types/wallet_balance_response.py deleted file mode 100644 index 8ca6b314..00000000 --- a/src/whop_sdk/types/wallet_balance_response.py +++ /dev/null @@ -1,32 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from typing import List, Optional -from typing_extensions import Literal - -from .._models import BaseModel - -__all__ = ["WalletBalanceResponse", "Token"] - - -class Token(BaseModel): - balance: str - - icon_url: Optional[str] = None - - name: str - - price_usd: Optional[float] = None - - symbol: str - - token_address: Optional[str] = None - - value_usd: Optional[str] = None - - -class WalletBalanceResponse(BaseModel): - object: Literal["balance"] - - tokens: List[Token] - - total_usd: str diff --git a/tests/api_resources/test_wallets.py b/tests/api_resources/test_wallets.py index acb5beb3..ed8d629a 100644 --- a/tests/api_resources/test_wallets.py +++ b/tests/api_resources/test_wallets.py @@ -9,11 +9,7 @@ from whop_sdk import Whop, AsyncWhop from tests.utils import assert_matches_type -from whop_sdk.types import ( - WalletListResponse, - WalletSendResponse, - WalletBalanceResponse, -) +from whop_sdk.types import WalletListResponse, WalletSendResponse from whop_sdk._utils import parse_datetime base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") @@ -50,40 +46,6 @@ 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_balance(self, client: Whop) -> None: - wallet = client.wallets.balance( - account_id="account_id", - ) - assert_matches_type(WalletBalanceResponse, wallet, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - def test_raw_response_balance(self, client: Whop) -> None: - response = client.wallets.with_raw_response.balance( - account_id="account_id", - ) - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - wallet = response.parse() - assert_matches_type(WalletBalanceResponse, wallet, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - def test_streaming_response_balance(self, client: Whop) -> None: - with client.wallets.with_streaming_response.balance( - account_id="account_id", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - wallet = response.parse() - assert_matches_type(WalletBalanceResponse, wallet, path=["response"]) - - assert cast(Any, response.is_closed) is True - @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_method_send(self, client: Whop) -> None: @@ -168,40 +130,6 @@ async def test_streaming_response_list(self, async_client: AsyncWhop) -> None: assert cast(Any, response.is_closed) is True - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - async def test_method_balance(self, async_client: AsyncWhop) -> None: - wallet = await async_client.wallets.balance( - account_id="account_id", - ) - assert_matches_type(WalletBalanceResponse, wallet, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - async def test_raw_response_balance(self, async_client: AsyncWhop) -> None: - response = await async_client.wallets.with_raw_response.balance( - account_id="account_id", - ) - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - wallet = await response.parse() - assert_matches_type(WalletBalanceResponse, wallet, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - async def test_streaming_response_balance(self, async_client: AsyncWhop) -> None: - async with async_client.wallets.with_streaming_response.balance( - account_id="account_id", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - wallet = await response.parse() - assert_matches_type(WalletBalanceResponse, wallet, path=["response"]) - - assert cast(Any, response.is_closed) is True - @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_method_send(self, async_client: AsyncWhop) -> None: From bb3ab490e3e9c95066d0c4fbcc914f93e820e8f4 Mon Sep 17 00:00:00 2001 From: stlc-bot Date: Thu, 11 Jun 2026 06:04:44 +0000 Subject: [PATCH 028/109] Add public GET /payouts API and power the Withdrawals tab with it Stainless-Generated-From: 99c59465e3275a62dbed5f91c1a53e3bc66016f1 --- .stats.yml | 2 +- api.md | 12 + src/whop_sdk/_client.py | 38 ++++ src/whop_sdk/resources/__init__.py | 14 ++ src/whop_sdk/resources/payouts.py | 242 +++++++++++++++++++++ src/whop_sdk/types/__init__.py | 2 + src/whop_sdk/types/payout_list_params.py | 30 +++ src/whop_sdk/types/payout_list_response.py | 58 +++++ tests/api_resources/test_payouts.py | 109 ++++++++++ 9 files changed, 506 insertions(+), 1 deletion(-) create mode 100644 src/whop_sdk/resources/payouts.py create mode 100644 src/whop_sdk/types/payout_list_params.py create mode 100644 src/whop_sdk/types/payout_list_response.py create mode 100644 tests/api_resources/test_payouts.py diff --git a/.stats.yml b/.stats.yml index 9d0edd97..5aab750e 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1 +1 @@ -configured_endpoints: 232 +configured_endpoints: 233 diff --git a/api.md b/api.md index cb8ce14e..a4776fbc 100644 --- a/api.md +++ b/api.md @@ -752,6 +752,18 @@ Methods: - client.financial_activity.list(\*\*params) -> FinancialActivityListResponse +# Payouts + +Types: + +```python +from whop_sdk.types import PayoutListResponse +``` + +Methods: + +- client.payouts.list(\*\*params) -> SyncCursorPage[PayoutListResponse] + # Swaps Types: diff --git a/src/whop_sdk/_client.py b/src/whop_sdk/_client.py index 89aa63fc..c48d2fa4 100644 --- a/src/whop_sdk/_client.py +++ b/src/whop_sdk/_client.py @@ -48,6 +48,7 @@ courses, entries, members, + payouts, refunds, reviews, wallets, @@ -113,6 +114,7 @@ from .resources.courses import CoursesResource, AsyncCoursesResource from .resources.entries import EntriesResource, AsyncEntriesResource from .resources.members import MembersResource, AsyncMembersResource + from .resources.payouts import PayoutsResource, AsyncPayoutsResource from .resources.refunds import RefundsResource, AsyncRefundsResource from .resources.reviews import ReviewsResource, AsyncReviewsResource from .resources.wallets import WalletsResource, AsyncWalletsResource @@ -536,6 +538,12 @@ def financial_activity(self) -> FinancialActivityResource: return FinancialActivityResource(self) + @cached_property + def payouts(self) -> PayoutsResource: + from .resources.payouts import PayoutsResource + + return PayoutsResource(self) + @cached_property def swaps(self) -> SwapsResource: from .resources.swaps import SwapsResource @@ -1184,6 +1192,12 @@ def financial_activity(self) -> AsyncFinancialActivityResource: return AsyncFinancialActivityResource(self) + @cached_property + def payouts(self) -> AsyncPayoutsResource: + from .resources.payouts import AsyncPayoutsResource + + return AsyncPayoutsResource(self) + @cached_property def swaps(self) -> AsyncSwapsResource: from .resources.swaps import AsyncSwapsResource @@ -1752,6 +1766,12 @@ def financial_activity(self) -> financial_activity.FinancialActivityResourceWith return FinancialActivityResourceWithRawResponse(self._client.financial_activity) + @cached_property + def payouts(self) -> payouts.PayoutsResourceWithRawResponse: + from .resources.payouts import PayoutsResourceWithRawResponse + + return PayoutsResourceWithRawResponse(self._client.payouts) + @cached_property def swaps(self) -> swaps.SwapsResourceWithRawResponse: from .resources.swaps import SwapsResourceWithRawResponse @@ -2202,6 +2222,12 @@ def financial_activity(self) -> financial_activity.AsyncFinancialActivityResourc return AsyncFinancialActivityResourceWithRawResponse(self._client.financial_activity) + @cached_property + def payouts(self) -> payouts.AsyncPayoutsResourceWithRawResponse: + from .resources.payouts import AsyncPayoutsResourceWithRawResponse + + return AsyncPayoutsResourceWithRawResponse(self._client.payouts) + @cached_property def swaps(self) -> swaps.AsyncSwapsResourceWithRawResponse: from .resources.swaps import AsyncSwapsResourceWithRawResponse @@ -2654,6 +2680,12 @@ def financial_activity(self) -> financial_activity.FinancialActivityResourceWith return FinancialActivityResourceWithStreamingResponse(self._client.financial_activity) + @cached_property + def payouts(self) -> payouts.PayoutsResourceWithStreamingResponse: + from .resources.payouts import PayoutsResourceWithStreamingResponse + + return PayoutsResourceWithStreamingResponse(self._client.payouts) + @cached_property def swaps(self) -> swaps.SwapsResourceWithStreamingResponse: from .resources.swaps import SwapsResourceWithStreamingResponse @@ -3108,6 +3140,12 @@ def financial_activity(self) -> financial_activity.AsyncFinancialActivityResourc return AsyncFinancialActivityResourceWithStreamingResponse(self._client.financial_activity) + @cached_property + def payouts(self) -> payouts.AsyncPayoutsResourceWithStreamingResponse: + from .resources.payouts import AsyncPayoutsResourceWithStreamingResponse + + return AsyncPayoutsResourceWithStreamingResponse(self._client.payouts) + @cached_property def swaps(self) -> swaps.AsyncSwapsResourceWithStreamingResponse: from .resources.swaps import AsyncSwapsResourceWithStreamingResponse diff --git a/src/whop_sdk/resources/__init__.py b/src/whop_sdk/resources/__init__.py index 222ce2ef..0e8ba666 100644 --- a/src/whop_sdk/resources/__init__.py +++ b/src/whop_sdk/resources/__init__.py @@ -96,6 +96,14 @@ MembersResourceWithStreamingResponse, AsyncMembersResourceWithStreamingResponse, ) +from .payouts import ( + PayoutsResource, + AsyncPayoutsResource, + PayoutsResourceWithRawResponse, + AsyncPayoutsResourceWithRawResponse, + PayoutsResourceWithStreamingResponse, + AsyncPayoutsResourceWithStreamingResponse, +) from .refunds import ( RefundsResource, AsyncRefundsResource, @@ -754,6 +762,12 @@ "AsyncFinancialActivityResourceWithRawResponse", "FinancialActivityResourceWithStreamingResponse", "AsyncFinancialActivityResourceWithStreamingResponse", + "PayoutsResource", + "AsyncPayoutsResource", + "PayoutsResourceWithRawResponse", + "AsyncPayoutsResourceWithRawResponse", + "PayoutsResourceWithStreamingResponse", + "AsyncPayoutsResourceWithStreamingResponse", "SwapsResource", "AsyncSwapsResource", "SwapsResourceWithRawResponse", diff --git a/src/whop_sdk/resources/payouts.py b/src/whop_sdk/resources/payouts.py new file mode 100644 index 00000000..4394b6ea --- /dev/null +++ b/src/whop_sdk/resources/payouts.py @@ -0,0 +1,242 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import httpx + +from ..types import payout_list_params +from .._types import Body, Omit, Query, Headers, NotGiven, omit, not_given +from .._utils import 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.payout_list_response import PayoutListResponse + +__all__ = ["PayoutsResource", "AsyncPayoutsResource"] + + +class PayoutsResource(SyncAPIResource): + @cached_property + def with_raw_response(self) -> PayoutsResourceWithRawResponse: + """ + 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 PayoutsResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> PayoutsResourceWithStreamingResponse: + """ + 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 PayoutsResourceWithStreamingResponse(self) + + def list( + self, + *, + account_id: str | Omit = omit, + after: str | Omit = omit, + before: str | Omit = omit, + currency: str | Omit = omit, + first: int | Omit = omit, + last: int | Omit = omit, + user_id: 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, + ) -> SyncCursorPage[PayoutListResponse]: + """Lists payouts (withdrawal requests) for a ledger account, most recent first. + + The + ledger's owner is passed as exactly one of account*id (a biz* identifier) or + user*id (a user* identifier). The saved payout method on each payout + additionally requires the payout:destination:read scope and is null without it. + + Args: + account_id: The owning account ID (a biz\\__ identifier). Provide this or user_id. + + after: Cursor to fetch the page after (from page_info.end_cursor). + + before: Cursor to fetch the page before (from page_info.start_cursor). + + currency: Optional currency code filter, for example usd. + + first: Number of payouts to return from the start of the window. + + last: Number of payouts to return from the end of the window. + + user_id: The owning user ID (a user\\__ identifier). Provide this or account_id. + + 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( + "/payouts", + page=SyncCursorPage[PayoutListResponse], + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "account_id": account_id, + "after": after, + "before": before, + "currency": currency, + "first": first, + "last": last, + "user_id": user_id, + }, + payout_list_params.PayoutListParams, + ), + ), + model=PayoutListResponse, + ) + + +class AsyncPayoutsResource(AsyncAPIResource): + @cached_property + def with_raw_response(self) -> AsyncPayoutsResourceWithRawResponse: + """ + 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 AsyncPayoutsResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncPayoutsResourceWithStreamingResponse: + """ + 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 AsyncPayoutsResourceWithStreamingResponse(self) + + def list( + self, + *, + account_id: str | Omit = omit, + after: str | Omit = omit, + before: str | Omit = omit, + currency: str | Omit = omit, + first: int | Omit = omit, + last: int | Omit = omit, + user_id: 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, + ) -> AsyncPaginator[PayoutListResponse, AsyncCursorPage[PayoutListResponse]]: + """Lists payouts (withdrawal requests) for a ledger account, most recent first. + + The + ledger's owner is passed as exactly one of account*id (a biz* identifier) or + user*id (a user* identifier). The saved payout method on each payout + additionally requires the payout:destination:read scope and is null without it. + + Args: + account_id: The owning account ID (a biz\\__ identifier). Provide this or user_id. + + after: Cursor to fetch the page after (from page_info.end_cursor). + + before: Cursor to fetch the page before (from page_info.start_cursor). + + currency: Optional currency code filter, for example usd. + + first: Number of payouts to return from the start of the window. + + last: Number of payouts to return from the end of the window. + + user_id: The owning user ID (a user\\__ identifier). Provide this or account_id. + + 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( + "/payouts", + page=AsyncCursorPage[PayoutListResponse], + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "account_id": account_id, + "after": after, + "before": before, + "currency": currency, + "first": first, + "last": last, + "user_id": user_id, + }, + payout_list_params.PayoutListParams, + ), + ), + model=PayoutListResponse, + ) + + +class PayoutsResourceWithRawResponse: + def __init__(self, payouts: PayoutsResource) -> None: + self._payouts = payouts + + self.list = to_raw_response_wrapper( + payouts.list, + ) + + +class AsyncPayoutsResourceWithRawResponse: + def __init__(self, payouts: AsyncPayoutsResource) -> None: + self._payouts = payouts + + self.list = async_to_raw_response_wrapper( + payouts.list, + ) + + +class PayoutsResourceWithStreamingResponse: + def __init__(self, payouts: PayoutsResource) -> None: + self._payouts = payouts + + self.list = to_streamed_response_wrapper( + payouts.list, + ) + + +class AsyncPayoutsResourceWithStreamingResponse: + def __init__(self, payouts: AsyncPayoutsResource) -> None: + self._payouts = payouts + + self.list = async_to_streamed_response_wrapper( + payouts.list, + ) diff --git a/src/whop_sdk/types/__init__.py b/src/whop_sdk/types/__init__.py index d04af606..112e1f86 100644 --- a/src/whop_sdk/types/__init__.py +++ b/src/whop_sdk/types/__init__.py @@ -133,6 +133,7 @@ from .lead_list_response import LeadListResponse as LeadListResponse from .lead_update_params import LeadUpdateParams as LeadUpdateParams from .member_list_params import MemberListParams as MemberListParams +from .payout_list_params import PayoutListParams as PayoutListParams from .plan_create_params import PlanCreateParams as PlanCreateParams from .plan_list_response import PlanListResponse as PlanListResponse from .plan_update_params import PlanUpdateParams as PlanUpdateParams @@ -172,6 +173,7 @@ from .file_create_response import FileCreateResponse as FileCreateResponse from .member_list_response import MemberListResponse as MemberListResponse from .payment_method_types import PaymentMethodTypes as PaymentMethodTypes +from .payout_list_response import PayoutListResponse as PayoutListResponse from .plan_delete_response import PlanDeleteResponse as PlanDeleteResponse from .reaction_list_params import ReactionListParams as ReactionListParams from .receipt_tax_behavior import ReceiptTaxBehavior as ReceiptTaxBehavior diff --git a/src/whop_sdk/types/payout_list_params.py b/src/whop_sdk/types/payout_list_params.py new file mode 100644 index 00000000..72274108 --- /dev/null +++ b/src/whop_sdk/types/payout_list_params.py @@ -0,0 +1,30 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import TypedDict + +__all__ = ["PayoutListParams"] + + +class PayoutListParams(TypedDict, total=False): + account_id: str + """The owning account ID (a biz\\__ identifier). Provide this or user_id.""" + + after: str + """Cursor to fetch the page after (from page_info.end_cursor).""" + + before: str + """Cursor to fetch the page before (from page_info.start_cursor).""" + + currency: str + """Optional currency code filter, for example usd.""" + + first: int + """Number of payouts to return from the start of the window.""" + + last: int + """Number of payouts to return from the end of the window.""" + + user_id: str + """The owning user ID (a user\\__ identifier). Provide this or account_id.""" diff --git a/src/whop_sdk/types/payout_list_response.py b/src/whop_sdk/types/payout_list_response.py new file mode 100644 index 00000000..2e662c5b --- /dev/null +++ b/src/whop_sdk/types/payout_list_response.py @@ -0,0 +1,58 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from datetime import datetime +from typing_extensions import Literal + +from .._models import BaseModel + +__all__ = ["PayoutListResponse", "PayoutToken", "PayoutTokenPayoutDestination"] + + +class PayoutTokenPayoutDestination(BaseModel): + icon_url: Optional[str] = None + + payer_name: Optional[str] = None + + +class PayoutToken(BaseModel): + """The saved payout method used. + + Requires payout:destination:read; null without it. + """ + + nickname: Optional[str] = None + + payout_destination: Optional[PayoutTokenPayoutDestination] = None + + +class PayoutListResponse(BaseModel): + id: str + + amount: float + """The payout amount in whole currency units.""" + + created_at: datetime + + currency: str + + estimated_arrival: Optional[datetime] = None + """Estimated time the funds become available in the destination account.""" + + fee_amount: float + """The fee charged for the payout, in the payout currency.""" + + object: Literal["payout"] + + payer_name: Optional[str] = None + """Name of the entity processing the payout.""" + + payout_token: Optional[PayoutToken] = None + """The saved payout method used. + + Requires payout:destination:read; null without it. + """ + + speed: Literal["standard", "instant"] + + status: Literal["requested", "awaiting_payment", "in_transit", "completed", "failed", "canceled", "denied"] diff --git a/tests/api_resources/test_payouts.py b/tests/api_resources/test_payouts.py new file mode 100644 index 00000000..df377533 --- /dev/null +++ b/tests/api_resources/test_payouts.py @@ -0,0 +1,109 @@ +# 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 PayoutListResponse +from whop_sdk.pagination import SyncCursorPage, AsyncCursorPage + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestPayouts: + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_list(self, client: Whop) -> None: + payout = client.payouts.list() + assert_matches_type(SyncCursorPage[PayoutListResponse], payout, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_list_with_all_params(self, client: Whop) -> None: + payout = client.payouts.list( + account_id="account_id", + after="after", + before="before", + currency="currency", + first=100, + last=100, + user_id="user_id", + ) + assert_matches_type(SyncCursorPage[PayoutListResponse], payout, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_raw_response_list(self, client: Whop) -> None: + response = client.payouts.with_raw_response.list() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + payout = response.parse() + assert_matches_type(SyncCursorPage[PayoutListResponse], payout, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_streaming_response_list(self, client: Whop) -> None: + with client.payouts.with_streaming_response.list() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + payout = response.parse() + assert_matches_type(SyncCursorPage[PayoutListResponse], payout, path=["response"]) + + assert cast(Any, response.is_closed) is True + + +class TestAsyncPayouts: + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_list(self, async_client: AsyncWhop) -> None: + payout = await async_client.payouts.list() + assert_matches_type(AsyncCursorPage[PayoutListResponse], payout, 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: + payout = await async_client.payouts.list( + account_id="account_id", + after="after", + before="before", + currency="currency", + first=100, + last=100, + user_id="user_id", + ) + assert_matches_type(AsyncCursorPage[PayoutListResponse], payout, 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.payouts.with_raw_response.list() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + payout = await response.parse() + assert_matches_type(AsyncCursorPage[PayoutListResponse], payout, 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.payouts.with_streaming_response.list() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + payout = await response.parse() + assert_matches_type(AsyncCursorPage[PayoutListResponse], payout, path=["response"]) + + assert cast(Any, response.is_closed) is True From 691a4f732334a360e606ef387209f768bef3fff8 Mon Sep 17 00:00:00 2001 From: stlc-bot Date: Thu, 11 Jun 2026 06:34:14 +0000 Subject: [PATCH 029/109] Revert "Products endpoints to v1 API (#13130)" Stainless-Generated-From: c38f0732fc11ce5d13c0fa540c6f7eede8d3987a --- src/whop_sdk/resources/products.py | 459 +++++++++++++----- src/whop_sdk/types/product_create_params.py | 141 +++++- src/whop_sdk/types/product_list_params.py | 45 +- src/whop_sdk/types/product_update_params.py | 105 +++- src/whop_sdk/types/shared_params/__init__.py | 3 + .../types/shared_params/access_pass_type.py | 9 + .../types/shared_params/custom_cta.py | 23 + .../types/shared_params/visibility_filter.py | 11 + tests/api_resources/test_products.py | 211 +++++--- 9 files changed, 780 insertions(+), 227 deletions(-) create mode 100644 src/whop_sdk/types/shared_params/access_pass_type.py create mode 100644 src/whop_sdk/types/shared_params/custom_cta.py create mode 100644 src/whop_sdk/types/shared_params/visibility_filter.py diff --git a/src/whop_sdk/resources/products.py b/src/whop_sdk/resources/products.py index 5bb4f3b4..505de8e1 100644 --- a/src/whop_sdk/resources/products.py +++ b/src/whop_sdk/resources/products.py @@ -2,7 +2,8 @@ from __future__ import annotations -from typing import Optional +from typing import Dict, List, Union, Iterable, Optional +from datetime import datetime from typing_extensions import Literal import httpx @@ -21,8 +22,14 @@ from ..pagination import SyncCursorPage, AsyncCursorPage from .._base_client import AsyncPaginator, make_request_options from ..types.shared.product import Product +from ..types.shared.direction import Direction +from ..types.shared.custom_cta import CustomCta +from ..types.shared.visibility import Visibility from ..types.product_delete_response import ProductDeleteResponse +from ..types.shared.access_pass_type import AccessPassType from ..types.shared.product_list_item import ProductListItem +from ..types.shared.visibility_filter import VisibilityFilter +from ..types.shared.global_affiliate_status import GlobalAffiliateStatus __all__ = ["ProductsResource", "AsyncProductsResource"] @@ -52,23 +59,26 @@ def with_streaming_response(self) -> ProductsResourceWithStreamingResponse: def create( self, *, + company_id: str, title: str, collect_shipping_address: Optional[bool] | Omit = omit, - company_id: str | Omit = omit, - custom_cta: Optional[str] | Omit = omit, + custom_cta: Optional[CustomCta] | Omit = omit, custom_cta_url: Optional[str] | Omit = omit, custom_statement_descriptor: Optional[str] | Omit = omit, description: Optional[str] | Omit = omit, + experience_ids: Optional[SequenceNotStr[str]] | Omit = omit, global_affiliate_percentage: Optional[float] | Omit = omit, - global_affiliate_status: str | Omit = omit, + global_affiliate_status: Optional[GlobalAffiliateStatus] | Omit = omit, headline: Optional[str] | Omit = omit, member_affiliate_percentage: Optional[float] | Omit = omit, - member_affiliate_status: str | Omit = omit, - metadata: Optional[object] | Omit = omit, + member_affiliate_status: Optional[GlobalAffiliateStatus] | Omit = omit, + metadata: Optional[Dict[str, object]] | Omit = omit, + plan_options: Optional[product_create_params.PlanOptions] | Omit = omit, product_tax_code_id: Optional[str] | Omit = omit, redirect_purchase_url: Optional[str] | Omit = omit, route: Optional[str] | Omit = omit, - visibility: str | Omit = omit, + send_welcome_message: Optional[bool] | Omit = omit, + visibility: Optional[Visibility] | 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, @@ -76,43 +86,64 @@ def create( extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> Product: - """ - Creates a new product for a company. + """Create a new product for a company. + + The product serves as the top-level + container for plans and experiences. + + Required permissions: + + - `access_pass:create` + - `access_pass:basic:read` Args: + company_id: The unique identifier of the company to create this product for. + title: The display name of the product. Maximum 80 characters. - collect_shipping_address: Whether to collect a shipping address at checkout. + collect_shipping_address: Whether the checkout flow collects a shipping address from the customer. - company_id: The unique identifier of the company to create this product for. + custom_cta: The different types of custom CTAs that can be selected. - custom_cta: The call-to-action button label. + custom_cta_url: A URL that the call-to-action button links to instead of the default checkout + flow. - custom_cta_url: A URL the call-to-action button links to. + custom_statement_descriptor: A custom text label that appears on the customer's bank statement. Must be 5-22 + characters, contain at least one letter, and not contain <, >, \\,, ', or " + characters. - custom_statement_descriptor: Custom bank statement descriptor. Must start with WHOP\\**. + description: A written description of the product displayed on its product page. - description: A written description displayed on the product page. + experience_ids: The unique identifiers of experiences to connect to this product. - global_affiliate_percentage: The commission rate affiliates earn. + global_affiliate_percentage: The commission rate as a percentage that affiliates earn through the global + affiliate program. - global_affiliate_status: The enrollment status in the global affiliate program. + global_affiliate_status: The different statuses of the global affiliate program for a product. - headline: A short marketing headline for the product page. + headline: A short marketing headline displayed prominently on the product page. - member_affiliate_percentage: The commission rate members earn. + member_affiliate_percentage: The commission rate as a percentage that members earn through the member + affiliate program. - member_affiliate_status: The enrollment status in the member affiliate program. + member_affiliate_status: The different statuses of the global affiliate program for a product. - metadata: Custom key-value pairs to store on the product. + metadata: Custom key-value pairs to store on the product. Included in webhook payloads for + payment and membership events. Max 50 keys, 500 chars per key, 5000 chars per + value. - product_tax_code_id: The unique identifier of the tax classification code. + plan_options: Configuration for an automatically generated plan to attach to this product. - redirect_purchase_url: A URL to redirect the customer to after purchase. + product_tax_code_id: The unique identifier of the tax classification code to apply to this product. + + redirect_purchase_url: A URL to redirect the customer to after completing a purchase. route: The URL slug for the product's public link. - visibility: Whether the product is visible to customers. + send_welcome_message: Whether to send an automated welcome message via support chat when a user joins + this product. Defaults to true. + + visibility: Visibility of a resource extra_headers: Send extra headers @@ -126,22 +157,25 @@ def create( "/products", body=maybe_transform( { + "company_id": company_id, "title": title, "collect_shipping_address": collect_shipping_address, - "company_id": company_id, "custom_cta": custom_cta, "custom_cta_url": custom_cta_url, "custom_statement_descriptor": custom_statement_descriptor, "description": description, + "experience_ids": experience_ids, "global_affiliate_percentage": global_affiliate_percentage, "global_affiliate_status": global_affiliate_status, "headline": headline, "member_affiliate_percentage": member_affiliate_percentage, "member_affiliate_status": member_affiliate_status, "metadata": metadata, + "plan_options": plan_options, "product_tax_code_id": product_tax_code_id, "redirect_purchase_url": redirect_purchase_url, "route": route, + "send_welcome_message": send_welcome_message, "visibility": visibility, }, product_create_params.ProductCreateParams, @@ -163,10 +197,12 @@ def retrieve( extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> Product: - """Retrieves the details of an existing product. + """ + Retrieves the details of an existing product. + + Required permissions: - This endpoint is publicly - accessible. + - `access_pass:basic:read` Args: extra_headers: Send extra headers @@ -191,11 +227,25 @@ def update( self, id: str, *, + collect_shipping_address: Optional[bool] | Omit = omit, + custom_cta: Optional[CustomCta] | Omit = omit, + custom_cta_url: Optional[str] | Omit = omit, + custom_statement_descriptor: Optional[str] | Omit = omit, description: Optional[str] | Omit = omit, + gallery_images: Optional[Iterable[product_update_params.GalleryImage]] | Omit = omit, + global_affiliate_percentage: Optional[float] | Omit = omit, + global_affiliate_status: Optional[GlobalAffiliateStatus] | Omit = omit, headline: Optional[str] | Omit = omit, - metadata: Optional[object] | Omit = omit, - title: str | Omit = omit, - visibility: str | Omit = omit, + member_affiliate_percentage: Optional[float] | Omit = omit, + member_affiliate_status: Optional[GlobalAffiliateStatus] | Omit = omit, + metadata: Optional[Dict[str, object]] | Omit = omit, + product_tax_code_id: Optional[str] | Omit = omit, + redirect_purchase_url: Optional[str] | Omit = omit, + route: Optional[str] | Omit = omit, + send_welcome_message: Optional[bool] | Omit = omit, + store_page_config: Optional[product_update_params.StorePageConfig] | Omit = omit, + title: Optional[str] | Omit = omit, + visibility: Optional[Visibility] | 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, @@ -204,18 +254,59 @@ def update( timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> Product: """ - Updates an existing product. + Update a product's title, description, visibility, and other settings. + + Required permissions: + + - `access_pass:update` + - `access_pass:basic:read` Args: - description: A written description displayed on the product page. + collect_shipping_address: Whether the checkout flow collects a shipping address from the customer. + + custom_cta: The different types of custom CTAs that can be selected. + + custom_cta_url: A URL that the call-to-action button links to instead of the default checkout + flow. + + custom_statement_descriptor: A custom text label that appears on the customer's bank statement. Must be 5-22 + characters, contain at least one letter, and not contain <, >, \\,, ', or " + characters. + + description: A written description of the product displayed on its product page. + + gallery_images: The gallery images for the product. - headline: A short marketing headline for the product page. + global_affiliate_percentage: The commission rate as a percentage that affiliates earn through the global + affiliate program. - metadata: Custom key-value pairs to store on the product. + global_affiliate_status: The different statuses of the global affiliate program for a product. - title: The display name of the product. + headline: A short marketing headline displayed prominently on the product page. - visibility: Whether the product is visible to customers. + member_affiliate_percentage: The commission rate as a percentage that members earn through the member + affiliate program. + + member_affiliate_status: The different statuses of the global affiliate program for a product. + + metadata: Custom key-value pairs to store on the product. Included in webhook payloads for + payment and membership events. Max 50 keys, 500 chars per key, 5000 chars per + value. + + product_tax_code_id: The unique identifier of the tax classification code to apply to this product. + + redirect_purchase_url: A URL to redirect the customer to after completing a purchase. + + route: The URL slug for the product's public link. + + send_welcome_message: Whether to send an automated welcome message via support chat when a user joins + this product. + + store_page_config: Layout and display configuration for this product on the company's store page. + + title: The display name of the product. Maximum 80 characters. + + visibility: Visibility of a resource extra_headers: Send extra headers @@ -231,9 +322,23 @@ def update( path_template("/products/{id}", id=id), body=maybe_transform( { + "collect_shipping_address": collect_shipping_address, + "custom_cta": custom_cta, + "custom_cta_url": custom_cta_url, + "custom_statement_descriptor": custom_statement_descriptor, "description": description, + "gallery_images": gallery_images, + "global_affiliate_percentage": global_affiliate_percentage, + "global_affiliate_status": global_affiliate_status, "headline": headline, + "member_affiliate_percentage": member_affiliate_percentage, + "member_affiliate_status": member_affiliate_status, "metadata": metadata, + "product_tax_code_id": product_tax_code_id, + "redirect_purchase_url": redirect_purchase_url, + "route": route, + "send_welcome_message": send_welcome_message, + "store_page_config": store_page_config, "title": title, "visibility": visibility, }, @@ -249,14 +354,16 @@ def list( self, *, company_id: str, - access_pass_types: SequenceNotStr[str] | Omit = omit, - after: str | Omit = omit, - before: str | Omit = omit, - direction: Literal["asc", "desc"] | Omit = omit, - first: int | Omit = omit, - last: int | Omit = omit, - order: str | Omit = omit, - visibilities: SequenceNotStr[str] | Omit = omit, + 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, + direction: Optional[Direction] | Omit = omit, + first: Optional[int] | Omit = omit, + last: Optional[int] | Omit = omit, + order: Optional[Literal["active_memberships_count", "created_at", "usd_gmv", "usd_gmv_30_days"]] | Omit = omit, + product_types: Optional[List[AccessPassType]] | Omit = omit, + visibilities: Optional[List[VisibilityFilter]] | 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, @@ -265,24 +372,33 @@ def list( timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> SyncCursorPage[ProductListItem]: """ - Returns a paginated list of products belonging to a company. + Returns a paginated list of products belonging to a company, with optional + filtering by type, visibility, and creation date. + + Required permissions: + + - `access_pass:basic:read` Args: company_id: The unique identifier of the company to list products for. - access_pass_types: Filter to only products matching these types. + 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. - after: A cursor; returns products after this position. + created_after: Only return products created after this timestamp. - before: A cursor; returns products before this position. + created_before: Only return products created before this timestamp. - direction: The sort direction for results. Defaults to descending. + direction: The direction of the sort. - first: The number of products to return (default and max 100). + first: Returns the first _n_ elements from the list. - last: The number of products to return from the end of the range. + last: Returns the last _n_ elements from the list. - order: The field to sort results by. Defaults to created_at. + order: The ways a relation of AccessPasses can be ordered + + product_types: Filter to only products matching these type classifications. visibilities: Filter to only products matching these visibility states. @@ -305,13 +421,15 @@ def list( query=maybe_transform( { "company_id": company_id, - "access_pass_types": access_pass_types, "after": after, "before": before, + "created_after": created_after, + "created_before": created_before, "direction": direction, "first": first, "last": last, "order": order, + "product_types": product_types, "visibilities": visibilities, }, product_list_params.ProductListParams, @@ -331,10 +449,12 @@ def delete( extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> ProductDeleteResponse: - """Deletes a product. + """ + Permanently delete a product and remove it from the company's catalog. + + Required permissions: - Only products with no memberships, entries, reviews, or - invoices can be deleted. + - `access_pass:delete` Args: extra_headers: Send extra headers @@ -381,23 +501,26 @@ def with_streaming_response(self) -> AsyncProductsResourceWithStreamingResponse: async def create( self, *, + company_id: str, title: str, collect_shipping_address: Optional[bool] | Omit = omit, - company_id: str | Omit = omit, - custom_cta: Optional[str] | Omit = omit, + custom_cta: Optional[CustomCta] | Omit = omit, custom_cta_url: Optional[str] | Omit = omit, custom_statement_descriptor: Optional[str] | Omit = omit, description: Optional[str] | Omit = omit, + experience_ids: Optional[SequenceNotStr[str]] | Omit = omit, global_affiliate_percentage: Optional[float] | Omit = omit, - global_affiliate_status: str | Omit = omit, + global_affiliate_status: Optional[GlobalAffiliateStatus] | Omit = omit, headline: Optional[str] | Omit = omit, member_affiliate_percentage: Optional[float] | Omit = omit, - member_affiliate_status: str | Omit = omit, - metadata: Optional[object] | Omit = omit, + member_affiliate_status: Optional[GlobalAffiliateStatus] | Omit = omit, + metadata: Optional[Dict[str, object]] | Omit = omit, + plan_options: Optional[product_create_params.PlanOptions] | Omit = omit, product_tax_code_id: Optional[str] | Omit = omit, redirect_purchase_url: Optional[str] | Omit = omit, route: Optional[str] | Omit = omit, - visibility: str | Omit = omit, + send_welcome_message: Optional[bool] | Omit = omit, + visibility: Optional[Visibility] | 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, @@ -405,43 +528,64 @@ async def create( extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> Product: - """ - Creates a new product for a company. + """Create a new product for a company. + + The product serves as the top-level + container for plans and experiences. + + Required permissions: + + - `access_pass:create` + - `access_pass:basic:read` Args: + company_id: The unique identifier of the company to create this product for. + title: The display name of the product. Maximum 80 characters. - collect_shipping_address: Whether to collect a shipping address at checkout. + collect_shipping_address: Whether the checkout flow collects a shipping address from the customer. - company_id: The unique identifier of the company to create this product for. + custom_cta: The different types of custom CTAs that can be selected. - custom_cta: The call-to-action button label. + custom_cta_url: A URL that the call-to-action button links to instead of the default checkout + flow. - custom_cta_url: A URL the call-to-action button links to. + custom_statement_descriptor: A custom text label that appears on the customer's bank statement. Must be 5-22 + characters, contain at least one letter, and not contain <, >, \\,, ', or " + characters. - custom_statement_descriptor: Custom bank statement descriptor. Must start with WHOP\\**. + description: A written description of the product displayed on its product page. - description: A written description displayed on the product page. + experience_ids: The unique identifiers of experiences to connect to this product. - global_affiliate_percentage: The commission rate affiliates earn. + global_affiliate_percentage: The commission rate as a percentage that affiliates earn through the global + affiliate program. - global_affiliate_status: The enrollment status in the global affiliate program. + global_affiliate_status: The different statuses of the global affiliate program for a product. - headline: A short marketing headline for the product page. + headline: A short marketing headline displayed prominently on the product page. - member_affiliate_percentage: The commission rate members earn. + member_affiliate_percentage: The commission rate as a percentage that members earn through the member + affiliate program. - member_affiliate_status: The enrollment status in the member affiliate program. + member_affiliate_status: The different statuses of the global affiliate program for a product. - metadata: Custom key-value pairs to store on the product. + metadata: Custom key-value pairs to store on the product. Included in webhook payloads for + payment and membership events. Max 50 keys, 500 chars per key, 5000 chars per + value. - product_tax_code_id: The unique identifier of the tax classification code. + plan_options: Configuration for an automatically generated plan to attach to this product. - redirect_purchase_url: A URL to redirect the customer to after purchase. + product_tax_code_id: The unique identifier of the tax classification code to apply to this product. + + redirect_purchase_url: A URL to redirect the customer to after completing a purchase. route: The URL slug for the product's public link. - visibility: Whether the product is visible to customers. + send_welcome_message: Whether to send an automated welcome message via support chat when a user joins + this product. Defaults to true. + + visibility: Visibility of a resource extra_headers: Send extra headers @@ -455,22 +599,25 @@ async def create( "/products", body=await async_maybe_transform( { + "company_id": company_id, "title": title, "collect_shipping_address": collect_shipping_address, - "company_id": company_id, "custom_cta": custom_cta, "custom_cta_url": custom_cta_url, "custom_statement_descriptor": custom_statement_descriptor, "description": description, + "experience_ids": experience_ids, "global_affiliate_percentage": global_affiliate_percentage, "global_affiliate_status": global_affiliate_status, "headline": headline, "member_affiliate_percentage": member_affiliate_percentage, "member_affiliate_status": member_affiliate_status, "metadata": metadata, + "plan_options": plan_options, "product_tax_code_id": product_tax_code_id, "redirect_purchase_url": redirect_purchase_url, "route": route, + "send_welcome_message": send_welcome_message, "visibility": visibility, }, product_create_params.ProductCreateParams, @@ -492,10 +639,12 @@ async def retrieve( extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> Product: - """Retrieves the details of an existing product. + """ + Retrieves the details of an existing product. + + Required permissions: - This endpoint is publicly - accessible. + - `access_pass:basic:read` Args: extra_headers: Send extra headers @@ -520,11 +669,25 @@ async def update( self, id: str, *, + collect_shipping_address: Optional[bool] | Omit = omit, + custom_cta: Optional[CustomCta] | Omit = omit, + custom_cta_url: Optional[str] | Omit = omit, + custom_statement_descriptor: Optional[str] | Omit = omit, description: Optional[str] | Omit = omit, + gallery_images: Optional[Iterable[product_update_params.GalleryImage]] | Omit = omit, + global_affiliate_percentage: Optional[float] | Omit = omit, + global_affiliate_status: Optional[GlobalAffiliateStatus] | Omit = omit, headline: Optional[str] | Omit = omit, - metadata: Optional[object] | Omit = omit, - title: str | Omit = omit, - visibility: str | Omit = omit, + member_affiliate_percentage: Optional[float] | Omit = omit, + member_affiliate_status: Optional[GlobalAffiliateStatus] | Omit = omit, + metadata: Optional[Dict[str, object]] | Omit = omit, + product_tax_code_id: Optional[str] | Omit = omit, + redirect_purchase_url: Optional[str] | Omit = omit, + route: Optional[str] | Omit = omit, + send_welcome_message: Optional[bool] | Omit = omit, + store_page_config: Optional[product_update_params.StorePageConfig] | Omit = omit, + title: Optional[str] | Omit = omit, + visibility: Optional[Visibility] | 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, @@ -533,18 +696,59 @@ async def update( timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> Product: """ - Updates an existing product. + Update a product's title, description, visibility, and other settings. + + Required permissions: + + - `access_pass:update` + - `access_pass:basic:read` Args: - description: A written description displayed on the product page. + collect_shipping_address: Whether the checkout flow collects a shipping address from the customer. + + custom_cta: The different types of custom CTAs that can be selected. + + custom_cta_url: A URL that the call-to-action button links to instead of the default checkout + flow. + + custom_statement_descriptor: A custom text label that appears on the customer's bank statement. Must be 5-22 + characters, contain at least one letter, and not contain <, >, \\,, ', or " + characters. + + description: A written description of the product displayed on its product page. + + gallery_images: The gallery images for the product. - headline: A short marketing headline for the product page. + global_affiliate_percentage: The commission rate as a percentage that affiliates earn through the global + affiliate program. - metadata: Custom key-value pairs to store on the product. + global_affiliate_status: The different statuses of the global affiliate program for a product. - title: The display name of the product. + headline: A short marketing headline displayed prominently on the product page. - visibility: Whether the product is visible to customers. + member_affiliate_percentage: The commission rate as a percentage that members earn through the member + affiliate program. + + member_affiliate_status: The different statuses of the global affiliate program for a product. + + metadata: Custom key-value pairs to store on the product. Included in webhook payloads for + payment and membership events. Max 50 keys, 500 chars per key, 5000 chars per + value. + + product_tax_code_id: The unique identifier of the tax classification code to apply to this product. + + redirect_purchase_url: A URL to redirect the customer to after completing a purchase. + + route: The URL slug for the product's public link. + + send_welcome_message: Whether to send an automated welcome message via support chat when a user joins + this product. + + store_page_config: Layout and display configuration for this product on the company's store page. + + title: The display name of the product. Maximum 80 characters. + + visibility: Visibility of a resource extra_headers: Send extra headers @@ -560,9 +764,23 @@ async def update( path_template("/products/{id}", id=id), body=await async_maybe_transform( { + "collect_shipping_address": collect_shipping_address, + "custom_cta": custom_cta, + "custom_cta_url": custom_cta_url, + "custom_statement_descriptor": custom_statement_descriptor, "description": description, + "gallery_images": gallery_images, + "global_affiliate_percentage": global_affiliate_percentage, + "global_affiliate_status": global_affiliate_status, "headline": headline, + "member_affiliate_percentage": member_affiliate_percentage, + "member_affiliate_status": member_affiliate_status, "metadata": metadata, + "product_tax_code_id": product_tax_code_id, + "redirect_purchase_url": redirect_purchase_url, + "route": route, + "send_welcome_message": send_welcome_message, + "store_page_config": store_page_config, "title": title, "visibility": visibility, }, @@ -578,14 +796,16 @@ def list( self, *, company_id: str, - access_pass_types: SequenceNotStr[str] | Omit = omit, - after: str | Omit = omit, - before: str | Omit = omit, - direction: Literal["asc", "desc"] | Omit = omit, - first: int | Omit = omit, - last: int | Omit = omit, - order: str | Omit = omit, - visibilities: SequenceNotStr[str] | Omit = omit, + 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, + direction: Optional[Direction] | Omit = omit, + first: Optional[int] | Omit = omit, + last: Optional[int] | Omit = omit, + order: Optional[Literal["active_memberships_count", "created_at", "usd_gmv", "usd_gmv_30_days"]] | Omit = omit, + product_types: Optional[List[AccessPassType]] | Omit = omit, + visibilities: Optional[List[VisibilityFilter]] | 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, @@ -594,24 +814,33 @@ def list( timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> AsyncPaginator[ProductListItem, AsyncCursorPage[ProductListItem]]: """ - Returns a paginated list of products belonging to a company. + Returns a paginated list of products belonging to a company, with optional + filtering by type, visibility, and creation date. + + Required permissions: + + - `access_pass:basic:read` Args: company_id: The unique identifier of the company to list products for. - access_pass_types: Filter to only products matching these types. + 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. - after: A cursor; returns products after this position. + created_after: Only return products created after this timestamp. - before: A cursor; returns products before this position. + created_before: Only return products created before this timestamp. - direction: The sort direction for results. Defaults to descending. + direction: The direction of the sort. - first: The number of products to return (default and max 100). + first: Returns the first _n_ elements from the list. - last: The number of products to return from the end of the range. + last: Returns the last _n_ elements from the list. - order: The field to sort results by. Defaults to created_at. + order: The ways a relation of AccessPasses can be ordered + + product_types: Filter to only products matching these type classifications. visibilities: Filter to only products matching these visibility states. @@ -634,13 +863,15 @@ def list( query=maybe_transform( { "company_id": company_id, - "access_pass_types": access_pass_types, "after": after, "before": before, + "created_after": created_after, + "created_before": created_before, "direction": direction, "first": first, "last": last, "order": order, + "product_types": product_types, "visibilities": visibilities, }, product_list_params.ProductListParams, @@ -660,10 +891,12 @@ async def delete( extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> ProductDeleteResponse: - """Deletes a product. + """ + Permanently delete a product and remove it from the company's catalog. + + Required permissions: - Only products with no memberships, entries, reviews, or - invoices can be deleted. + - `access_pass:delete` Args: extra_headers: Send extra headers diff --git a/src/whop_sdk/types/product_create_params.py b/src/whop_sdk/types/product_create_params.py index 29597ede..103fbc3d 100644 --- a/src/whop_sdk/types/product_create_params.py +++ b/src/whop_sdk/types/product_create_params.py @@ -2,60 +2,151 @@ from __future__ import annotations -from typing import Optional -from typing_extensions import Required, TypedDict +from typing import Dict, Iterable, Optional +from typing_extensions import Literal, Required, TypedDict -__all__ = ["ProductCreateParams"] +from .._types import SequenceNotStr +from .shared.currency import Currency +from .shared.plan_type import PlanType +from .shared.custom_cta import CustomCta +from .shared.visibility import Visibility +from .shared.release_method import ReleaseMethod +from .shared.global_affiliate_status import GlobalAffiliateStatus + +__all__ = ["ProductCreateParams", "PlanOptions", "PlanOptionsCustomField"] class ProductCreateParams(TypedDict, total=False): + company_id: Required[str] + """The unique identifier of the company to create this product for.""" + title: Required[str] """The display name of the product. Maximum 80 characters.""" collect_shipping_address: Optional[bool] - """Whether to collect a shipping address at checkout.""" - - company_id: str - """The unique identifier of the company to create this product for.""" + """Whether the checkout flow collects a shipping address from the customer.""" - custom_cta: Optional[str] - """The call-to-action button label.""" + custom_cta: Optional[CustomCta] + """The different types of custom CTAs that can be selected.""" custom_cta_url: Optional[str] - """A URL the call-to-action button links to.""" + """ + A URL that the call-to-action button links to instead of the default checkout + flow. + """ custom_statement_descriptor: Optional[str] - """Custom bank statement descriptor. Must start with WHOP\\**.""" + """A custom text label that appears on the customer's bank statement. + + Must be 5-22 characters, contain at least one letter, and not contain <, >, \\,, + ', or " characters. + """ description: Optional[str] - """A written description displayed on the product page.""" + """A written description of the product displayed on its product page.""" + + experience_ids: Optional[SequenceNotStr[str]] + """The unique identifiers of experiences to connect to this product.""" global_affiliate_percentage: Optional[float] - """The commission rate affiliates earn.""" + """ + The commission rate as a percentage that affiliates earn through the global + affiliate program. + """ - global_affiliate_status: str - """The enrollment status in the global affiliate program.""" + global_affiliate_status: Optional[GlobalAffiliateStatus] + """The different statuses of the global affiliate program for a product.""" headline: Optional[str] - """A short marketing headline for the product page.""" + """A short marketing headline displayed prominently on the product page.""" member_affiliate_percentage: Optional[float] - """The commission rate members earn.""" + """ + The commission rate as a percentage that members earn through the member + affiliate program. + """ + + member_affiliate_status: Optional[GlobalAffiliateStatus] + """The different statuses of the global affiliate program for a product.""" - member_affiliate_status: str - """The enrollment status in the member affiliate program.""" + metadata: Optional[Dict[str, object]] + """Custom key-value pairs to store on the product. - metadata: Optional[object] - """Custom key-value pairs to store on the product.""" + Included in webhook payloads for payment and membership events. Max 50 keys, 500 + chars per key, 5000 chars per value. + """ + + plan_options: Optional[PlanOptions] + """Configuration for an automatically generated plan to attach to this product.""" product_tax_code_id: Optional[str] - """The unique identifier of the tax classification code.""" + """The unique identifier of the tax classification code to apply to this product.""" redirect_purchase_url: Optional[str] - """A URL to redirect the customer to after purchase.""" + """A URL to redirect the customer to after completing a purchase.""" route: Optional[str] """The URL slug for the product's public link.""" - visibility: str - """Whether the product is visible to customers.""" + send_welcome_message: Optional[bool] + """ + Whether to send an automated welcome message via support chat when a user joins + this product. Defaults to true. + """ + + visibility: Optional[Visibility] + """Visibility of a resource""" + + +class PlanOptionsCustomField(TypedDict, total=False): + field_type: Required[Literal["text"]] + """The type of the custom field.""" + + name: Required[str] + """The name of the custom field.""" + + id: Optional[str] + """The ID of the custom field (if being updated)""" + + order: Optional[int] + """The order of the field.""" + + placeholder: Optional[str] + """The placeholder value of the field.""" + + required: Optional[bool] + """Whether or not the field is required.""" + + +class PlanOptions(TypedDict, total=False): + """Configuration for an automatically generated plan to attach to this product.""" + + base_currency: Optional[Currency] + """The available currencies on the platform""" + + billing_period: Optional[int] + """The interval at which the plan charges (renewal plans).""" + + custom_fields: Optional[Iterable[PlanOptionsCustomField]] + """An array of custom field objects.""" + + initial_price: Optional[float] + """An additional amount charged upon first purchase. + + Provided as a number in the specified currency. Eg: 10.43 for $10.43 USD. + """ + + plan_type: Optional[PlanType] + """The type of plan that can be attached to a product""" + + release_method: Optional[ReleaseMethod] + """The methods of how a plan can be released.""" + + renewal_price: Optional[float] + """The amount the customer is charged every billing period. + + Provided as a number in the specified currency. Eg: 10.43 for $10.43 USD. + """ + + visibility: Optional[Visibility] + """Visibility of a resource""" diff --git a/src/whop_sdk/types/product_list_params.py b/src/whop_sdk/types/product_list_params.py index 5b666c38..47862a61 100644 --- a/src/whop_sdk/types/product_list_params.py +++ b/src/whop_sdk/types/product_list_params.py @@ -2,9 +2,14 @@ from __future__ import annotations -from typing_extensions import Literal, Required, TypedDict +from typing import List, Union, Optional +from datetime import datetime +from typing_extensions import Literal, Required, Annotated, TypedDict -from .._types import SequenceNotStr +from .._utils import PropertyInfo +from .shared.direction import Direction +from .shared.access_pass_type import AccessPassType +from .shared.visibility_filter import VisibilityFilter __all__ = ["ProductListParams"] @@ -13,26 +18,32 @@ class ProductListParams(TypedDict, total=False): company_id: Required[str] """The unique identifier of the company to list products for.""" - access_pass_types: SequenceNotStr[str] - """Filter to only products matching these types.""" + after: Optional[str] + """Returns the elements in the list that come after the specified cursor.""" - after: str - """A cursor; returns products after this position.""" + before: Optional[str] + """Returns the elements in the list that come before the specified cursor.""" - before: str - """A cursor; returns products before this position.""" + created_after: Annotated[Union[str, datetime, None], PropertyInfo(format="iso8601")] + """Only return products created after this timestamp.""" - direction: Literal["asc", "desc"] - """The sort direction for results. Defaults to descending.""" + created_before: Annotated[Union[str, datetime, None], PropertyInfo(format="iso8601")] + """Only return products created before this timestamp.""" - first: int - """The number of products to return (default and max 100).""" + direction: Optional[Direction] + """The direction of the sort.""" - last: int - """The number of products to return from the end of the range.""" + first: Optional[int] + """Returns the first _n_ elements from the list.""" - order: str - """The field to sort results by. Defaults to created_at.""" + last: Optional[int] + """Returns the last _n_ elements from the list.""" - visibilities: SequenceNotStr[str] + order: Optional[Literal["active_memberships_count", "created_at", "usd_gmv", "usd_gmv_30_days"]] + """The ways a relation of AccessPasses can be ordered""" + + product_types: Optional[List[AccessPassType]] + """Filter to only products matching these type classifications.""" + + visibilities: Optional[List[VisibilityFilter]] """Filter to only products matching these visibility states.""" diff --git a/src/whop_sdk/types/product_update_params.py b/src/whop_sdk/types/product_update_params.py index d9d48096..64a7c8c1 100644 --- a/src/whop_sdk/types/product_update_params.py +++ b/src/whop_sdk/types/product_update_params.py @@ -2,24 +2,107 @@ from __future__ import annotations -from typing import Optional -from typing_extensions import TypedDict +from typing import Dict, Iterable, Optional +from typing_extensions import Required, TypedDict -__all__ = ["ProductUpdateParams"] +from .shared.custom_cta import CustomCta +from .shared.visibility import Visibility +from .shared.global_affiliate_status import GlobalAffiliateStatus + +__all__ = ["ProductUpdateParams", "GalleryImage", "StorePageConfig"] class ProductUpdateParams(TypedDict, total=False): + collect_shipping_address: Optional[bool] + """Whether the checkout flow collects a shipping address from the customer.""" + + custom_cta: Optional[CustomCta] + """The different types of custom CTAs that can be selected.""" + + custom_cta_url: Optional[str] + """ + A URL that the call-to-action button links to instead of the default checkout + flow. + """ + + custom_statement_descriptor: Optional[str] + """A custom text label that appears on the customer's bank statement. + + Must be 5-22 characters, contain at least one letter, and not contain <, >, \\,, + ', or " characters. + """ + description: Optional[str] - """A written description displayed on the product page.""" + """A written description of the product displayed on its product page.""" + + gallery_images: Optional[Iterable[GalleryImage]] + """The gallery images for the product.""" + + global_affiliate_percentage: Optional[float] + """ + The commission rate as a percentage that affiliates earn through the global + affiliate program. + """ + + global_affiliate_status: Optional[GlobalAffiliateStatus] + """The different statuses of the global affiliate program for a product.""" headline: Optional[str] - """A short marketing headline for the product page.""" + """A short marketing headline displayed prominently on the product page.""" + + member_affiliate_percentage: Optional[float] + """ + The commission rate as a percentage that members earn through the member + affiliate program. + """ + + member_affiliate_status: Optional[GlobalAffiliateStatus] + """The different statuses of the global affiliate program for a product.""" + + metadata: Optional[Dict[str, object]] + """Custom key-value pairs to store on the product. + + Included in webhook payloads for payment and membership events. Max 50 keys, 500 + chars per key, 5000 chars per value. + """ + + product_tax_code_id: Optional[str] + """The unique identifier of the tax classification code to apply to this product.""" + + redirect_purchase_url: Optional[str] + """A URL to redirect the customer to after completing a purchase.""" + + route: Optional[str] + """The URL slug for the product's public link.""" + + send_welcome_message: Optional[bool] + """ + Whether to send an automated welcome message via support chat when a user joins + this product. + """ + + store_page_config: Optional[StorePageConfig] + """Layout and display configuration for this product on the company's store page.""" + + title: Optional[str] + """The display name of the product. Maximum 80 characters.""" + + visibility: Optional[Visibility] + """Visibility of a resource""" + + +class GalleryImage(TypedDict, total=False): + """Input for an attachment""" + + id: Required[str] + """The ID of an existing file object.""" + - metadata: Optional[object] - """Custom key-value pairs to store on the product.""" +class StorePageConfig(TypedDict, total=False): + """Layout and display configuration for this product on the company's store page.""" - title: str - """The display name of the product.""" + custom_cta: Optional[str] + """Custom call-to-action text for the product's store page.""" - visibility: str - """Whether the product is visible to customers.""" + show_price: Optional[bool] + """Whether or not to show the price on the product's store page.""" diff --git a/src/whop_sdk/types/shared_params/__init__.py b/src/whop_sdk/types/shared_params/__init__.py index 4ae01cc7..eb838c16 100644 --- a/src/whop_sdk/types/shared_params/__init__.py +++ b/src/whop_sdk/types/shared_params/__init__.py @@ -4,6 +4,7 @@ from .tax_type import TaxType as TaxType from .direction import Direction as Direction from .plan_type import PlanType as PlanType +from .custom_cta import CustomCta as CustomCta from .promo_type import PromoType as PromoType from .visibility import Visibility as Visibility from .access_level import AccessLevel as AccessLevel @@ -16,8 +17,10 @@ from .receipt_status import ReceiptStatus as ReceiptStatus from .release_method import ReleaseMethod as ReleaseMethod from .member_statuses import MemberStatuses as MemberStatuses +from .access_pass_type import AccessPassType as AccessPassType from .collection_method import CollectionMethod as CollectionMethod from .membership_status import MembershipStatus as MembershipStatus +from .visibility_filter import VisibilityFilter as VisibilityFilter from .app_build_statuses import AppBuildStatuses as AppBuildStatuses from .who_can_post_types import WhoCanPostTypes as WhoCanPostTypes from .app_build_platforms import AppBuildPlatforms as AppBuildPlatforms diff --git a/src/whop_sdk/types/shared_params/access_pass_type.py b/src/whop_sdk/types/shared_params/access_pass_type.py new file mode 100644 index 00000000..3f760c67 --- /dev/null +++ b/src/whop_sdk/types/shared_params/access_pass_type.py @@ -0,0 +1,9 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Literal, TypeAlias + +__all__ = ["AccessPassType"] + +AccessPassType: TypeAlias = Literal["regular", "app", "experience_upsell", "api_only"] diff --git a/src/whop_sdk/types/shared_params/custom_cta.py b/src/whop_sdk/types/shared_params/custom_cta.py new file mode 100644 index 00000000..0c2f5d19 --- /dev/null +++ b/src/whop_sdk/types/shared_params/custom_cta.py @@ -0,0 +1,23 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Literal, TypeAlias + +__all__ = ["CustomCta"] + +CustomCta: TypeAlias = Literal[ + "get_access", + "join", + "order_now", + "shop_now", + "call_now", + "donate_now", + "contact_us", + "sign_up", + "subscribe", + "purchase", + "get_offer", + "apply_now", + "complete_order", +] diff --git a/src/whop_sdk/types/shared_params/visibility_filter.py b/src/whop_sdk/types/shared_params/visibility_filter.py new file mode 100644 index 00000000..d01e6cb1 --- /dev/null +++ b/src/whop_sdk/types/shared_params/visibility_filter.py @@ -0,0 +1,11 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Literal, TypeAlias + +__all__ = ["VisibilityFilter"] + +VisibilityFilter: TypeAlias = Literal[ + "visible", "hidden", "archived", "quick_link", "all", "not_quick_link", "not_archived" +] diff --git a/tests/api_resources/test_products.py b/tests/api_resources/test_products.py index 10201f78..b9a1c329 100644 --- a/tests/api_resources/test_products.py +++ b/tests/api_resources/test_products.py @@ -9,7 +9,10 @@ from whop_sdk import Whop, AsyncWhop from tests.utils import assert_matches_type -from whop_sdk.types import ProductDeleteResponse +from whop_sdk.types import ( + ProductDeleteResponse, +) +from whop_sdk._utils import parse_datetime from whop_sdk.pagination import SyncCursorPage, AsyncCursorPage from whop_sdk.types.shared import Product, ProductListItem @@ -23,6 +26,7 @@ class TestProducts: @parametrize def test_method_create(self, client: Whop) -> None: product = client.products.create( + company_id="biz_xxxxxxxxxxxxxx", title="title", ) assert_matches_type(Product, product, path=["response"]) @@ -31,23 +35,44 @@ def test_method_create(self, client: Whop) -> None: @parametrize def test_method_create_with_all_params(self, client: Whop) -> None: product = client.products.create( + company_id="biz_xxxxxxxxxxxxxx", title="title", collect_shipping_address=True, - company_id="company_id", - custom_cta="custom_cta", + custom_cta="get_access", custom_cta_url="custom_cta_url", custom_statement_descriptor="custom_statement_descriptor", description="description", - global_affiliate_percentage=0, - global_affiliate_status="global_affiliate_status", + experience_ids=["string"], + global_affiliate_percentage=6.9, + global_affiliate_status="enabled", headline="headline", - member_affiliate_percentage=0, - member_affiliate_status="member_affiliate_status", - metadata={}, - product_tax_code_id="product_tax_code_id", + member_affiliate_percentage=6.9, + member_affiliate_status="enabled", + metadata={"foo": "bar"}, + plan_options={ + "base_currency": "usd", + "billing_period": 42, + "custom_fields": [ + { + "field_type": "text", + "name": "name", + "id": "id", + "order": 42, + "placeholder": "placeholder", + "required": True, + } + ], + "initial_price": 6.9, + "plan_type": "renewal", + "release_method": "buy_now", + "renewal_price": 6.9, + "visibility": "visible", + }, + product_tax_code_id="ptc_xxxxxxxxxxxxxx", redirect_purchase_url="redirect_purchase_url", route="route", - visibility="visibility", + send_welcome_message=True, + visibility="visible", ) assert_matches_type(Product, product, path=["response"]) @@ -55,6 +80,7 @@ def test_method_create_with_all_params(self, client: Whop) -> None: @parametrize def test_raw_response_create(self, client: Whop) -> None: response = client.products.with_raw_response.create( + company_id="biz_xxxxxxxxxxxxxx", title="title", ) @@ -67,6 +93,7 @@ def test_raw_response_create(self, client: Whop) -> None: @parametrize def test_streaming_response_create(self, client: Whop) -> None: with client.products.with_streaming_response.create( + company_id="biz_xxxxxxxxxxxxxx", title="title", ) as response: assert not response.is_closed @@ -81,7 +108,7 @@ def test_streaming_response_create(self, client: Whop) -> None: @parametrize def test_method_retrieve(self, client: Whop) -> None: product = client.products.retrieve( - "id", + "prod_xxxxxxxxxxxxx", ) assert_matches_type(Product, product, path=["response"]) @@ -89,7 +116,7 @@ def test_method_retrieve(self, client: Whop) -> None: @parametrize def test_raw_response_retrieve(self, client: Whop) -> None: response = client.products.with_raw_response.retrieve( - "id", + "prod_xxxxxxxxxxxxx", ) assert response.is_closed is True @@ -101,7 +128,7 @@ def test_raw_response_retrieve(self, client: Whop) -> None: @parametrize def test_streaming_response_retrieve(self, client: Whop) -> None: with client.products.with_streaming_response.retrieve( - "id", + "prod_xxxxxxxxxxxxx", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -123,7 +150,7 @@ def test_path_params_retrieve(self, client: Whop) -> None: @parametrize def test_method_update(self, client: Whop) -> None: product = client.products.update( - id="id", + id="prod_xxxxxxxxxxxxx", ) assert_matches_type(Product, product, path=["response"]) @@ -131,12 +158,29 @@ def test_method_update(self, client: Whop) -> None: @parametrize def test_method_update_with_all_params(self, client: Whop) -> None: product = client.products.update( - id="id", + id="prod_xxxxxxxxxxxxx", + collect_shipping_address=True, + custom_cta="get_access", + custom_cta_url="custom_cta_url", + custom_statement_descriptor="custom_statement_descriptor", description="description", + gallery_images=[{"id": "id"}], + global_affiliate_percentage=6.9, + global_affiliate_status="enabled", headline="headline", - metadata={}, + member_affiliate_percentage=6.9, + member_affiliate_status="enabled", + metadata={"foo": "bar"}, + product_tax_code_id="ptc_xxxxxxxxxxxxxx", + redirect_purchase_url="redirect_purchase_url", + route="route", + send_welcome_message=True, + store_page_config={ + "custom_cta": "custom_cta", + "show_price": True, + }, title="title", - visibility="visibility", + visibility="visible", ) assert_matches_type(Product, product, path=["response"]) @@ -144,7 +188,7 @@ def test_method_update_with_all_params(self, client: Whop) -> None: @parametrize def test_raw_response_update(self, client: Whop) -> None: response = client.products.with_raw_response.update( - id="id", + id="prod_xxxxxxxxxxxxx", ) assert response.is_closed is True @@ -156,7 +200,7 @@ def test_raw_response_update(self, client: Whop) -> None: @parametrize def test_streaming_response_update(self, client: Whop) -> None: with client.products.with_streaming_response.update( - id="id", + id="prod_xxxxxxxxxxxxx", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -178,7 +222,7 @@ def test_path_params_update(self, client: Whop) -> None: @parametrize def test_method_list(self, client: Whop) -> None: product = client.products.list( - company_id="company_id", + company_id="biz_xxxxxxxxxxxxxx", ) assert_matches_type(SyncCursorPage[ProductListItem], product, path=["response"]) @@ -186,15 +230,17 @@ def test_method_list(self, client: Whop) -> None: @parametrize def test_method_list_with_all_params(self, client: Whop) -> None: product = client.products.list( - company_id="company_id", - access_pass_types=["string"], + 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"), direction="asc", - first=0, - last=0, - order="order", - visibilities=["string"], + first=42, + last=42, + order="active_memberships_count", + product_types=["regular"], + visibilities=["visible"], ) assert_matches_type(SyncCursorPage[ProductListItem], product, path=["response"]) @@ -202,7 +248,7 @@ def test_method_list_with_all_params(self, client: Whop) -> None: @parametrize def test_raw_response_list(self, client: Whop) -> None: response = client.products.with_raw_response.list( - company_id="company_id", + company_id="biz_xxxxxxxxxxxxxx", ) assert response.is_closed is True @@ -214,7 +260,7 @@ def test_raw_response_list(self, client: Whop) -> None: @parametrize def test_streaming_response_list(self, client: Whop) -> None: with client.products.with_streaming_response.list( - company_id="company_id", + company_id="biz_xxxxxxxxxxxxxx", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -228,7 +274,7 @@ def test_streaming_response_list(self, client: Whop) -> None: @parametrize def test_method_delete(self, client: Whop) -> None: product = client.products.delete( - "id", + "prod_xxxxxxxxxxxxx", ) assert_matches_type(ProductDeleteResponse, product, path=["response"]) @@ -236,7 +282,7 @@ def test_method_delete(self, client: Whop) -> None: @parametrize def test_raw_response_delete(self, client: Whop) -> None: response = client.products.with_raw_response.delete( - "id", + "prod_xxxxxxxxxxxxx", ) assert response.is_closed is True @@ -248,7 +294,7 @@ def test_raw_response_delete(self, client: Whop) -> None: @parametrize def test_streaming_response_delete(self, client: Whop) -> None: with client.products.with_streaming_response.delete( - "id", + "prod_xxxxxxxxxxxxx", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -276,6 +322,7 @@ class TestAsyncProducts: @parametrize async def test_method_create(self, async_client: AsyncWhop) -> None: product = await async_client.products.create( + company_id="biz_xxxxxxxxxxxxxx", title="title", ) assert_matches_type(Product, product, path=["response"]) @@ -284,23 +331,44 @@ async def test_method_create(self, async_client: AsyncWhop) -> None: @parametrize async def test_method_create_with_all_params(self, async_client: AsyncWhop) -> None: product = await async_client.products.create( + company_id="biz_xxxxxxxxxxxxxx", title="title", collect_shipping_address=True, - company_id="company_id", - custom_cta="custom_cta", + custom_cta="get_access", custom_cta_url="custom_cta_url", custom_statement_descriptor="custom_statement_descriptor", description="description", - global_affiliate_percentage=0, - global_affiliate_status="global_affiliate_status", + experience_ids=["string"], + global_affiliate_percentage=6.9, + global_affiliate_status="enabled", headline="headline", - member_affiliate_percentage=0, - member_affiliate_status="member_affiliate_status", - metadata={}, - product_tax_code_id="product_tax_code_id", + member_affiliate_percentage=6.9, + member_affiliate_status="enabled", + metadata={"foo": "bar"}, + plan_options={ + "base_currency": "usd", + "billing_period": 42, + "custom_fields": [ + { + "field_type": "text", + "name": "name", + "id": "id", + "order": 42, + "placeholder": "placeholder", + "required": True, + } + ], + "initial_price": 6.9, + "plan_type": "renewal", + "release_method": "buy_now", + "renewal_price": 6.9, + "visibility": "visible", + }, + product_tax_code_id="ptc_xxxxxxxxxxxxxx", redirect_purchase_url="redirect_purchase_url", route="route", - visibility="visibility", + send_welcome_message=True, + visibility="visible", ) assert_matches_type(Product, product, path=["response"]) @@ -308,6 +376,7 @@ async def test_method_create_with_all_params(self, async_client: AsyncWhop) -> N @parametrize async def test_raw_response_create(self, async_client: AsyncWhop) -> None: response = await async_client.products.with_raw_response.create( + company_id="biz_xxxxxxxxxxxxxx", title="title", ) @@ -320,6 +389,7 @@ async def test_raw_response_create(self, async_client: AsyncWhop) -> None: @parametrize async def test_streaming_response_create(self, async_client: AsyncWhop) -> None: async with async_client.products.with_streaming_response.create( + company_id="biz_xxxxxxxxxxxxxx", title="title", ) as response: assert not response.is_closed @@ -334,7 +404,7 @@ async def test_streaming_response_create(self, async_client: AsyncWhop) -> None: @parametrize async def test_method_retrieve(self, async_client: AsyncWhop) -> None: product = await async_client.products.retrieve( - "id", + "prod_xxxxxxxxxxxxx", ) assert_matches_type(Product, product, path=["response"]) @@ -342,7 +412,7 @@ async def test_method_retrieve(self, async_client: AsyncWhop) -> None: @parametrize async def test_raw_response_retrieve(self, async_client: AsyncWhop) -> None: response = await async_client.products.with_raw_response.retrieve( - "id", + "prod_xxxxxxxxxxxxx", ) assert response.is_closed is True @@ -354,7 +424,7 @@ async def test_raw_response_retrieve(self, async_client: AsyncWhop) -> None: @parametrize async def test_streaming_response_retrieve(self, async_client: AsyncWhop) -> None: async with async_client.products.with_streaming_response.retrieve( - "id", + "prod_xxxxxxxxxxxxx", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -376,7 +446,7 @@ async def test_path_params_retrieve(self, async_client: AsyncWhop) -> None: @parametrize async def test_method_update(self, async_client: AsyncWhop) -> None: product = await async_client.products.update( - id="id", + id="prod_xxxxxxxxxxxxx", ) assert_matches_type(Product, product, path=["response"]) @@ -384,12 +454,29 @@ async def test_method_update(self, async_client: AsyncWhop) -> None: @parametrize async def test_method_update_with_all_params(self, async_client: AsyncWhop) -> None: product = await async_client.products.update( - id="id", + id="prod_xxxxxxxxxxxxx", + collect_shipping_address=True, + custom_cta="get_access", + custom_cta_url="custom_cta_url", + custom_statement_descriptor="custom_statement_descriptor", description="description", + gallery_images=[{"id": "id"}], + global_affiliate_percentage=6.9, + global_affiliate_status="enabled", headline="headline", - metadata={}, + member_affiliate_percentage=6.9, + member_affiliate_status="enabled", + metadata={"foo": "bar"}, + product_tax_code_id="ptc_xxxxxxxxxxxxxx", + redirect_purchase_url="redirect_purchase_url", + route="route", + send_welcome_message=True, + store_page_config={ + "custom_cta": "custom_cta", + "show_price": True, + }, title="title", - visibility="visibility", + visibility="visible", ) assert_matches_type(Product, product, path=["response"]) @@ -397,7 +484,7 @@ async def test_method_update_with_all_params(self, async_client: AsyncWhop) -> N @parametrize async def test_raw_response_update(self, async_client: AsyncWhop) -> None: response = await async_client.products.with_raw_response.update( - id="id", + id="prod_xxxxxxxxxxxxx", ) assert response.is_closed is True @@ -409,7 +496,7 @@ async def test_raw_response_update(self, async_client: AsyncWhop) -> None: @parametrize async def test_streaming_response_update(self, async_client: AsyncWhop) -> None: async with async_client.products.with_streaming_response.update( - id="id", + id="prod_xxxxxxxxxxxxx", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -431,7 +518,7 @@ async def test_path_params_update(self, async_client: AsyncWhop) -> None: @parametrize async def test_method_list(self, async_client: AsyncWhop) -> None: product = await async_client.products.list( - company_id="company_id", + company_id="biz_xxxxxxxxxxxxxx", ) assert_matches_type(AsyncCursorPage[ProductListItem], product, path=["response"]) @@ -439,15 +526,17 @@ async def test_method_list(self, async_client: AsyncWhop) -> None: @parametrize async def test_method_list_with_all_params(self, async_client: AsyncWhop) -> None: product = await async_client.products.list( - company_id="company_id", - access_pass_types=["string"], + 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"), direction="asc", - first=0, - last=0, - order="order", - visibilities=["string"], + first=42, + last=42, + order="active_memberships_count", + product_types=["regular"], + visibilities=["visible"], ) assert_matches_type(AsyncCursorPage[ProductListItem], product, path=["response"]) @@ -455,7 +544,7 @@ async def test_method_list_with_all_params(self, async_client: AsyncWhop) -> Non @parametrize async def test_raw_response_list(self, async_client: AsyncWhop) -> None: response = await async_client.products.with_raw_response.list( - company_id="company_id", + company_id="biz_xxxxxxxxxxxxxx", ) assert response.is_closed is True @@ -467,7 +556,7 @@ async def test_raw_response_list(self, async_client: AsyncWhop) -> None: @parametrize async def test_streaming_response_list(self, async_client: AsyncWhop) -> None: async with async_client.products.with_streaming_response.list( - company_id="company_id", + company_id="biz_xxxxxxxxxxxxxx", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -481,7 +570,7 @@ async def test_streaming_response_list(self, async_client: AsyncWhop) -> None: @parametrize async def test_method_delete(self, async_client: AsyncWhop) -> None: product = await async_client.products.delete( - "id", + "prod_xxxxxxxxxxxxx", ) assert_matches_type(ProductDeleteResponse, product, path=["response"]) @@ -489,7 +578,7 @@ async def test_method_delete(self, async_client: AsyncWhop) -> None: @parametrize async def test_raw_response_delete(self, async_client: AsyncWhop) -> None: response = await async_client.products.with_raw_response.delete( - "id", + "prod_xxxxxxxxxxxxx", ) assert response.is_closed is True @@ -501,7 +590,7 @@ async def test_raw_response_delete(self, async_client: AsyncWhop) -> None: @parametrize async def test_streaming_response_delete(self, async_client: AsyncWhop) -> None: async with async_client.products.with_streaming_response.delete( - "id", + "prod_xxxxxxxxxxxxx", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" From 9f854ae429336f74f1c189260c83369c00d332e8 Mon Sep 17 00:00:00 2001 From: stlc-bot Date: Thu, 11 Jun 2026 19:36:22 +0000 Subject: [PATCH 030/109] Add card transactions and swaps to financial activity endpoint Stainless-Generated-From: 1ab9bbcb63f0275436e7a3ad2d0a39cde3216d1c --- .../types/financial_activity_list_response.py | 36 ++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/src/whop_sdk/types/financial_activity_list_response.py b/src/whop_sdk/types/financial_activity_list_response.py index 3340175a..628d7671 100644 --- a/src/whop_sdk/types/financial_activity_list_response.py +++ b/src/whop_sdk/types/financial_activity_list_response.py @@ -24,6 +24,7 @@ "DataResourceUnionMember3Bank", "DataResourceUnionMember3Card", "DataResourceUnionMember4", + "DataResourceUnionMember5", "DataSource", "DataSourcePayoutDestination", "PageInfo", @@ -150,12 +151,33 @@ class DataResourceUnionMember4(BaseModel): provider: Optional[str] = None +class DataResourceUnionMember5(BaseModel): + id: str + + merchant_category: Optional[str] = None + + merchant_icon_url: Optional[str] = None + + merchant_name: Optional[str] = None + + object: Literal["card_transaction"] + + status: Optional[str] = None + + usd_amount: Optional[str] = None + """The processor-settled USD amount as a decimal string. + + The ledger's USDT leg is posted 1:1 from this value. + """ + + DataResource: TypeAlias = Union[ DataResourceUnionMember0, DataResourceUnionMember1, DataResourceUnionMember2, DataResourceUnionMember3, DataResourceUnionMember4, + DataResourceUnionMember5, None, ] @@ -191,6 +213,12 @@ class DataSource(BaseModel): payout:withdrawal:read). """ + from_amount: Optional[str] = None + """Amount converted out of from_currency as a decimal string (swap sources only).""" + + from_currency: Optional[str] = None + """Lowercase currency code converted from (swap sources only).""" + payer_name: Optional[str] = None """ Name of the entity processing the payout (withdrawal sources only; requires @@ -215,8 +243,14 @@ class DataSource(BaseModel): payout:withdrawal:read). """ + to_amount: Optional[str] = None + """Amount received in to_currency as a decimal string (swap sources only).""" + + to_currency: Optional[str] = None + """Lowercase currency code converted to (swap sources only).""" + tx_hash: Optional[str] = None - """On-chain transaction hash (onchain_transaction sources only).""" + """On-chain transaction hash (onchain_transaction and swap sources only).""" if TYPE_CHECKING: # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a From e551005aca67847110ec5ea1fa2ce97aa0337bec Mon Sep 17 00:00:00 2001 From: stlc-bot Date: Thu, 11 Jun 2026 23:18:28 +0000 Subject: [PATCH 031/109] Make POST /transfers the single money-out API (ledger transfers, wallet sends, claim links) Stainless-Generated-From: c515faed7f9933b503d5e28123d7ee2aee156f24 --- .stats.yml | 2 +- api.md | 10 +- src/whop_sdk/_client.py | 6 - src/whop_sdk/resources/transfers.py | 319 +++++++++--------- src/whop_sdk/resources/wallets.py | 166 +-------- src/whop_sdk/types/__init__.py | 5 +- src/whop_sdk/types/shared/__init__.py | 1 - src/whop_sdk/types/shared/transfer.py | 150 -------- src/whop_sdk/types/transfer_create_params.py | 52 +-- .../types/transfer_create_response.py | 157 +++++++++ src/whop_sdk/types/transfer_list_params.py | 53 ++- src/whop_sdk/types/transfer_list_response.py | 28 +- .../types/transfer_retrieve_response.py | 90 +++++ src/whop_sdk/types/wallet_send_params.py | 40 --- src/whop_sdk/types/wallet_send_response.py | 68 ---- tests/api_resources/test_transfers.py | 105 +++--- tests/api_resources/test_wallets.py | 103 +----- 17 files changed, 519 insertions(+), 836 deletions(-) delete mode 100644 src/whop_sdk/types/shared/transfer.py create mode 100644 src/whop_sdk/types/transfer_create_response.py create mode 100644 src/whop_sdk/types/transfer_retrieve_response.py delete mode 100644 src/whop_sdk/types/wallet_send_params.py delete mode 100644 src/whop_sdk/types/wallet_send_response.py diff --git a/.stats.yml b/.stats.yml index 5aab750e..9d0edd97 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1 +1 @@ -configured_endpoints: 233 +configured_endpoints: 232 diff --git a/api.md b/api.md index a4776fbc..1faf21c0 100644 --- a/api.md +++ b/api.md @@ -53,7 +53,6 @@ from whop_sdk.types import ( ShipmentSubstatus, SupportChannel, TaxType, - Transfer, Visibility, VisibilityFilter, WhoCanCommentTypes, @@ -261,13 +260,13 @@ Methods: Types: ```python -from whop_sdk.types import TransferListResponse +from whop_sdk.types import TransferCreateResponse, TransferRetrieveResponse, TransferListResponse ``` Methods: -- client.transfers.create(\*\*params) -> Transfer -- client.transfers.retrieve(id) -> Transfer +- client.transfers.create(\*\*params) -> TransferCreateResponse +- client.transfers.retrieve(id) -> TransferRetrieveResponse - client.transfers.list(\*\*params) -> SyncCursorPage[TransferListResponse] # LedgerAccounts @@ -732,13 +731,12 @@ Methods: Types: ```python -from whop_sdk.types import AccountWallet, WalletListResponse, WalletSendResponse +from whop_sdk.types import AccountWallet, WalletListResponse ``` Methods: - client.wallets.list() -> WalletListResponse -- client.wallets.send(\*\*params) -> WalletSendResponse # FinancialActivity diff --git a/src/whop_sdk/_client.py b/src/whop_sdk/_client.py index c48d2fa4..f23f4e9e 100644 --- a/src/whop_sdk/_client.py +++ b/src/whop_sdk/_client.py @@ -327,7 +327,6 @@ def forum_posts(self) -> ForumPostsResource: @cached_property def transfers(self) -> TransfersResource: - """Transfers""" from .resources.transfers import TransfersResource return TransfersResource(self) @@ -981,7 +980,6 @@ def forum_posts(self) -> AsyncForumPostsResource: @cached_property def transfers(self) -> AsyncTransfersResource: - """Transfers""" from .resources.transfers import AsyncTransfersResource return AsyncTransfersResource(self) @@ -1555,7 +1553,6 @@ def forum_posts(self) -> forum_posts.ForumPostsResourceWithRawResponse: @cached_property def transfers(self) -> transfers.TransfersResourceWithRawResponse: - """Transfers""" from .resources.transfers import TransfersResourceWithRawResponse return TransfersResourceWithRawResponse(self._client.transfers) @@ -2011,7 +2008,6 @@ def forum_posts(self) -> forum_posts.AsyncForumPostsResourceWithRawResponse: @cached_property def transfers(self) -> transfers.AsyncTransfersResourceWithRawResponse: - """Transfers""" from .resources.transfers import AsyncTransfersResourceWithRawResponse return AsyncTransfersResourceWithRawResponse(self._client.transfers) @@ -2469,7 +2465,6 @@ def forum_posts(self) -> forum_posts.ForumPostsResourceWithStreamingResponse: @cached_property def transfers(self) -> transfers.TransfersResourceWithStreamingResponse: - """Transfers""" from .resources.transfers import TransfersResourceWithStreamingResponse return TransfersResourceWithStreamingResponse(self._client.transfers) @@ -2927,7 +2922,6 @@ def forum_posts(self) -> forum_posts.AsyncForumPostsResourceWithStreamingRespons @cached_property def transfers(self) -> transfers.AsyncTransfersResourceWithStreamingResponse: - """Transfers""" from .resources.transfers import AsyncTransfersResourceWithStreamingResponse return AsyncTransfersResourceWithStreamingResponse(self._client.transfers) diff --git a/src/whop_sdk/resources/transfers.py b/src/whop_sdk/resources/transfers.py index 806984ba..b017736c 100644 --- a/src/whop_sdk/resources/transfers.py +++ b/src/whop_sdk/resources/transfers.py @@ -2,7 +2,7 @@ from __future__ import annotations -from typing import Dict, Union, Optional +from typing import Any, Dict, Union, Optional, cast from datetime import datetime from typing_extensions import Literal @@ -21,17 +21,14 @@ ) from ..pagination import SyncCursorPage, AsyncCursorPage from .._base_client import AsyncPaginator, make_request_options -from ..types.shared.currency import Currency -from ..types.shared.transfer import Transfer -from ..types.shared.direction import Direction from ..types.transfer_list_response import TransferListResponse +from ..types.transfer_create_response import TransferCreateResponse +from ..types.transfer_retrieve_response import TransferRetrieveResponse __all__ = ["TransfersResource", "AsyncTransfersResource"] class TransfersResource(SyncAPIResource): - """Transfers""" - @cached_property def with_raw_response(self) -> TransfersResourceWithRawResponse: """ @@ -55,48 +52,53 @@ def create( self, *, amount: float, - currency: Currency, - destination_id: str, origin_id: str, + currency: str | Omit = omit, + destination_id: str | Omit = omit, + expires_at: Union[str, datetime, None] | Omit = omit, idempotence_key: Optional[str] | Omit = omit, metadata: Optional[Dict[str, object]] | Omit = omit, notes: Optional[str] | Omit = omit, + redeemable_count: int | Omit = omit, + type: Literal["ledger", "wallet_send", "claim_link"] | 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, - ) -> Transfer: - """ - Transfer funds between two ledger accounts, such as from a company balance to a - user balance. - - Required permissions: + ) -> TransferCreateResponse: + """Moves funds out of an account. - - `payout:transfer_funds` + `type` selects the kind of movement (default + `ledger`): `ledger` transfers credit between two ledger accounts and returns a + Transfer; `wallet_send` sends USDT from the origin account's Ethereum wallet to + a recipient; `claim_link` funds a shareable claim link anyone with the URL can + redeem. Args: - amount: The amount to transfer in the specified currency. For example, 25.00 for $25.00 - USD. + amount: The amount to move, in the transfer currency. For example 25.00. + + origin_id: The account sending the funds. A user ID (user_xxx), company ID (biz_xxx), or + ledger account ID (ldgr_xxx). + + currency: The currency, such as usd. Required for ledger transfers. - currency: The currency of the transfer amount, such as 'usd'. + destination_id: The recipient. Required for ledger and wallet*send (a user*/biz*/ldgr* ID, or — + for sends — an email). Omit for claim_link. - destination_id: The identifier of the account receiving the funds. Accepts a user ID - ('user_xxx'), company ID ('biz_xxx'), ledger account ID ('ldgr_xxx'), or an - email address — emails without an existing Whop user trigger a placeholder-user - signup. + expires_at: claim_link only. Link expiry as an ISO 8601 timestamp. Defaults to 24 hours from + creation. - origin_id: The identifier of the account sending the funds. Accepts a user ID ('user_xxx'), - company ID ('biz_xxx'), or ledger account ID ('ldgr_xxx'). + idempotence_key: Ledger transfers only. A unique key to prevent duplicate transfers. - idempotence_key: A unique key to prevent duplicate transfers. Use a UUID or similar unique - string. + metadata: Ledger transfers only. Custom key-value pairs attached to the transfer. - metadata: A JSON object of custom metadata to attach to the transfer for tracking - purposes. + notes: Ledger transfers only. A short note describing the transfer. - notes: A short note describing the transfer, up to 50 characters. + redeemable_count: claim_link only. How many different users can claim the link. Defaults to 1. + + type: The kind of money movement. Defaults to ledger. extra_headers: Send extra headers @@ -106,24 +108,32 @@ def create( timeout: Override the client-level default timeout for this request, in seconds """ - return self._post( - "/transfers", - body=maybe_transform( - { - "amount": amount, - "currency": currency, - "destination_id": destination_id, - "origin_id": origin_id, - "idempotence_key": idempotence_key, - "metadata": metadata, - "notes": notes, - }, - transfer_create_params.TransferCreateParams, - ), - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + return cast( + TransferCreateResponse, + self._post( + "/transfers", + body=maybe_transform( + { + "amount": amount, + "origin_id": origin_id, + "currency": currency, + "destination_id": destination_id, + "expires_at": expires_at, + "idempotence_key": idempotence_key, + "metadata": metadata, + "notes": notes, + "redeemable_count": redeemable_count, + "type": type, + }, + transfer_create_params.TransferCreateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=cast( + Any, TransferCreateResponse + ), # Union types cannot be passed in as arguments in the type system ), - cast_to=Transfer, ) def retrieve( @@ -136,13 +146,9 @@ def retrieve( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> Transfer: + ) -> TransferRetrieveResponse: """ - Retrieves the details of an existing transfer. - - Required permissions: - - - `payout:transfer:read` + Retrieves a ledger transfer by ID. Args: extra_headers: Send extra headers @@ -160,22 +166,22 @@ def retrieve( options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), - cast_to=Transfer, + cast_to=TransferRetrieveResponse, ) def list( self, *, - 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, - destination_id: Optional[str] | Omit = omit, - direction: Optional[Direction] | Omit = omit, - first: Optional[int] | Omit = omit, - last: Optional[int] | Omit = omit, - order: Optional[Literal["amount", "created_at"]] | Omit = omit, - origin_id: Optional[str] | Omit = omit, + after: str | Omit = omit, + before: str | Omit = omit, + created_after: str | Omit = omit, + created_before: str | Omit = omit, + destination_id: str | Omit = omit, + direction: Literal["asc", "desc"] | Omit = omit, + first: int | Omit = omit, + last: int | Omit = omit, + order: Literal["created_at", "amount"] | Omit = omit, + origin_id: 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, @@ -183,36 +189,31 @@ def list( extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> SyncCursorPage[TransferListResponse]: - """ - Returns a paginated list of fund transfers, filtered by origin or destination - account, with optional sorting and date filtering. - - Required permissions: + """Lists ledger transfers for an account. - - `payout:transfer:read` + You must specify an origin_id or a + destination_id. Args: - after: Returns the elements in the list that come after the specified cursor. + after: Cursor to fetch the page after (from page_info.end_cursor). - before: Returns the elements in the list that come before the specified cursor. + before: Cursor to fetch the page before (from page_info.start_cursor). - created_after: Only return transfers created after this timestamp. + created_after: Only transfers created strictly after this ISO 8601 timestamp. - created_before: Only return transfers created before this timestamp. + created_before: Only transfers created strictly before this ISO 8601 timestamp. - destination_id: Filter to transfers received by this account. Accepts a user, company, or ledger - account ID. + destination_id: Filter to transfers received by this account. - direction: The direction of the sort. + direction: Sort direction. Defaults to desc. - first: Returns the first _n_ elements from the list. + first: Number of transfers to return from the start of the window. - last: Returns the last _n_ elements from the list. + last: Number of transfers to return from the end of the window. - order: Which columns can be used to sort. + order: Sort column. Defaults to created_at. - origin_id: Filter to transfers sent from this account. Accepts a user, company, or ledger - account ID. + origin_id: Filter to transfers sent from this account. extra_headers: Send extra headers @@ -251,8 +252,6 @@ def list( class AsyncTransfersResource(AsyncAPIResource): - """Transfers""" - @cached_property def with_raw_response(self) -> AsyncTransfersResourceWithRawResponse: """ @@ -276,48 +275,53 @@ async def create( self, *, amount: float, - currency: Currency, - destination_id: str, origin_id: str, + currency: str | Omit = omit, + destination_id: str | Omit = omit, + expires_at: Union[str, datetime, None] | Omit = omit, idempotence_key: Optional[str] | Omit = omit, metadata: Optional[Dict[str, object]] | Omit = omit, notes: Optional[str] | Omit = omit, + redeemable_count: int | Omit = omit, + type: Literal["ledger", "wallet_send", "claim_link"] | 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, - ) -> Transfer: - """ - Transfer funds between two ledger accounts, such as from a company balance to a - user balance. - - Required permissions: + ) -> TransferCreateResponse: + """Moves funds out of an account. - - `payout:transfer_funds` + `type` selects the kind of movement (default + `ledger`): `ledger` transfers credit between two ledger accounts and returns a + Transfer; `wallet_send` sends USDT from the origin account's Ethereum wallet to + a recipient; `claim_link` funds a shareable claim link anyone with the URL can + redeem. Args: - amount: The amount to transfer in the specified currency. For example, 25.00 for $25.00 - USD. + amount: The amount to move, in the transfer currency. For example 25.00. + + origin_id: The account sending the funds. A user ID (user_xxx), company ID (biz_xxx), or + ledger account ID (ldgr_xxx). + + currency: The currency, such as usd. Required for ledger transfers. - currency: The currency of the transfer amount, such as 'usd'. + destination_id: The recipient. Required for ledger and wallet*send (a user*/biz*/ldgr* ID, or — + for sends — an email). Omit for claim_link. - destination_id: The identifier of the account receiving the funds. Accepts a user ID - ('user_xxx'), company ID ('biz_xxx'), ledger account ID ('ldgr_xxx'), or an - email address — emails without an existing Whop user trigger a placeholder-user - signup. + expires_at: claim_link only. Link expiry as an ISO 8601 timestamp. Defaults to 24 hours from + creation. - origin_id: The identifier of the account sending the funds. Accepts a user ID ('user_xxx'), - company ID ('biz_xxx'), or ledger account ID ('ldgr_xxx'). + idempotence_key: Ledger transfers only. A unique key to prevent duplicate transfers. - idempotence_key: A unique key to prevent duplicate transfers. Use a UUID or similar unique - string. + metadata: Ledger transfers only. Custom key-value pairs attached to the transfer. - metadata: A JSON object of custom metadata to attach to the transfer for tracking - purposes. + notes: Ledger transfers only. A short note describing the transfer. - notes: A short note describing the transfer, up to 50 characters. + redeemable_count: claim_link only. How many different users can claim the link. Defaults to 1. + + type: The kind of money movement. Defaults to ledger. extra_headers: Send extra headers @@ -327,24 +331,32 @@ async def create( timeout: Override the client-level default timeout for this request, in seconds """ - return await self._post( - "/transfers", - body=await async_maybe_transform( - { - "amount": amount, - "currency": currency, - "destination_id": destination_id, - "origin_id": origin_id, - "idempotence_key": idempotence_key, - "metadata": metadata, - "notes": notes, - }, - transfer_create_params.TransferCreateParams, - ), - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + return cast( + TransferCreateResponse, + await self._post( + "/transfers", + body=await async_maybe_transform( + { + "amount": amount, + "origin_id": origin_id, + "currency": currency, + "destination_id": destination_id, + "expires_at": expires_at, + "idempotence_key": idempotence_key, + "metadata": metadata, + "notes": notes, + "redeemable_count": redeemable_count, + "type": type, + }, + transfer_create_params.TransferCreateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=cast( + Any, TransferCreateResponse + ), # Union types cannot be passed in as arguments in the type system ), - cast_to=Transfer, ) async def retrieve( @@ -357,13 +369,9 @@ async def retrieve( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> Transfer: + ) -> TransferRetrieveResponse: """ - Retrieves the details of an existing transfer. - - Required permissions: - - - `payout:transfer:read` + Retrieves a ledger transfer by ID. Args: extra_headers: Send extra headers @@ -381,22 +389,22 @@ async def retrieve( options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), - cast_to=Transfer, + cast_to=TransferRetrieveResponse, ) def list( self, *, - 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, - destination_id: Optional[str] | Omit = omit, - direction: Optional[Direction] | Omit = omit, - first: Optional[int] | Omit = omit, - last: Optional[int] | Omit = omit, - order: Optional[Literal["amount", "created_at"]] | Omit = omit, - origin_id: Optional[str] | Omit = omit, + after: str | Omit = omit, + before: str | Omit = omit, + created_after: str | Omit = omit, + created_before: str | Omit = omit, + destination_id: str | Omit = omit, + direction: Literal["asc", "desc"] | Omit = omit, + first: int | Omit = omit, + last: int | Omit = omit, + order: Literal["created_at", "amount"] | Omit = omit, + origin_id: 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, @@ -404,36 +412,31 @@ def list( extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> AsyncPaginator[TransferListResponse, AsyncCursorPage[TransferListResponse]]: - """ - Returns a paginated list of fund transfers, filtered by origin or destination - account, with optional sorting and date filtering. - - Required permissions: + """Lists ledger transfers for an account. - - `payout:transfer:read` + You must specify an origin_id or a + destination_id. Args: - after: Returns the elements in the list that come after the specified cursor. + after: Cursor to fetch the page after (from page_info.end_cursor). - before: Returns the elements in the list that come before the specified cursor. + before: Cursor to fetch the page before (from page_info.start_cursor). - created_after: Only return transfers created after this timestamp. + created_after: Only transfers created strictly after this ISO 8601 timestamp. - created_before: Only return transfers created before this timestamp. + created_before: Only transfers created strictly before this ISO 8601 timestamp. - destination_id: Filter to transfers received by this account. Accepts a user, company, or ledger - account ID. + destination_id: Filter to transfers received by this account. - direction: The direction of the sort. + direction: Sort direction. Defaults to desc. - first: Returns the first _n_ elements from the list. + first: Number of transfers to return from the start of the window. - last: Returns the last _n_ elements from the list. + last: Number of transfers to return from the end of the window. - order: Which columns can be used to sort. + order: Sort column. Defaults to created_at. - origin_id: Filter to transfers sent from this account. Accepts a user, company, or ledger - account ID. + origin_id: Filter to transfers sent from this account. extra_headers: Send extra headers diff --git a/src/whop_sdk/resources/wallets.py b/src/whop_sdk/resources/wallets.py index 7ead30a7..d5ef6406 100644 --- a/src/whop_sdk/resources/wallets.py +++ b/src/whop_sdk/resources/wallets.py @@ -2,14 +2,9 @@ from __future__ import annotations -from typing import Any, Union, cast -from datetime import datetime - import httpx -from ..types import wallet_send_params -from .._types import Body, Omit, Query, Headers, NotGiven, omit, not_given -from .._utils import maybe_transform, async_maybe_transform +from .._types import Body, Query, Headers, NotGiven, not_given from .._compat import cached_property from .._resource import SyncAPIResource, AsyncAPIResource from .._response import ( @@ -20,7 +15,6 @@ ) from .._base_client import make_request_options from ..types.wallet_list_response import WalletListResponse -from ..types.wallet_send_response import WalletSendResponse __all__ = ["WalletsResource", "AsyncWalletsResource"] @@ -64,79 +58,6 @@ def list( cast_to=WalletListResponse, ) - def send( - self, - *, - account_id: str, - amount: str, - expires_at: Union[str, datetime] | Omit = omit, - link: bool | Omit = omit, - redeemable_count: int | Omit = omit, - to: 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, - ) -> WalletSendResponse: - """Sends USDT from an account's wallet to another Whop user or business. - - With link: - true instead of to, funds a claim link anyone with the URL can redeem (requires - the airdrop_link:manage scope) and returns its claim_url. - - Args: - account_id: The sending account ID. - - amount: USDT amount to send — or the per-claim USD amount when link is true. - - expires_at: Claim-link expiry as an ISO 8601 timestamp (link mode only). Defaults to 24 - hours from creation. - - link: When true, creates a claim link instead of sending to a recipient. Mutually - exclusive with to. Requires the airdrop_link:manage scope. - - redeemable_count: How many different users can claim the link (link mode only). Defaults to 1. - - to: Recipient user ID, business account ID, ledger account ID, or email. Omit when - link is true. - - 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( - WalletSendResponse, - self._post( - "/wallets/send", - body=maybe_transform( - { - "amount": amount, - "expires_at": expires_at, - "link": link, - "redeemable_count": redeemable_count, - "to": to, - }, - wallet_send_params.WalletSendParams, - ), - options=make_request_options( - extra_headers=extra_headers, - extra_query=extra_query, - extra_body=extra_body, - timeout=timeout, - query=maybe_transform({"account_id": account_id}, wallet_send_params.WalletSendParams), - ), - cast_to=cast( - Any, WalletSendResponse - ), # Union types cannot be passed in as arguments in the type system - ), - ) - class AsyncWalletsResource(AsyncAPIResource): @cached_property @@ -177,79 +98,6 @@ async def list( cast_to=WalletListResponse, ) - async def send( - self, - *, - account_id: str, - amount: str, - expires_at: Union[str, datetime] | Omit = omit, - link: bool | Omit = omit, - redeemable_count: int | Omit = omit, - to: 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, - ) -> WalletSendResponse: - """Sends USDT from an account's wallet to another Whop user or business. - - With link: - true instead of to, funds a claim link anyone with the URL can redeem (requires - the airdrop_link:manage scope) and returns its claim_url. - - Args: - account_id: The sending account ID. - - amount: USDT amount to send — or the per-claim USD amount when link is true. - - expires_at: Claim-link expiry as an ISO 8601 timestamp (link mode only). Defaults to 24 - hours from creation. - - link: When true, creates a claim link instead of sending to a recipient. Mutually - exclusive with to. Requires the airdrop_link:manage scope. - - redeemable_count: How many different users can claim the link (link mode only). Defaults to 1. - - to: Recipient user ID, business account ID, ledger account ID, or email. Omit when - link is true. - - 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( - WalletSendResponse, - await self._post( - "/wallets/send", - body=await async_maybe_transform( - { - "amount": amount, - "expires_at": expires_at, - "link": link, - "redeemable_count": redeemable_count, - "to": to, - }, - wallet_send_params.WalletSendParams, - ), - options=make_request_options( - extra_headers=extra_headers, - extra_query=extra_query, - extra_body=extra_body, - timeout=timeout, - query=await async_maybe_transform({"account_id": account_id}, wallet_send_params.WalletSendParams), - ), - cast_to=cast( - Any, WalletSendResponse - ), # Union types cannot be passed in as arguments in the type system - ), - ) - class WalletsResourceWithRawResponse: def __init__(self, wallets: WalletsResource) -> None: @@ -258,9 +106,6 @@ def __init__(self, wallets: WalletsResource) -> None: self.list = to_raw_response_wrapper( wallets.list, ) - self.send = to_raw_response_wrapper( - wallets.send, - ) class AsyncWalletsResourceWithRawResponse: @@ -270,9 +115,6 @@ def __init__(self, wallets: AsyncWalletsResource) -> None: self.list = async_to_raw_response_wrapper( wallets.list, ) - self.send = async_to_raw_response_wrapper( - wallets.send, - ) class WalletsResourceWithStreamingResponse: @@ -282,9 +124,6 @@ def __init__(self, wallets: WalletsResource) -> None: self.list = to_streamed_response_wrapper( wallets.list, ) - self.send = to_streamed_response_wrapper( - wallets.send, - ) class AsyncWalletsResourceWithStreamingResponse: @@ -294,6 +133,3 @@ def __init__(self, wallets: AsyncWalletsResource) -> None: self.list = async_to_streamed_response_wrapper( wallets.list, ) - self.send = async_to_streamed_response_wrapper( - wallets.send, - ) diff --git a/src/whop_sdk/types/__init__.py b/src/whop_sdk/types/__init__.py index 112e1f86..515c4777 100644 --- a/src/whop_sdk/types/__init__.py +++ b/src/whop_sdk/types/__init__.py @@ -24,7 +24,6 @@ PlanType as PlanType, Reaction as Reaction, Shipment as Shipment, - Transfer as Transfer, CustomCta as CustomCta, Direction as Direction, ForumPost as ForumPost, @@ -142,7 +141,6 @@ from .swap_create_params import SwapCreateParams as SwapCreateParams from .swap_list_response import SwapListResponse as SwapListResponse from .user_update_params import UserUpdateParams as UserUpdateParams -from .wallet_send_params import WalletSendParams as WalletSendParams from .account_list_params import AccountListParams as AccountListParams from .account_social_link import AccountSocialLink as AccountSocialLink from .ai_chat_list_params import AIChatListParams as AIChatListParams @@ -186,7 +184,6 @@ from .unwrap_webhook_event import UnwrapWebhookEvent as UnwrapWebhookEvent from .user_retrieve_params import UserRetrieveParams as UserRetrieveParams from .wallet_list_response import WalletListResponse as WalletListResponse -from .wallet_send_response import WalletSendResponse as WalletSendResponse from .withdrawal_fee_types import WithdrawalFeeTypes as WithdrawalFeeTypes from .account_create_params import AccountCreateParams as AccountCreateParams from .account_list_response import AccountListResponse as AccountListResponse @@ -291,6 +288,7 @@ from .review_retrieve_response import ReviewRetrieveResponse as ReviewRetrieveResponse from .setup_intent_list_params import SetupIntentListParams as SetupIntentListParams from .swap_create_quote_params import SwapCreateQuoteParams as SwapCreateQuoteParams +from .transfer_create_response import TransferCreateResponse as TransferCreateResponse from .verification_list_params import VerificationListParams as VerificationListParams from .withdrawal_create_params import WithdrawalCreateParams as WithdrawalCreateParams from .withdrawal_list_response import WithdrawalListResponse as WithdrawalListResponse @@ -326,6 +324,7 @@ from .promo_code_delete_response import PromoCodeDeleteResponse as PromoCodeDeleteResponse from .setup_intent_list_response import SetupIntentListResponse as SetupIntentListResponse from .swap_create_quote_response import SwapCreateQuoteResponse as SwapCreateQuoteResponse +from .transfer_retrieve_response import TransferRetrieveResponse as TransferRetrieveResponse from .user_check_access_response import UserCheckAccessResponse as UserCheckAccessResponse from .verification_list_response import VerificationListResponse as VerificationListResponse from .ad_campaign_retrieve_params import AdCampaignRetrieveParams as AdCampaignRetrieveParams diff --git a/src/whop_sdk/types/shared/__init__.py b/src/whop_sdk/types/shared/__init__.py index b1c4a467..249442f1 100644 --- a/src/whop_sdk/types/shared/__init__.py +++ b/src/whop_sdk/types/shared/__init__.py @@ -13,7 +13,6 @@ from .reaction import Reaction as Reaction from .shipment import Shipment as Shipment from .tax_type import TaxType as TaxType -from .transfer import Transfer as Transfer from .app_build import AppBuild as AppBuild from .direction import Direction as Direction from .page_info import PageInfo as PageInfo diff --git a/src/whop_sdk/types/shared/transfer.py b/src/whop_sdk/types/shared/transfer.py deleted file mode 100644 index 8668f9e1..00000000 --- a/src/whop_sdk/types/shared/transfer.py +++ /dev/null @@ -1,150 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from typing import Dict, Union, Optional -from datetime import datetime -from typing_extensions import Literal, Annotated, TypeAlias - -from ..._utils import PropertyInfo -from .currency import Currency -from ..._models import BaseModel - -__all__ = ["Transfer", "Destination", "DestinationUser", "DestinationCompany", "Origin", "OriginUser", "OriginCompany"] - - -class DestinationUser(BaseModel): - """A user account on Whop. - - Contains profile information, identity details, and social connections. - """ - - id: str - """The unique identifier for the user.""" - - name: Optional[str] = None - """The user's display name shown on their public profile.""" - - typename: Literal["User"] - """The typename of this object""" - - username: str - """The user's unique username shown on their public profile.""" - - -class DestinationCompany(BaseModel): - """A company is a seller on Whop. - - Companies own products, manage members, and receive payouts. - """ - - id: str - """The unique identifier for the company.""" - - route: str - """ - The URL slug for the company's store page (e.g., 'pickaxe' in whop.com/pickaxe). - """ - - title: str - """The display name of the company shown to customers.""" - - typename: Literal["Company"] - """The typename of this object""" - - -Destination: TypeAlias = Annotated[ - Union[Optional[DestinationUser], Optional[DestinationCompany]], PropertyInfo(discriminator="typename") -] - - -class OriginUser(BaseModel): - """A user account on Whop. - - Contains profile information, identity details, and social connections. - """ - - id: str - """The unique identifier for the user.""" - - name: Optional[str] = None - """The user's display name shown on their public profile.""" - - typename: Literal["User"] - """The typename of this object""" - - username: str - """The user's unique username shown on their public profile.""" - - -class OriginCompany(BaseModel): - """A company is a seller on Whop. - - Companies own products, manage members, and receive payouts. - """ - - id: str - """The unique identifier for the company.""" - - route: str - """ - The URL slug for the company's store page (e.g., 'pickaxe' in whop.com/pickaxe). - """ - - title: str - """The display name of the company shown to customers.""" - - typename: Literal["Company"] - """The typename of this object""" - - -Origin: TypeAlias = Annotated[ - Union[Optional[OriginUser], Optional[OriginCompany]], PropertyInfo(discriminator="typename") -] - - -class Transfer(BaseModel): - """A transfer of credit between two ledger accounts.""" - - id: str - """The unique identifier for the credit transaction transfer.""" - - amount: float - """The transfer amount in the currency specified by the currency field. - - For example, 10.43 represents $10.43 USD. - """ - - created_at: datetime - """The datetime the credit transaction transfer was created.""" - - currency: Currency - """The currency in which this transfer amount is denominated.""" - - destination: Destination - """The entity receiving the transferred funds.""" - - destination_ledger_account_id: str - """The unique identifier of the ledger account receiving the funds.""" - - fee_amount: Optional[float] = None - """The flat fee amount deducted from this transfer, in the transfer's currency. - - Null if no flat fee was applied. - """ - - metadata: Optional[Dict[str, object]] = None - """Custom key-value pairs attached to this transfer. - - Maximum 50 keys, 500 characters per key, 5000 characters per value. - """ - - notes: Optional[str] = None - """A free-text note attached to this transfer by the sender. - - Null if no note was provided. - """ - - origin: Origin - """The entity that sent the transferred funds.""" - - origin_ledger_account_id: str - """The unique identifier of the ledger account that sent the funds.""" diff --git a/src/whop_sdk/types/transfer_create_params.py b/src/whop_sdk/types/transfer_create_params.py index f1729e58..04f239f7 100644 --- a/src/whop_sdk/types/transfer_create_params.py +++ b/src/whop_sdk/types/transfer_create_params.py @@ -2,50 +2,52 @@ from __future__ import annotations -from typing import Dict, Optional -from typing_extensions import Required, TypedDict +from typing import Dict, Union, Optional +from datetime import datetime +from typing_extensions import Literal, Required, Annotated, TypedDict -from .shared.currency import Currency +from .._utils import PropertyInfo __all__ = ["TransferCreateParams"] class TransferCreateParams(TypedDict, total=False): amount: Required[float] - """The amount to transfer in the specified currency. + """The amount to move, in the transfer currency. For example 25.00.""" - For example, 25.00 for $25.00 USD. + origin_id: Required[str] + """The account sending the funds. + + A user ID (user_xxx), company ID (biz_xxx), or ledger account ID (ldgr_xxx). """ - currency: Required[Currency] - """The currency of the transfer amount, such as 'usd'.""" + currency: str + """The currency, such as usd. Required for ledger transfers.""" - destination_id: Required[str] - """The identifier of the account receiving the funds. + destination_id: str + """The recipient. - Accepts a user ID ('user_xxx'), company ID ('biz_xxx'), ledger account ID - ('ldgr_xxx'), or an email address — emails without an existing Whop user trigger - a placeholder-user signup. + Required for ledger and wallet*send (a user*/biz*/ldgr* ID, or — for sends — an + email). Omit for claim_link. """ - origin_id: Required[str] - """The identifier of the account sending the funds. + expires_at: Annotated[Union[str, datetime, None], PropertyInfo(format="iso8601")] + """claim_link only. - Accepts a user ID ('user_xxx'), company ID ('biz_xxx'), or ledger account ID - ('ldgr_xxx'). + Link expiry as an ISO 8601 timestamp. Defaults to 24 hours from creation. """ idempotence_key: Optional[str] - """A unique key to prevent duplicate transfers. - - Use a UUID or similar unique string. - """ + """Ledger transfers only. A unique key to prevent duplicate transfers.""" metadata: Optional[Dict[str, object]] - """ - A JSON object of custom metadata to attach to the transfer for tracking - purposes. - """ + """Ledger transfers only. Custom key-value pairs attached to the transfer.""" notes: Optional[str] - """A short note describing the transfer, up to 50 characters.""" + """Ledger transfers only. A short note describing the transfer.""" + + redeemable_count: int + """claim_link only. How many different users can claim the link. Defaults to 1.""" + + type: Literal["ledger", "wallet_send", "claim_link"] + """The kind of money movement. Defaults to ledger.""" diff --git a/src/whop_sdk/types/transfer_create_response.py b/src/whop_sdk/types/transfer_create_response.py new file mode 100644 index 00000000..dd6d5e70 --- /dev/null +++ b/src/whop_sdk/types/transfer_create_response.py @@ -0,0 +1,157 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Dict, Union, Optional +from datetime import datetime +from typing_extensions import Literal, Annotated, TypeAlias + +from .._utils import PropertyInfo +from .._models import BaseModel + +__all__ = [ + "TransferCreateResponse", + "Transfer", + "TransferDestination", + "TransferDestinationCompany", + "TransferDestinationUser", + "TransferOrigin", + "TransferOriginCompany", + "TransferOriginUser", + "Send", + "SendDestination", + "SendSource", + "ClaimLink", + "ClaimLinkSource", +] + + +class TransferDestinationCompany(BaseModel): + id: str + + typename: Literal["Company"] + + route: Optional[str] = None + + title: Optional[str] = None + + +class TransferDestinationUser(BaseModel): + id: str + + typename: Literal["User"] + + name: Optional[str] = None + + username: Optional[str] = None + + +TransferDestination: TypeAlias = Annotated[ + Union[TransferDestinationCompany, TransferDestinationUser], PropertyInfo(discriminator="typename") +] + + +class TransferOriginCompany(BaseModel): + id: str + + typename: Literal["Company"] + + route: Optional[str] = None + + title: Optional[str] = None + + +class TransferOriginUser(BaseModel): + id: str + + typename: Literal["User"] + + name: Optional[str] = None + + username: Optional[str] = None + + +TransferOrigin: TypeAlias = Annotated[ + Union[TransferOriginCompany, TransferOriginUser], PropertyInfo(discriminator="typename") +] + + +class Transfer(BaseModel): + """A transfer of credit between two ledger accounts.""" + + id: str + + amount: float + + created_at: datetime + + currency: str + + destination: TransferDestination + + destination_ledger_account_id: str + + origin: TransferOrigin + + origin_ledger_account_id: str + + fee_amount: Optional[float] = None + + metadata: Optional[Dict[str, object]] = None + + notes: Optional[str] = None + + +class SendDestination(BaseModel): + account_id: str + + address: str + + +class SendSource(BaseModel): + account_id: str + + address: str + + +class Send(BaseModel): + """Returned for a wallet_send: an onchain USDT send to a recipient.""" + + amount: str + + currency: str + + destination: SendDestination + + object: Literal["send"] + + source: SendSource + + tx_hash: str + + +class ClaimLinkSource(BaseModel): + account_id: str + + +class ClaimLink(BaseModel): + """Returned for a claim_link: a shareable URL anyone can open to claim the funds.""" + + id: str + + amount: str + + claim_url: str + + currency: str + + expires_at: Optional[datetime] = None + + object: Literal["claim_link"] + + redeemable_count: int + + source: ClaimLinkSource + + status: str + + +TransferCreateResponse: TypeAlias = Union[Transfer, Send, ClaimLink] diff --git a/src/whop_sdk/types/transfer_list_params.py b/src/whop_sdk/types/transfer_list_params.py index aedb7b41..2f6bd493 100644 --- a/src/whop_sdk/types/transfer_list_params.py +++ b/src/whop_sdk/types/transfer_list_params.py @@ -2,49 +2,38 @@ from __future__ import annotations -from typing import Union, Optional -from datetime import datetime -from typing_extensions import Literal, Annotated, TypedDict - -from .._utils import PropertyInfo -from .shared.direction import Direction +from typing_extensions import Literal, TypedDict __all__ = ["TransferListParams"] class TransferListParams(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.""" - - created_after: Annotated[Union[str, datetime, None], PropertyInfo(format="iso8601")] - """Only return transfers created after this timestamp.""" + after: str + """Cursor to fetch the page after (from page_info.end_cursor).""" - created_before: Annotated[Union[str, datetime, None], PropertyInfo(format="iso8601")] - """Only return transfers created before this timestamp.""" + before: str + """Cursor to fetch the page before (from page_info.start_cursor).""" - destination_id: Optional[str] - """Filter to transfers received by this account. + created_after: str + """Only transfers created strictly after this ISO 8601 timestamp.""" - Accepts a user, company, or ledger account ID. - """ + created_before: str + """Only transfers created strictly before this ISO 8601 timestamp.""" - direction: Optional[Direction] - """The direction of the sort.""" + destination_id: str + """Filter to transfers received by this account.""" - first: Optional[int] - """Returns the first _n_ elements from the list.""" + direction: Literal["asc", "desc"] + """Sort direction. Defaults to desc.""" - last: Optional[int] - """Returns the last _n_ elements from the list.""" + first: int + """Number of transfers to return from the start of the window.""" - order: Optional[Literal["amount", "created_at"]] - """Which columns can be used to sort.""" + last: int + """Number of transfers to return from the end of the window.""" - origin_id: Optional[str] - """Filter to transfers sent from this account. + order: Literal["created_at", "amount"] + """Sort column. Defaults to created_at.""" - Accepts a user, company, or ledger account ID. - """ + origin_id: str + """Filter to transfers sent from this account.""" diff --git a/src/whop_sdk/types/transfer_list_response.py b/src/whop_sdk/types/transfer_list_response.py index 7e1530a3..5a167016 100644 --- a/src/whop_sdk/types/transfer_list_response.py +++ b/src/whop_sdk/types/transfer_list_response.py @@ -4,7 +4,6 @@ from datetime import datetime from .._models import BaseModel -from .shared.currency import Currency __all__ = ["TransferListResponse"] @@ -13,40 +12,19 @@ class TransferListResponse(BaseModel): """A transfer of credit between two ledger accounts.""" id: str - """The unique identifier for the credit transaction transfer.""" amount: float - """The transfer amount in the currency specified by the currency field. - - For example, 10.43 represents $10.43 USD. - """ created_at: datetime - """The datetime the credit transaction transfer was created.""" - currency: Currency - """The currency in which this transfer amount is denominated.""" + currency: str destination_ledger_account_id: str - """The unique identifier of the ledger account receiving the funds.""" - fee_amount: Optional[float] = None - """The flat fee amount deducted from this transfer, in the transfer's currency. + origin_ledger_account_id: str - Null if no flat fee was applied. - """ + fee_amount: Optional[float] = None metadata: Optional[Dict[str, object]] = None - """Custom key-value pairs attached to this transfer. - - Maximum 50 keys, 500 characters per key, 5000 characters per value. - """ notes: Optional[str] = None - """A free-text note attached to this transfer by the sender. - - Null if no note was provided. - """ - - origin_ledger_account_id: str - """The unique identifier of the ledger account that sent the funds.""" diff --git a/src/whop_sdk/types/transfer_retrieve_response.py b/src/whop_sdk/types/transfer_retrieve_response.py new file mode 100644 index 00000000..c06cef55 --- /dev/null +++ b/src/whop_sdk/types/transfer_retrieve_response.py @@ -0,0 +1,90 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Dict, Union, Optional +from datetime import datetime +from typing_extensions import Literal, Annotated, TypeAlias + +from .._utils import PropertyInfo +from .._models import BaseModel + +__all__ = [ + "TransferRetrieveResponse", + "Destination", + "DestinationCompany", + "DestinationUser", + "Origin", + "OriginCompany", + "OriginUser", +] + + +class DestinationCompany(BaseModel): + id: str + + typename: Literal["Company"] + + route: Optional[str] = None + + title: Optional[str] = None + + +class DestinationUser(BaseModel): + id: str + + typename: Literal["User"] + + name: Optional[str] = None + + username: Optional[str] = None + + +Destination: TypeAlias = Annotated[Union[DestinationCompany, DestinationUser], PropertyInfo(discriminator="typename")] + + +class OriginCompany(BaseModel): + id: str + + typename: Literal["Company"] + + route: Optional[str] = None + + title: Optional[str] = None + + +class OriginUser(BaseModel): + id: str + + typename: Literal["User"] + + name: Optional[str] = None + + username: Optional[str] = None + + +Origin: TypeAlias = Annotated[Union[OriginCompany, OriginUser], PropertyInfo(discriminator="typename")] + + +class TransferRetrieveResponse(BaseModel): + """A transfer of credit between two ledger accounts.""" + + id: str + + amount: float + + created_at: datetime + + currency: str + + destination: Destination + + destination_ledger_account_id: str + + origin: Origin + + origin_ledger_account_id: str + + fee_amount: Optional[float] = None + + metadata: Optional[Dict[str, object]] = None + + notes: Optional[str] = None diff --git a/src/whop_sdk/types/wallet_send_params.py b/src/whop_sdk/types/wallet_send_params.py deleted file mode 100644 index 99928859..00000000 --- a/src/whop_sdk/types/wallet_send_params.py +++ /dev/null @@ -1,40 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from __future__ import annotations - -from typing import Union -from datetime import datetime -from typing_extensions import Required, Annotated, TypedDict - -from .._utils import PropertyInfo - -__all__ = ["WalletSendParams"] - - -class WalletSendParams(TypedDict, total=False): - account_id: Required[str] - """The sending account ID.""" - - amount: Required[str] - """USDT amount to send — or the per-claim USD amount when link is true.""" - - expires_at: Annotated[Union[str, datetime], PropertyInfo(format="iso8601")] - """Claim-link expiry as an ISO 8601 timestamp (link mode only). - - Defaults to 24 hours from creation. - """ - - link: bool - """When true, creates a claim link instead of sending to a recipient. - - Mutually exclusive with to. Requires the airdrop_link:manage scope. - """ - - redeemable_count: int - """How many different users can claim the link (link mode only). Defaults to 1.""" - - to: str - """Recipient user ID, business account ID, ledger account ID, or email. - - Omit when link is true. - """ diff --git a/src/whop_sdk/types/wallet_send_response.py b/src/whop_sdk/types/wallet_send_response.py deleted file mode 100644 index 0d02ef87..00000000 --- a/src/whop_sdk/types/wallet_send_response.py +++ /dev/null @@ -1,68 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from typing import Union, Optional -from datetime import datetime -from typing_extensions import Literal, TypeAlias - -from .._models import BaseModel - -__all__ = ["WalletSendResponse", "Send", "SendDestination", "SendSource", "ClaimLink", "ClaimLinkSource"] - - -class SendDestination(BaseModel): - account_id: str - - address: str - - -class SendSource(BaseModel): - account_id: str - - address: str - - -class Send(BaseModel): - """Returned when sending to a recipient (`to`).""" - - amount: str - - currency: str - - destination: SendDestination - - object: Literal["send"] - - source: SendSource - - tx_hash: str - - -class ClaimLinkSource(BaseModel): - account_id: str - - -class ClaimLink(BaseModel): - """Returned when creating a claim link (`link: true`).""" - - id: str - - amount: str - """Per-claim amount; a multi-claim link debits amount × redeemable_count.""" - - claim_url: str - """Shareable URL anyone can open to claim the funds.""" - - currency: str - - expires_at: Optional[datetime] = None - - object: Literal["claim_link"] - - redeemable_count: int - - source: ClaimLinkSource - - status: str - - -WalletSendResponse: TypeAlias = Union[Send, ClaimLink] diff --git a/tests/api_resources/test_transfers.py b/tests/api_resources/test_transfers.py index 0b2c1c1d..95586346 100644 --- a/tests/api_resources/test_transfers.py +++ b/tests/api_resources/test_transfers.py @@ -9,10 +9,13 @@ from whop_sdk import Whop, AsyncWhop from tests.utils import assert_matches_type -from whop_sdk.types import TransferListResponse +from whop_sdk.types import ( + TransferListResponse, + TransferCreateResponse, + TransferRetrieveResponse, +) from whop_sdk._utils import parse_datetime from whop_sdk.pagination import SyncCursorPage, AsyncCursorPage -from whop_sdk.types.shared import Transfer base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") @@ -24,56 +27,53 @@ class TestTransfers: @parametrize def test_method_create(self, client: Whop) -> None: transfer = client.transfers.create( - amount=6.9, - currency="usd", - destination_id="destination_id", + amount=0, origin_id="origin_id", ) - assert_matches_type(Transfer, transfer, path=["response"]) + assert_matches_type(TransferCreateResponse, transfer, path=["response"]) @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_method_create_with_all_params(self, client: Whop) -> None: transfer = client.transfers.create( - amount=6.9, + amount=0, + origin_id="origin_id", currency="usd", destination_id="destination_id", - origin_id="origin_id", + expires_at=parse_datetime("2019-12-27T18:11:19.117Z"), idempotence_key="idempotence_key", metadata={"foo": "bar"}, notes="notes", + redeemable_count=0, + type="ledger", ) - assert_matches_type(Transfer, transfer, path=["response"]) + assert_matches_type(TransferCreateResponse, transfer, path=["response"]) @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_raw_response_create(self, client: Whop) -> None: response = client.transfers.with_raw_response.create( - amount=6.9, - currency="usd", - destination_id="destination_id", + amount=0, origin_id="origin_id", ) assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" transfer = response.parse() - assert_matches_type(Transfer, transfer, path=["response"]) + assert_matches_type(TransferCreateResponse, transfer, path=["response"]) @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_streaming_response_create(self, client: Whop) -> None: with client.transfers.with_streaming_response.create( - amount=6.9, - currency="usd", - destination_id="destination_id", + amount=0, origin_id="origin_id", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" transfer = response.parse() - assert_matches_type(Transfer, transfer, path=["response"]) + assert_matches_type(TransferCreateResponse, transfer, path=["response"]) assert cast(Any, response.is_closed) is True @@ -81,33 +81,33 @@ def test_streaming_response_create(self, client: Whop) -> None: @parametrize def test_method_retrieve(self, client: Whop) -> None: transfer = client.transfers.retrieve( - "ctt_xxxxxxxxxxxxxx", + "id", ) - assert_matches_type(Transfer, transfer, path=["response"]) + assert_matches_type(TransferRetrieveResponse, transfer, path=["response"]) @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_raw_response_retrieve(self, client: Whop) -> None: response = client.transfers.with_raw_response.retrieve( - "ctt_xxxxxxxxxxxxxx", + "id", ) assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" transfer = response.parse() - assert_matches_type(Transfer, transfer, path=["response"]) + assert_matches_type(TransferRetrieveResponse, transfer, path=["response"]) @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_streaming_response_retrieve(self, client: Whop) -> None: with client.transfers.with_streaming_response.retrieve( - "ctt_xxxxxxxxxxxxxx", + "id", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" transfer = response.parse() - assert_matches_type(Transfer, transfer, path=["response"]) + assert_matches_type(TransferRetrieveResponse, transfer, path=["response"]) assert cast(Any, response.is_closed) is True @@ -131,13 +131,13 @@ def test_method_list_with_all_params(self, client: Whop) -> None: transfer = client.transfers.list( after="after", before="before", - created_after=parse_datetime("2023-12-01T05:00:00.401Z"), - created_before=parse_datetime("2023-12-01T05:00:00.401Z"), + created_after="created_after", + created_before="created_before", destination_id="destination_id", direction="asc", - first=42, - last=42, - order="amount", + first=50, + last=50, + order="created_at", origin_id="origin_id", ) assert_matches_type(SyncCursorPage[TransferListResponse], transfer, path=["response"]) @@ -174,56 +174,53 @@ class TestAsyncTransfers: @parametrize async def test_method_create(self, async_client: AsyncWhop) -> None: transfer = await async_client.transfers.create( - amount=6.9, - currency="usd", - destination_id="destination_id", + amount=0, origin_id="origin_id", ) - assert_matches_type(Transfer, transfer, path=["response"]) + assert_matches_type(TransferCreateResponse, transfer, 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: transfer = await async_client.transfers.create( - amount=6.9, + amount=0, + origin_id="origin_id", currency="usd", destination_id="destination_id", - origin_id="origin_id", + expires_at=parse_datetime("2019-12-27T18:11:19.117Z"), idempotence_key="idempotence_key", metadata={"foo": "bar"}, notes="notes", + redeemable_count=0, + type="ledger", ) - assert_matches_type(Transfer, transfer, path=["response"]) + assert_matches_type(TransferCreateResponse, transfer, 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.transfers.with_raw_response.create( - amount=6.9, - currency="usd", - destination_id="destination_id", + amount=0, origin_id="origin_id", ) assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" transfer = await response.parse() - assert_matches_type(Transfer, transfer, path=["response"]) + assert_matches_type(TransferCreateResponse, transfer, 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.transfers.with_streaming_response.create( - amount=6.9, - currency="usd", - destination_id="destination_id", + amount=0, origin_id="origin_id", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" transfer = await response.parse() - assert_matches_type(Transfer, transfer, path=["response"]) + assert_matches_type(TransferCreateResponse, transfer, path=["response"]) assert cast(Any, response.is_closed) is True @@ -231,33 +228,33 @@ async def test_streaming_response_create(self, async_client: AsyncWhop) -> None: @parametrize async def test_method_retrieve(self, async_client: AsyncWhop) -> None: transfer = await async_client.transfers.retrieve( - "ctt_xxxxxxxxxxxxxx", + "id", ) - assert_matches_type(Transfer, transfer, path=["response"]) + assert_matches_type(TransferRetrieveResponse, transfer, 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.transfers.with_raw_response.retrieve( - "ctt_xxxxxxxxxxxxxx", + "id", ) assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" transfer = await response.parse() - assert_matches_type(Transfer, transfer, path=["response"]) + assert_matches_type(TransferRetrieveResponse, transfer, 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.transfers.with_streaming_response.retrieve( - "ctt_xxxxxxxxxxxxxx", + "id", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" transfer = await response.parse() - assert_matches_type(Transfer, transfer, path=["response"]) + assert_matches_type(TransferRetrieveResponse, transfer, path=["response"]) assert cast(Any, response.is_closed) is True @@ -281,13 +278,13 @@ async def test_method_list_with_all_params(self, async_client: AsyncWhop) -> Non transfer = await async_client.transfers.list( after="after", before="before", - created_after=parse_datetime("2023-12-01T05:00:00.401Z"), - created_before=parse_datetime("2023-12-01T05:00:00.401Z"), + created_after="created_after", + created_before="created_before", destination_id="destination_id", direction="asc", - first=42, - last=42, - order="amount", + first=50, + last=50, + order="created_at", origin_id="origin_id", ) assert_matches_type(AsyncCursorPage[TransferListResponse], transfer, path=["response"]) diff --git a/tests/api_resources/test_wallets.py b/tests/api_resources/test_wallets.py index ed8d629a..7643a6e4 100644 --- a/tests/api_resources/test_wallets.py +++ b/tests/api_resources/test_wallets.py @@ -9,8 +9,7 @@ from whop_sdk import Whop, AsyncWhop from tests.utils import assert_matches_type -from whop_sdk.types import WalletListResponse, WalletSendResponse -from whop_sdk._utils import parse_datetime +from whop_sdk.types import WalletListResponse base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") @@ -46,56 +45,6 @@ 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_send(self, client: Whop) -> None: - wallet = client.wallets.send( - account_id="account_id", - amount="amount", - ) - assert_matches_type(WalletSendResponse, wallet, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - def test_method_send_with_all_params(self, client: Whop) -> None: - wallet = client.wallets.send( - account_id="account_id", - amount="amount", - expires_at=parse_datetime("2019-12-27T18:11:19.117Z"), - link=True, - redeemable_count=0, - to="to", - ) - assert_matches_type(WalletSendResponse, wallet, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - def test_raw_response_send(self, client: Whop) -> None: - response = client.wallets.with_raw_response.send( - account_id="account_id", - amount="amount", - ) - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - wallet = response.parse() - assert_matches_type(WalletSendResponse, wallet, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - def test_streaming_response_send(self, client: Whop) -> None: - with client.wallets.with_streaming_response.send( - account_id="account_id", - amount="amount", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - wallet = response.parse() - assert_matches_type(WalletSendResponse, wallet, path=["response"]) - - assert cast(Any, response.is_closed) is True - class TestAsyncWallets: parametrize = pytest.mark.parametrize( @@ -129,53 +78,3 @@ async def test_streaming_response_list(self, async_client: AsyncWhop) -> None: assert_matches_type(WalletListResponse, wallet, path=["response"]) assert cast(Any, response.is_closed) is True - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - async def test_method_send(self, async_client: AsyncWhop) -> None: - wallet = await async_client.wallets.send( - account_id="account_id", - amount="amount", - ) - assert_matches_type(WalletSendResponse, wallet, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - async def test_method_send_with_all_params(self, async_client: AsyncWhop) -> None: - wallet = await async_client.wallets.send( - account_id="account_id", - amount="amount", - expires_at=parse_datetime("2019-12-27T18:11:19.117Z"), - link=True, - redeemable_count=0, - to="to", - ) - assert_matches_type(WalletSendResponse, wallet, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - async def test_raw_response_send(self, async_client: AsyncWhop) -> None: - response = await async_client.wallets.with_raw_response.send( - account_id="account_id", - amount="amount", - ) - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - wallet = await response.parse() - assert_matches_type(WalletSendResponse, wallet, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - async def test_streaming_response_send(self, async_client: AsyncWhop) -> None: - async with async_client.wallets.with_streaming_response.send( - account_id="account_id", - amount="amount", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - wallet = await response.parse() - assert_matches_type(WalletSendResponse, wallet, path=["response"]) - - assert cast(Any, response.is_closed) is True From a61c5c6f22131bdc9bd3cf0b7b8fbd446743f3a5 Mon Sep 17 00:00:00 2001 From: stlc-bot Date: Thu, 11 Jun 2026 23:49:30 +0000 Subject: [PATCH 032/109] allow making plans without a product id Stainless-Generated-From: 87bc562e11465246ecb8fbf86af2f0a5652f7f27 --- src/whop_sdk/resources/plans.py | 16 +++++++------- src/whop_sdk/types/plan_create_params.py | 8 +++---- tests/api_resources/test_plans.py | 28 +++++++----------------- 3 files changed, 20 insertions(+), 32 deletions(-) diff --git a/src/whop_sdk/resources/plans.py b/src/whop_sdk/resources/plans.py index 8b6eb4ae..9f6f5b91 100644 --- a/src/whop_sdk/resources/plans.py +++ b/src/whop_sdk/resources/plans.py @@ -51,7 +51,6 @@ def with_streaming_response(self) -> PlansResourceWithStreamingResponse: def create( self, *, - product_id: str, account_id: str | Omit = omit, adaptive_pricing_enabled: Optional[bool] | Omit = omit, billing_period: Optional[int] | Omit = omit, @@ -68,6 +67,7 @@ def create( override_tax_type: str | Omit = omit, payment_method_configuration: Optional[plan_create_params.PaymentMethodConfiguration] | Omit = omit, plan_type: str | Omit = omit, + product_id: str | Omit = omit, release_method: str | Omit = omit, renewal_price: Optional[float] | Omit = omit, split_pay_required_payments: Optional[int] | Omit = omit, @@ -90,8 +90,6 @@ def create( price, and availability for customers. Args: - product_id: The unique identifier of the product to attach this plan to. - account_id: The unique identifier of the account to create this plan for. Defaults to the caller's account. @@ -130,6 +128,8 @@ def create( plan_type: The billing type of the plan, such as one_time or renewal. + product_id: The unique identifier of the product to attach this plan to. + release_method: The method used to sell this plan (e.g., buy_now, waitlist). renewal_price: The amount charged each billing period for recurring plans, in the plan's @@ -162,7 +162,6 @@ def create( "/plans", body=maybe_transform( { - "product_id": product_id, "account_id": account_id, "adaptive_pricing_enabled": adaptive_pricing_enabled, "billing_period": billing_period, @@ -179,6 +178,7 @@ def create( "override_tax_type": override_tax_type, "payment_method_configuration": payment_method_configuration, "plan_type": plan_type, + "product_id": product_id, "release_method": release_method, "renewal_price": renewal_price, "split_pay_required_payments": split_pay_required_payments, @@ -575,7 +575,6 @@ def with_streaming_response(self) -> AsyncPlansResourceWithStreamingResponse: async def create( self, *, - product_id: str, account_id: str | Omit = omit, adaptive_pricing_enabled: Optional[bool] | Omit = omit, billing_period: Optional[int] | Omit = omit, @@ -592,6 +591,7 @@ async def create( override_tax_type: str | Omit = omit, payment_method_configuration: Optional[plan_create_params.PaymentMethodConfiguration] | Omit = omit, plan_type: str | Omit = omit, + product_id: str | Omit = omit, release_method: str | Omit = omit, renewal_price: Optional[float] | Omit = omit, split_pay_required_payments: Optional[int] | Omit = omit, @@ -614,8 +614,6 @@ async def create( price, and availability for customers. Args: - product_id: The unique identifier of the product to attach this plan to. - account_id: The unique identifier of the account to create this plan for. Defaults to the caller's account. @@ -654,6 +652,8 @@ async def create( plan_type: The billing type of the plan, such as one_time or renewal. + product_id: The unique identifier of the product to attach this plan to. + release_method: The method used to sell this plan (e.g., buy_now, waitlist). renewal_price: The amount charged each billing period for recurring plans, in the plan's @@ -686,7 +686,6 @@ async def create( "/plans", body=await async_maybe_transform( { - "product_id": product_id, "account_id": account_id, "adaptive_pricing_enabled": adaptive_pricing_enabled, "billing_period": billing_period, @@ -703,6 +702,7 @@ async def create( "override_tax_type": override_tax_type, "payment_method_configuration": payment_method_configuration, "plan_type": plan_type, + "product_id": product_id, "release_method": release_method, "renewal_price": renewal_price, "split_pay_required_payments": split_pay_required_payments, diff --git a/src/whop_sdk/types/plan_create_params.py b/src/whop_sdk/types/plan_create_params.py index eee58fe6..350aec97 100644 --- a/src/whop_sdk/types/plan_create_params.py +++ b/src/whop_sdk/types/plan_create_params.py @@ -3,7 +3,7 @@ from __future__ import annotations from typing import Iterable, Optional -from typing_extensions import Literal, Required, TypedDict +from typing_extensions import Literal, TypedDict from .._types import SequenceNotStr @@ -11,9 +11,6 @@ class PlanCreateParams(TypedDict, total=False): - product_id: Required[str] - """The unique identifier of the product to attach this plan to.""" - account_id: str """The unique identifier of the account to create this plan for. @@ -80,6 +77,9 @@ class PlanCreateParams(TypedDict, total=False): plan_type: str """The billing type of the plan, such as one_time or renewal.""" + product_id: str + """The unique identifier of the product to attach this plan to.""" + release_method: str """The method used to sell this plan (e.g., buy_now, waitlist).""" diff --git a/tests/api_resources/test_plans.py b/tests/api_resources/test_plans.py index d573b6df..32e2cec2 100644 --- a/tests/api_resources/test_plans.py +++ b/tests/api_resources/test_plans.py @@ -26,16 +26,13 @@ class TestPlans: @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_method_create(self, client: Whop) -> None: - plan = client.plans.create( - product_id="product_id", - ) + plan = client.plans.create() assert_matches_type(Plan, plan, path=["response"]) @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_method_create_with_all_params(self, client: Whop) -> None: plan = client.plans.create( - product_id="product_id", account_id="account_id", adaptive_pricing_enabled=True, billing_period=0, @@ -68,6 +65,7 @@ def test_method_create_with_all_params(self, client: Whop) -> None: "include_platform_defaults": True, }, plan_type="plan_type", + product_id="product_id", release_method="release_method", renewal_price=0, split_pay_required_payments=0, @@ -83,9 +81,7 @@ def test_method_create_with_all_params(self, client: Whop) -> None: @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_raw_response_create(self, client: Whop) -> None: - response = client.plans.with_raw_response.create( - product_id="product_id", - ) + response = client.plans.with_raw_response.create() assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -95,9 +91,7 @@ def test_raw_response_create(self, client: Whop) -> None: @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_streaming_response_create(self, client: Whop) -> None: - with client.plans.with_streaming_response.create( - product_id="product_id", - ) as response: + with client.plans.with_streaming_response.create() as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -408,16 +402,13 @@ class TestAsyncPlans: @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_method_create(self, async_client: AsyncWhop) -> None: - plan = await async_client.plans.create( - product_id="product_id", - ) + plan = await async_client.plans.create() assert_matches_type(Plan, plan, 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: plan = await async_client.plans.create( - product_id="product_id", account_id="account_id", adaptive_pricing_enabled=True, billing_period=0, @@ -450,6 +441,7 @@ async def test_method_create_with_all_params(self, async_client: AsyncWhop) -> N "include_platform_defaults": True, }, plan_type="plan_type", + product_id="product_id", release_method="release_method", renewal_price=0, split_pay_required_payments=0, @@ -465,9 +457,7 @@ async def test_method_create_with_all_params(self, async_client: AsyncWhop) -> N @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.plans.with_raw_response.create( - product_id="product_id", - ) + response = await async_client.plans.with_raw_response.create() assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -477,9 +467,7 @@ async def test_raw_response_create(self, async_client: AsyncWhop) -> None: @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.plans.with_streaming_response.create( - product_id="product_id", - ) as response: + async with async_client.plans.with_streaming_response.create() as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" From 981e1ab4db495515b09ed09cc51c2df528da82c8 Mon Sep 17 00:00:00 2001 From: stlc-bot Date: Fri, 12 Jun 2026 04:21:49 +0000 Subject: [PATCH 033/109] Public hosted deposit page so anyone can fund a business by link Stainless-Generated-From: a27e8e38b6a46d2d58ca82fc0cc46fc925f3d329 --- src/whop_sdk/resources/deposits.py | 6 +- src/whop_sdk/types/deposit_create_response.py | 56 ++++++++----------- 2 files changed, 27 insertions(+), 35 deletions(-) diff --git a/src/whop_sdk/resources/deposits.py b/src/whop_sdk/resources/deposits.py index 6e7a31a9..82ceb67d 100644 --- a/src/whop_sdk/resources/deposits.py +++ b/src/whop_sdk/resources/deposits.py @@ -60,7 +60,8 @@ def create( ) -> DepositCreateResponse: """ Resolves a deposit destination and returns the on-chain addresses that can fund - it. + it. No authentication is required; any business can be resolved by its account + ID. Args: destination: Destination account ID or wallet address. Object form is supported for @@ -173,7 +174,8 @@ async def create( ) -> DepositCreateResponse: """ Resolves a deposit destination and returns the on-chain addresses that can fund - it. + it. No authentication is required; any business can be resolved by its account + ID. Args: destination: Destination account ID or wallet address. Object form is supported for diff --git a/src/whop_sdk/types/deposit_create_response.py b/src/whop_sdk/types/deposit_create_response.py index f630bc3a..a59e33b9 100644 --- a/src/whop_sdk/types/deposit_create_response.py +++ b/src/whop_sdk/types/deposit_create_response.py @@ -5,10 +5,10 @@ from .._models import BaseModel -__all__ = ["DepositCreateResponse", "Bank", "BankInstruction", "BankMethod", "DepositAddress", "Destination"] +__all__ = ["DepositCreateResponse", "Methods", "MethodsBank", "MethodsBankCurrency", "MethodsCrypto"] -class BankInstruction(BaseModel): +class MethodsBankCurrency(BaseModel): account_number: Optional[str] = None currency: str @@ -19,60 +19,50 @@ class BankInstruction(BaseModel): deposit_reference: Optional[str] = None - routing_number: Optional[str] = None - - -class BankMethod(BaseModel): - currency: str - - rail: str + rails: List[str] + """Active deposit rails for this currency, e.g. ach, wire, sepa.""" + routing_number: Optional[str] = None -class Bank(BaseModel): - instructions: Optional[List[BankInstruction]] = None - methods: List[BankMethod] +class MethodsBank(BaseModel): + """Bank deposit details. - onboarding_link: Optional[str] = None + Only present when bank deposits are active for the destination account. + """ - status: Literal[ - "not_started", - "pending_identification", - "pending_review", - "requires_signing", - "active", - "user_required", - "suspended", - ] + currencies: List[MethodsBankCurrency] -class DepositAddress(BaseModel): +class MethodsCrypto(BaseModel): evm: str solana: str + wallet: str -class Destination(BaseModel): - address: str - currency: str +class Methods(BaseModel): + bank: Optional[MethodsBank] = None + """Bank deposit details. - network: str + Only present when bank deposits are active for the destination account. + """ - account_id: Optional[str] = None + crypto: MethodsCrypto class DepositCreateResponse(BaseModel): - bank: Optional[Bank] = None - - deposit_address: DepositAddress - - destination: Destination + account: Optional[str] = None + """Account ID of the destination owner. Null for raw wallet address destinations.""" hosted_url: Optional[str] = None + """URL of the hosted deposit page. Only present for business destinations.""" metadata: Dict[str, object] + methods: Methods + object: Literal["deposit"] amount: Optional[str] = None From 78db1d73f34e0621126fb72e1a52ef280ac94861 Mon Sep 17 00:00:00 2001 From: stlc-bot Date: Fri, 12 Jun 2026 06:22:08 +0000 Subject: [PATCH 034/109] Rename POST /deposits response field account to account_id Stainless-Generated-From: 7d3b14739c026384fbe922b2ca8a540a87ade9a3 --- src/whop_sdk/types/deposit_create_response.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/whop_sdk/types/deposit_create_response.py b/src/whop_sdk/types/deposit_create_response.py index a59e33b9..d079f82f 100644 --- a/src/whop_sdk/types/deposit_create_response.py +++ b/src/whop_sdk/types/deposit_create_response.py @@ -53,7 +53,7 @@ class Methods(BaseModel): class DepositCreateResponse(BaseModel): - account: Optional[str] = None + account_id: Optional[str] = None """Account ID of the destination owner. Null for raw wallet address destinations.""" hosted_url: Optional[str] = None From a16c1321aa28ebfb2e765803a6dbd0e49f43f274 Mon Sep 17 00:00:00 2001 From: stlc-bot Date: Fri, 12 Jun 2026 16:34:26 +0000 Subject: [PATCH 035/109] Return cashback on card transactions in the financial activity API Stainless-Generated-From: e0928b83f2680404951730552abcf778d19e023a --- src/whop_sdk/types/financial_activity_list_response.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/whop_sdk/types/financial_activity_list_response.py b/src/whop_sdk/types/financial_activity_list_response.py index 628d7671..f9940d02 100644 --- a/src/whop_sdk/types/financial_activity_list_response.py +++ b/src/whop_sdk/types/financial_activity_list_response.py @@ -154,6 +154,13 @@ class DataResourceUnionMember4(BaseModel): class DataResourceUnionMember5(BaseModel): id: str + cashback_usd: Optional[str] = None + """Cashback earned on this transaction as a USD decimal string. + + Zero for declined or ineligible transactions; null when cashback has not been + computed yet. + """ + merchant_category: Optional[str] = None merchant_icon_url: Optional[str] = None From 8551722a18af1cee0f0b23b1928456822a2b95ae Mon Sep 17 00:00:00 2001 From: stlc-bot Date: Fri, 12 Jun 2026 19:04:40 +0000 Subject: [PATCH 036/109] feat(ads): sort by any metric company-wide and let internal users see personal ad stats Stainless-Generated-From: 262d688860fe909012103e4d3db4e63fa91a95ba --- src/whop_sdk/resources/ad_campaigns.py | 61 +++++++++++++++++- src/whop_sdk/resources/ad_groups.py | 62 ++++++++++++++++++- src/whop_sdk/resources/ads.py | 48 ++++++++++++-- src/whop_sdk/types/ad_campaign_list_params.py | 28 ++++++++- src/whop_sdk/types/ad_group_list_params.py | 28 ++++++++- src/whop_sdk/types/ad_list_params.py | 25 +++++++- tests/api_resources/test_ad_campaigns.py | 4 ++ tests/api_resources/test_ad_groups.py | 4 ++ 8 files changed, 247 insertions(+), 13 deletions(-) diff --git a/src/whop_sdk/resources/ad_campaigns.py b/src/whop_sdk/resources/ad_campaigns.py index 3d26a1c9..a847090d 100644 --- a/src/whop_sdk/resources/ad_campaigns.py +++ b/src/whop_sdk/resources/ad_campaigns.py @@ -4,10 +4,16 @@ from typing import Union, Optional from datetime import datetime +from typing_extensions import Literal import httpx -from ..types import AdCampaignStatus, ad_campaign_list_params, ad_campaign_update_params, ad_campaign_retrieve_params +from ..types import ( + AdCampaignStatus, + ad_campaign_list_params, + ad_campaign_update_params, + ad_campaign_retrieve_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 @@ -21,6 +27,7 @@ from ..pagination import SyncCursorPage, AsyncCursorPage from .._base_client import AsyncPaginator, make_request_options from ..types.ad_campaign import AdCampaign +from ..types.shared.direction import Direction from ..types.ad_campaign_status import AdCampaignStatus from ..types.ad_campaign_list_response import AdCampaignListResponse @@ -154,8 +161,27 @@ def list( company_id: Optional[str] | Omit = omit, created_after: Union[str, datetime, None] | Omit = omit, created_before: Union[str, datetime, None] | Omit = omit, + direction: Optional[Direction] | Omit = omit, first: Optional[int] | Omit = omit, last: Optional[int] | Omit = omit, + order: Optional[ + Literal[ + "created_at", + "spend", + "impressions", + "clicks", + "reach", + "unique_clicks", + "results", + "click_through_rate", + "cost_per_click", + "cost_per_mille", + "cost_per_result", + "frequency", + "return_on_ad_spend", + ] + ] + | Omit = omit, query: Optional[str] | Omit = omit, stats_from: Union[str, datetime, None] | Omit = omit, stats_to: Union[str, datetime, None] | Omit = omit, @@ -186,10 +212,15 @@ def list( created_before: Only return ad campaigns created before this timestamp. + direction: The direction of the sort. + first: Returns the first _n_ elements from the list. last: Returns the last _n_ elements from the list. + order: The fields the ads dashboard lists (campaigns, ad sets) can be ordered by. Stat + columns are computed over the provided stats date range. + query: Case-insensitive substring match against the campaign title or ID. stats_from: Inclusive start of the window for each campaign's metric fields (spend, @@ -223,8 +254,10 @@ def list( "company_id": company_id, "created_after": created_after, "created_before": created_before, + "direction": direction, "first": first, "last": last, + "order": order, "query": query, "stats_from": stats_from, "stats_to": stats_to, @@ -438,8 +471,27 @@ def list( company_id: Optional[str] | Omit = omit, created_after: Union[str, datetime, None] | Omit = omit, created_before: Union[str, datetime, None] | Omit = omit, + direction: Optional[Direction] | Omit = omit, first: Optional[int] | Omit = omit, last: Optional[int] | Omit = omit, + order: Optional[ + Literal[ + "created_at", + "spend", + "impressions", + "clicks", + "reach", + "unique_clicks", + "results", + "click_through_rate", + "cost_per_click", + "cost_per_mille", + "cost_per_result", + "frequency", + "return_on_ad_spend", + ] + ] + | Omit = omit, query: Optional[str] | Omit = omit, stats_from: Union[str, datetime, None] | Omit = omit, stats_to: Union[str, datetime, None] | Omit = omit, @@ -470,10 +522,15 @@ def list( created_before: Only return ad campaigns created before this timestamp. + direction: The direction of the sort. + first: Returns the first _n_ elements from the list. last: Returns the last _n_ elements from the list. + order: The fields the ads dashboard lists (campaigns, ad sets) can be ordered by. Stat + columns are computed over the provided stats date range. + query: Case-insensitive substring match against the campaign title or ID. stats_from: Inclusive start of the window for each campaign's metric fields (spend, @@ -507,8 +564,10 @@ def list( "company_id": company_id, "created_after": created_after, "created_before": created_before, + "direction": direction, "first": first, "last": last, + "order": order, "query": query, "stats_from": stats_from, "stats_to": stats_to, diff --git a/src/whop_sdk/resources/ad_groups.py b/src/whop_sdk/resources/ad_groups.py index ce4459d5..faaf3860 100644 --- a/src/whop_sdk/resources/ad_groups.py +++ b/src/whop_sdk/resources/ad_groups.py @@ -4,10 +4,17 @@ from typing import Union, Optional from datetime import datetime +from typing_extensions import Literal import httpx -from ..types import AdBudgetType, AdGroupStatus, ad_group_list_params, ad_group_update_params, ad_group_retrieve_params +from ..types import ( + AdBudgetType, + AdGroupStatus, + ad_group_list_params, + ad_group_update_params, + ad_group_retrieve_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 @@ -23,6 +30,7 @@ from ..types.ad_group import AdGroup from ..types.ad_budget_type import AdBudgetType from ..types.ad_group_status import AdGroupStatus +from ..types.shared.direction import Direction from ..types.ad_group_list_response import AdGroupListResponse from ..types.ad_group_delete_response import AdGroupDeleteResponse @@ -192,8 +200,27 @@ def list( company_id: Optional[str] | Omit = omit, created_after: Union[str, datetime, None] | Omit = omit, created_before: Union[str, datetime, None] | Omit = omit, + direction: Optional[Direction] | Omit = omit, first: Optional[int] | Omit = omit, last: Optional[int] | Omit = omit, + order: Optional[ + Literal[ + "created_at", + "spend", + "impressions", + "clicks", + "reach", + "unique_clicks", + "results", + "click_through_rate", + "cost_per_click", + "cost_per_mille", + "cost_per_result", + "frequency", + "return_on_ad_spend", + ] + ] + | Omit = omit, query: Optional[str] | Omit = omit, stats_from: Union[str, datetime, None] | Omit = omit, stats_to: Union[str, datetime, None] | Omit = omit, @@ -231,10 +258,15 @@ def list( created_before: Only return ad groups created before this timestamp. + direction: The direction of the sort. + first: Returns the first _n_ elements from the list. last: Returns the last _n_ elements from the list. + order: The fields the ads dashboard lists (campaigns, ad sets) can be ordered by. Stat + columns are computed over the provided stats date range. + query: Case-insensitive substring match against the ad group name or ID. stats_from: Inclusive start of the window for each ad group's metric fields (spend, @@ -271,8 +303,10 @@ def list( "company_id": company_id, "created_after": created_after, "created_before": created_before, + "direction": direction, "first": first, "last": last, + "order": order, "query": query, "stats_from": stats_from, "stats_to": stats_to, @@ -561,8 +595,27 @@ def list( company_id: Optional[str] | Omit = omit, created_after: Union[str, datetime, None] | Omit = omit, created_before: Union[str, datetime, None] | Omit = omit, + direction: Optional[Direction] | Omit = omit, first: Optional[int] | Omit = omit, last: Optional[int] | Omit = omit, + order: Optional[ + Literal[ + "created_at", + "spend", + "impressions", + "clicks", + "reach", + "unique_clicks", + "results", + "click_through_rate", + "cost_per_click", + "cost_per_mille", + "cost_per_result", + "frequency", + "return_on_ad_spend", + ] + ] + | Omit = omit, query: Optional[str] | Omit = omit, stats_from: Union[str, datetime, None] | Omit = omit, stats_to: Union[str, datetime, None] | Omit = omit, @@ -600,10 +653,15 @@ def list( created_before: Only return ad groups created before this timestamp. + direction: The direction of the sort. + first: Returns the first _n_ elements from the list. last: Returns the last _n_ elements from the list. + order: The fields the ads dashboard lists (campaigns, ad sets) can be ordered by. Stat + columns are computed over the provided stats date range. + query: Case-insensitive substring match against the ad group name or ID. stats_from: Inclusive start of the window for each ad group's metric fields (spend, @@ -640,8 +698,10 @@ def list( "company_id": company_id, "created_after": created_after, "created_before": created_before, + "direction": direction, "first": first, "last": last, + "order": order, "query": query, "stats_from": stats_from, "stats_to": stats_to, diff --git a/src/whop_sdk/resources/ads.py b/src/whop_sdk/resources/ads.py index 97617892..7304fe6d 100644 --- a/src/whop_sdk/resources/ads.py +++ b/src/whop_sdk/resources/ads.py @@ -122,7 +122,24 @@ def list( direction: Optional[Direction] | Omit = omit, first: Optional[int] | Omit = omit, last: Optional[int] | Omit = omit, - order: Optional[Literal["created_at", "spend", "return_on_ad_spend"]] | Omit = omit, + order: Optional[ + Literal[ + "created_at", + "spend", + "impressions", + "clicks", + "reach", + "unique_clicks", + "results", + "click_through_rate", + "cost_per_click", + "cost_per_mille", + "cost_per_result", + "frequency", + "return_on_ad_spend", + ] + ] + | Omit = omit, order_by: Optional[Literal["spend", "return_on_ad_spend", "roas"]] | Omit = omit, order_direction: Optional[Direction] | Omit = omit, query: Optional[str] | Omit = omit, @@ -175,9 +192,10 @@ def list( last: Returns the last _n_ elements from the list. - order: The fields ad resources can be ordered by. + order: The fields the ads dashboard lists (campaigns, ad sets) can be ordered by. Stat + columns are computed over the provided stats date range. - order_by: Columns that the listAds query can sort by. Deprecated — use AdOrder. + order_by: Columns that the listAds query can sort by. Deprecated — use AdStatOrder. order_direction: The direction of the sort. @@ -407,7 +425,24 @@ def list( direction: Optional[Direction] | Omit = omit, first: Optional[int] | Omit = omit, last: Optional[int] | Omit = omit, - order: Optional[Literal["created_at", "spend", "return_on_ad_spend"]] | Omit = omit, + order: Optional[ + Literal[ + "created_at", + "spend", + "impressions", + "clicks", + "reach", + "unique_clicks", + "results", + "click_through_rate", + "cost_per_click", + "cost_per_mille", + "cost_per_result", + "frequency", + "return_on_ad_spend", + ] + ] + | Omit = omit, order_by: Optional[Literal["spend", "return_on_ad_spend", "roas"]] | Omit = omit, order_direction: Optional[Direction] | Omit = omit, query: Optional[str] | Omit = omit, @@ -460,9 +495,10 @@ def list( last: Returns the last _n_ elements from the list. - order: The fields ad resources can be ordered by. + order: The fields the ads dashboard lists (campaigns, ad sets) can be ordered by. Stat + columns are computed over the provided stats date range. - order_by: Columns that the listAds query can sort by. Deprecated — use AdOrder. + order_by: Columns that the listAds query can sort by. Deprecated — use AdStatOrder. order_direction: The direction of the sort. diff --git a/src/whop_sdk/types/ad_campaign_list_params.py b/src/whop_sdk/types/ad_campaign_list_params.py index 5c3dce5c..526ecf84 100644 --- a/src/whop_sdk/types/ad_campaign_list_params.py +++ b/src/whop_sdk/types/ad_campaign_list_params.py @@ -4,9 +4,10 @@ from typing import Union, Optional from datetime import datetime -from typing_extensions import Annotated, TypedDict +from typing_extensions import Literal, Annotated, TypedDict from .._utils import PropertyInfo +from .shared.direction import Direction from .ad_campaign_status import AdCampaignStatus __all__ = ["AdCampaignListParams"] @@ -28,12 +29,37 @@ class AdCampaignListParams(TypedDict, total=False): created_before: Annotated[Union[str, datetime, None], PropertyInfo(format="iso8601")] """Only return ad campaigns created before this timestamp.""" + direction: Optional[Direction] + """The direction of the sort.""" + first: Optional[int] """Returns the first _n_ elements from the list.""" last: Optional[int] """Returns the last _n_ elements from the list.""" + order: Optional[ + Literal[ + "created_at", + "spend", + "impressions", + "clicks", + "reach", + "unique_clicks", + "results", + "click_through_rate", + "cost_per_click", + "cost_per_mille", + "cost_per_result", + "frequency", + "return_on_ad_spend", + ] + ] + """The fields the ads dashboard lists (campaigns, ad sets) can be ordered by. + + Stat columns are computed over the provided stats date range. + """ + query: Optional[str] """Case-insensitive substring match against the campaign title or ID.""" diff --git a/src/whop_sdk/types/ad_group_list_params.py b/src/whop_sdk/types/ad_group_list_params.py index 5e92b127..9ddc929c 100644 --- a/src/whop_sdk/types/ad_group_list_params.py +++ b/src/whop_sdk/types/ad_group_list_params.py @@ -4,11 +4,12 @@ from typing import Union, Optional from datetime import datetime -from typing_extensions import Annotated, TypedDict +from typing_extensions import Literal, Annotated, TypedDict from .._types import SequenceNotStr from .._utils import PropertyInfo from .ad_group_status import AdGroupStatus +from .shared.direction import Direction __all__ = ["AdGroupListParams"] @@ -41,12 +42,37 @@ class AdGroupListParams(TypedDict, total=False): created_before: Annotated[Union[str, datetime, None], PropertyInfo(format="iso8601")] """Only return ad groups created before this timestamp.""" + direction: Optional[Direction] + """The direction of the sort.""" + first: Optional[int] """Returns the first _n_ elements from the list.""" last: Optional[int] """Returns the last _n_ elements from the list.""" + order: Optional[ + Literal[ + "created_at", + "spend", + "impressions", + "clicks", + "reach", + "unique_clicks", + "results", + "click_through_rate", + "cost_per_click", + "cost_per_mille", + "cost_per_result", + "frequency", + "return_on_ad_spend", + ] + ] + """The fields the ads dashboard lists (campaigns, ad sets) can be ordered by. + + Stat columns are computed over the provided stats date range. + """ + query: Optional[str] """Case-insensitive substring match against the ad group name or ID.""" diff --git a/src/whop_sdk/types/ad_list_params.py b/src/whop_sdk/types/ad_list_params.py index f436a972..7fbe33a3 100644 --- a/src/whop_sdk/types/ad_list_params.py +++ b/src/whop_sdk/types/ad_list_params.py @@ -69,11 +69,30 @@ class AdListParams(TypedDict, total=False): last: Optional[int] """Returns the last _n_ elements from the list.""" - order: Optional[Literal["created_at", "spend", "return_on_ad_spend"]] - """The fields ad resources can be ordered by.""" + order: Optional[ + Literal[ + "created_at", + "spend", + "impressions", + "clicks", + "reach", + "unique_clicks", + "results", + "click_through_rate", + "cost_per_click", + "cost_per_mille", + "cost_per_result", + "frequency", + "return_on_ad_spend", + ] + ] + """The fields the ads dashboard lists (campaigns, ad sets) can be ordered by. + + Stat columns are computed over the provided stats date range. + """ order_by: Optional[Literal["spend", "return_on_ad_spend", "roas"]] - """Columns that the listAds query can sort by. Deprecated — use AdOrder.""" + """Columns that the listAds query can sort by. Deprecated — use AdStatOrder.""" order_direction: Optional[Direction] """The direction of the sort.""" diff --git a/tests/api_resources/test_ad_campaigns.py b/tests/api_resources/test_ad_campaigns.py index 655a33ef..f7323e0b 100644 --- a/tests/api_resources/test_ad_campaigns.py +++ b/tests/api_resources/test_ad_campaigns.py @@ -140,8 +140,10 @@ def test_method_list_with_all_params(self, client: Whop) -> None: company_id="biz_xxxxxxxxxxxxxx", created_after=parse_datetime("2023-12-01T05:00:00.401Z"), created_before=parse_datetime("2023-12-01T05:00:00.401Z"), + direction="asc", first=42, last=42, + order="created_at", query="query", stats_from=parse_datetime("2023-12-01T05:00:00.401Z"), stats_to=parse_datetime("2023-12-01T05:00:00.401Z"), @@ -379,8 +381,10 @@ async def test_method_list_with_all_params(self, async_client: AsyncWhop) -> Non company_id="biz_xxxxxxxxxxxxxx", created_after=parse_datetime("2023-12-01T05:00:00.401Z"), created_before=parse_datetime("2023-12-01T05:00:00.401Z"), + direction="asc", first=42, last=42, + order="created_at", query="query", stats_from=parse_datetime("2023-12-01T05:00:00.401Z"), stats_to=parse_datetime("2023-12-01T05:00:00.401Z"), diff --git a/tests/api_resources/test_ad_groups.py b/tests/api_resources/test_ad_groups.py index 8f7fd044..4c6c1a19 100644 --- a/tests/api_resources/test_ad_groups.py +++ b/tests/api_resources/test_ad_groups.py @@ -485,8 +485,10 @@ def test_method_list_with_all_params(self, client: Whop) -> None: company_id="biz_xxxxxxxxxxxxxx", created_after=parse_datetime("2023-12-01T05:00:00.401Z"), created_before=parse_datetime("2023-12-01T05:00:00.401Z"), + direction="asc", first=42, last=42, + order="created_at", query="query", stats_from=parse_datetime("2023-12-01T05:00:00.401Z"), stats_to=parse_datetime("2023-12-01T05:00:00.401Z"), @@ -1110,8 +1112,10 @@ async def test_method_list_with_all_params(self, async_client: AsyncWhop) -> Non company_id="biz_xxxxxxxxxxxxxx", created_after=parse_datetime("2023-12-01T05:00:00.401Z"), created_before=parse_datetime("2023-12-01T05:00:00.401Z"), + direction="asc", first=42, last=42, + order="created_at", query="query", stats_from=parse_datetime("2023-12-01T05:00:00.401Z"), stats_to=parse_datetime("2023-12-01T05:00:00.401Z"), From 2632eae0f14e1b9a31e90fd4e2591656874aec84 Mon Sep 17 00:00:00 2001 From: stlc-bot Date: Fri, 12 Jun 2026 20:17:31 +0000 Subject: [PATCH 037/109] Centralize ads writes through platform adapters Stainless-Generated-From: e5ae6fc25e6080468fb9fc114ca8d7f783126a8c --- src/whop_sdk/resources/ad_campaigns.py | 6 +- src/whop_sdk/resources/ad_groups.py | 75 +- src/whop_sdk/types/ad_group_update_params.py | 1218 +----------------- tests/api_resources/test_ad_groups.py | 680 ---------- 4 files changed, 24 insertions(+), 1955 deletions(-) diff --git a/src/whop_sdk/resources/ad_campaigns.py b/src/whop_sdk/resources/ad_campaigns.py index a847090d..eb6f5d65 100644 --- a/src/whop_sdk/resources/ad_campaigns.py +++ b/src/whop_sdk/resources/ad_campaigns.py @@ -124,7 +124,8 @@ def update( timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> AdCampaign: """ - Updates an ad campaign synchronously. + Updates an ad campaign synchronously and returns it immediately (local-first). + The platform push runs in the background; any errors surface on the dashboard. Required permissions: @@ -434,7 +435,8 @@ async def update( timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> AdCampaign: """ - Updates an ad campaign synchronously. + Updates an ad campaign synchronously and returns it immediately (local-first). + The platform push runs in the background; any errors surface on the dashboard. Required permissions: diff --git a/src/whop_sdk/resources/ad_groups.py b/src/whop_sdk/resources/ad_groups.py index faaf3860..851fba8c 100644 --- a/src/whop_sdk/resources/ad_groups.py +++ b/src/whop_sdk/resources/ad_groups.py @@ -8,13 +8,7 @@ import httpx -from ..types import ( - AdBudgetType, - AdGroupStatus, - ad_group_list_params, - ad_group_update_params, - ad_group_retrieve_params, -) +from ..types import AdGroupStatus, ad_group_list_params, ad_group_update_params, ad_group_retrieve_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 @@ -28,7 +22,6 @@ from ..pagination import SyncCursorPage, AsyncCursorPage from .._base_client import AsyncPaginator, make_request_options from ..types.ad_group import AdGroup -from ..types.ad_budget_type import AdBudgetType from ..types.ad_group_status import AdGroupStatus from ..types.shared.direction import Direction from ..types.ad_group_list_response import AdGroupListResponse @@ -119,12 +112,6 @@ def update( id: str, *, budget: Optional[float] | Omit = omit, - budget_type: Optional[AdBudgetType] | 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[AdGroupStatus] | 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. @@ -133,8 +120,10 @@ def update( extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> AdGroup: - """ - Updates an existing ad group. + """Updates an ad group synchronously and returns it immediately (local-first). + + The + platform push runs in the background; any errors surface on the dashboard. Required permissions: @@ -142,19 +131,8 @@ def 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. + budget: Budget amount in dollars. The interpretation (daily or lifetime) follows the ad + group's existing budget type. title: Human-readable ad group title. @@ -173,12 +151,6 @@ def update( body=maybe_transform( { "budget": budget, - "budget_type": budget_type, - "config": config, - "daily_budget": daily_budget, - "name": name, - "platform_config": platform_config, - "status": status, "title": title, }, ad_group_update_params.AdGroupUpdateParams, @@ -514,12 +486,6 @@ async def update( id: str, *, budget: Optional[float] | Omit = omit, - budget_type: Optional[AdBudgetType] | 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[AdGroupStatus] | 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. @@ -528,8 +494,10 @@ async def update( extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> AdGroup: - """ - Updates an existing ad group. + """Updates an ad group synchronously and returns it immediately (local-first). + + The + platform push runs in the background; any errors surface on the dashboard. Required permissions: @@ -537,19 +505,8 @@ async def 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. + budget: Budget amount in dollars. The interpretation (daily or lifetime) follows the ad + group's existing budget type. title: Human-readable ad group title. @@ -568,12 +525,6 @@ async def update( 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, "title": title, }, ad_group_update_params.AdGroupUpdateParams, diff --git a/src/whop_sdk/types/ad_group_update_params.py b/src/whop_sdk/types/ad_group_update_params.py index 8b5b49ab..649dac4f 100644 --- a/src/whop_sdk/types/ad_group_update_params.py +++ b/src/whop_sdk/types/ad_group_update_params.py @@ -2,1223 +2,19 @@ from __future__ import annotations -from typing import Dict, List, Iterable, Optional -from typing_extensions import Literal, Required, TypedDict +from typing import Optional +from typing_extensions import TypedDict -from .._types import SequenceNotStr -from .ad_budget_type import AdBudgetType -from .ad_group_status import AdGroupStatus - -__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", -] +__all__ = ["AdGroupUpdateParams"] class AdGroupUpdateParams(TypedDict, total=False): budget: Optional[float] - """Budget amount in dollars.""" - - budget_type: Optional[AdBudgetType] - """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.""" + """Budget amount in dollars. - name: Optional[str] - """Human-readable ad group name.""" - - platform_config: Optional[PlatformConfig] - """Platform-specific ad group configuration.""" - - status: Optional[AdGroupStatus] - """The status of an external ad group.""" + The interpretation (daily or lifetime) follows the ad group's existing budget + type. + """ title: Optional[str] """Human-readable ad group title.""" - - -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", - "LEAD_FORM_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", "website_and_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/tests/api_resources/test_ad_groups.py b/tests/api_resources/test_ad_groups.py index 4c6c1a19..275e8edf 100644 --- a/tests/api_resources/test_ad_groups.py +++ b/tests/api_resources/test_ad_groups.py @@ -89,346 +89,6 @@ 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", title="title", ) assert_matches_type(AdGroup, ad_group, path=["response"]) @@ -716,346 +376,6 @@ async def test_method_update_with_all_params(self, async_client: AsyncWhop) -> N 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", title="title", ) assert_matches_type(AdGroup, ad_group, path=["response"]) From f779c92dd3bfbf8e9adb00d2928db32d60213351 Mon Sep 17 00:00:00 2001 From: stlc-bot Date: Fri, 12 Jun 2026 21:41:07 +0000 Subject: [PATCH 038/109] Flatten the account/user balance: top-level total_usd + one balances list (crypto + fiat) Stainless-Generated-From: f3d266e4db62488a9043f11a1bc635eb4495761d --- src/whop_sdk/types/account.py | 59 +++++++++++++++++------------------ src/whop_sdk/types/user.py | 59 +++++++++++++++++------------------ 2 files changed, 56 insertions(+), 62 deletions(-) diff --git a/src/whop_sdk/types/account.py b/src/whop_sdk/types/account.py index 7ff5abaf..dafcc3b7 100644 --- a/src/whop_sdk/types/account.py +++ b/src/whop_sdk/types/account.py @@ -6,57 +6,46 @@ from .account_wallet import AccountWallet from .account_social_link import AccountSocialLink -__all__ = ["Account", "Balance", "BalanceToken"] +__all__ = ["Account", "Balance"] -class BalanceToken(BaseModel): - """The account's per-token holdings""" +class Balance(BaseModel): + """The account's holdings (crypto and fiat), each with its USD value. + + Empty when total_usd is null (not computed) + """ balance: str - """The token amount in native units, as a decimal string""" + """The total amount held in native units, as a decimal string""" + + breakdown: object + """ + The holding split into available, pending, and reserve amounts (native-unit + decimal strings). On-chain crypto is entirely available; good_funds and fiat + cash can have pending/reserve portions + """ icon_url: Optional[str] = None - """The URL of the token icon, when available""" + """The URL of the holding's icon, when available""" name: str - """The token's display name""" + """The holding's display name""" price_usd: Optional[float] = None - """The USD price per token, or null when no exchange rate is available""" + """The USD price per unit, or null when no exchange rate is available""" symbol: str - """The token's display symbol, e.g. USDT or cbBTC""" - - token_address: Optional[str] = None - """The token contract address, when the holding maps to a single contract""" + """The holding's display symbol, e.g. USDT, cbBTC, or EUR""" value_usd: Optional[str] = None - """The USD value of the holding, or null when no exchange rate is available""" - - -class Balance(BaseModel): - """The account's balance by token. - - Only computed on single-account reads (retrieve and me); null on list responses, on writes, when the caller's token lacks the balance-read permission for the account, and when the balance source is unavailable - """ - - tokens: List[BalanceToken] - - total_usd: str - """The total USD value across all tokens with a known exchange rate""" + """The total USD value of the holding, or null when no exchange rate is available""" class Account(BaseModel): id: str """The ID of the account, which will look like biz\\__******\\********""" - balance: Optional[Balance] = None - """The account's balance by token. - - Only computed on single-account reads (retrieve and me); null on list responses, - on writes, when the caller's token lacks the balance-read permission for the - account, and when the balance source is unavailable - """ + balances: List[Balance] banner_image_url: Optional[str] = None """The URL of the account banner image""" @@ -143,6 +132,14 @@ class Account(BaseModel): title: str """The display name of the account""" + total_usd: Optional[str] = None + """Total USD value across all balances with a known exchange rate. + + Only computed on single-account reads (retrieve and me); null (with an empty + balances array) on list responses, on writes, when the caller's token lacks the + balance-read permission, and when the balance source is unavailable + """ + use_logo_as_opengraph_image_fallback: bool """Whether the account uses its logo as the fallback Open Graph image""" diff --git a/src/whop_sdk/types/user.py b/src/whop_sdk/types/user.py index a1078fd6..cac11115 100644 --- a/src/whop_sdk/types/user.py +++ b/src/whop_sdk/types/user.py @@ -4,57 +4,46 @@ from .._models import BaseModel -__all__ = ["User", "Balance", "BalanceToken"] +__all__ = ["User", "Balance"] -class BalanceToken(BaseModel): - """The account's per-token holdings""" +class Balance(BaseModel): + """The user's holdings (crypto and fiat), each with its USD value. + + Empty when total_usd is null (not computed) + """ balance: str - """The token amount in native units, as a decimal string""" + """The total amount held in native units, as a decimal string""" + + breakdown: object + """ + The holding split into available, pending, and reserve amounts (native-unit + decimal strings). On-chain crypto is entirely available; good_funds and fiat + cash can have pending/reserve portions + """ icon_url: Optional[str] = None - """The URL of the token icon, when available""" + """The URL of the holding's icon, when available""" name: str - """The token's display name""" + """The holding's display name""" price_usd: Optional[float] = None - """The USD price per token, or null when no exchange rate is available""" + """The USD price per unit, or null when no exchange rate is available""" symbol: str - """The token's display symbol, e.g. USDT or cbBTC""" - - token_address: Optional[str] = None - """The token contract address, when the holding maps to a single contract""" + """The holding's display symbol, e.g. USDT, cbBTC, or EUR""" value_usd: Optional[str] = None - """The USD value of the holding, or null when no exchange rate is available""" - - -class Balance(BaseModel): - """The user's balance by token. - - Only computed on the self-view (GET /users/me) for callers with the company:balance:read scope; null otherwise and when the balance source is unavailable - """ - - tokens: List[BalanceToken] - - total_usd: str - """The total USD value across all tokens with a known exchange rate""" + """The total USD value of the holding, or null when no exchange rate is available""" class User(BaseModel): id: str """The ID of the user, which will look like user\\__******\\********""" - balance: Optional[Balance] = None - """The user's balance by token. - - Only computed on the self-view (GET /users/me) for callers with the - company:balance:read scope; null otherwise and when the balance source is - unavailable - """ + balances: List[Balance] bio: Optional[str] = None """The user's biography""" @@ -68,5 +57,13 @@ class User(BaseModel): profile_picture: Optional[object] = None """The user's profile picture, an object with a url""" + total_usd: Optional[str] = None + """Total USD value across all balances with a known exchange rate. + + Only computed on the self-view (GET /users/me) for callers with the balance-read + scope; null (with an empty balances array) otherwise and when the balance source + is unavailable + """ + username: str """The user's unique username""" From b564ee0884bb17dccf3eef5e1f90ef529bbe28f9 Mon Sep 17 00:00:00 2001 From: stlc-bot Date: Sat, 13 Jun 2026 00:09:34 +0000 Subject: [PATCH 039/109] bring hidden product v1 endpoint Stainless-Generated-From: 567f568e9f0f3ebf44e4c9466203fab7890cc49a --- src/whop_sdk/resources/products.py | 459 +++++------------- src/whop_sdk/types/product_create_params.py | 141 +----- src/whop_sdk/types/product_list_params.py | 45 +- src/whop_sdk/types/product_update_params.py | 105 +--- src/whop_sdk/types/shared_params/__init__.py | 3 - .../types/shared_params/access_pass_type.py | 9 - .../types/shared_params/custom_cta.py | 23 - .../types/shared_params/visibility_filter.py | 11 - tests/api_resources/test_products.py | 211 +++----- 9 files changed, 227 insertions(+), 780 deletions(-) delete mode 100644 src/whop_sdk/types/shared_params/access_pass_type.py delete mode 100644 src/whop_sdk/types/shared_params/custom_cta.py delete mode 100644 src/whop_sdk/types/shared_params/visibility_filter.py diff --git a/src/whop_sdk/resources/products.py b/src/whop_sdk/resources/products.py index 505de8e1..5bb4f3b4 100644 --- a/src/whop_sdk/resources/products.py +++ b/src/whop_sdk/resources/products.py @@ -2,8 +2,7 @@ from __future__ import annotations -from typing import Dict, List, Union, Iterable, Optional -from datetime import datetime +from typing import Optional from typing_extensions import Literal import httpx @@ -22,14 +21,8 @@ from ..pagination import SyncCursorPage, AsyncCursorPage from .._base_client import AsyncPaginator, make_request_options from ..types.shared.product import Product -from ..types.shared.direction import Direction -from ..types.shared.custom_cta import CustomCta -from ..types.shared.visibility import Visibility from ..types.product_delete_response import ProductDeleteResponse -from ..types.shared.access_pass_type import AccessPassType from ..types.shared.product_list_item import ProductListItem -from ..types.shared.visibility_filter import VisibilityFilter -from ..types.shared.global_affiliate_status import GlobalAffiliateStatus __all__ = ["ProductsResource", "AsyncProductsResource"] @@ -59,26 +52,23 @@ def with_streaming_response(self) -> ProductsResourceWithStreamingResponse: def create( self, *, - company_id: str, title: str, collect_shipping_address: Optional[bool] | Omit = omit, - custom_cta: Optional[CustomCta] | Omit = omit, + company_id: str | Omit = omit, + custom_cta: Optional[str] | Omit = omit, custom_cta_url: Optional[str] | Omit = omit, custom_statement_descriptor: Optional[str] | Omit = omit, description: Optional[str] | Omit = omit, - experience_ids: Optional[SequenceNotStr[str]] | Omit = omit, global_affiliate_percentage: Optional[float] | Omit = omit, - global_affiliate_status: Optional[GlobalAffiliateStatus] | Omit = omit, + global_affiliate_status: str | Omit = omit, headline: Optional[str] | Omit = omit, member_affiliate_percentage: Optional[float] | Omit = omit, - member_affiliate_status: Optional[GlobalAffiliateStatus] | Omit = omit, - metadata: Optional[Dict[str, object]] | Omit = omit, - plan_options: Optional[product_create_params.PlanOptions] | Omit = omit, + member_affiliate_status: str | Omit = omit, + metadata: Optional[object] | Omit = omit, product_tax_code_id: Optional[str] | Omit = omit, redirect_purchase_url: Optional[str] | Omit = omit, route: Optional[str] | Omit = omit, - send_welcome_message: Optional[bool] | Omit = omit, - visibility: Optional[Visibility] | Omit = omit, + visibility: 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, @@ -86,64 +76,43 @@ def create( extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> Product: - """Create a new product for a company. - - The product serves as the top-level - container for plans and experiences. - - Required permissions: - - - `access_pass:create` - - `access_pass:basic:read` + """ + Creates a new product for a company. Args: - company_id: The unique identifier of the company to create this product for. - title: The display name of the product. Maximum 80 characters. - collect_shipping_address: Whether the checkout flow collects a shipping address from the customer. - - custom_cta: The different types of custom CTAs that can be selected. + collect_shipping_address: Whether to collect a shipping address at checkout. - custom_cta_url: A URL that the call-to-action button links to instead of the default checkout - flow. + company_id: The unique identifier of the company to create this product for. - custom_statement_descriptor: A custom text label that appears on the customer's bank statement. Must be 5-22 - characters, contain at least one letter, and not contain <, >, \\,, ', or " - characters. + custom_cta: The call-to-action button label. - description: A written description of the product displayed on its product page. + custom_cta_url: A URL the call-to-action button links to. - experience_ids: The unique identifiers of experiences to connect to this product. + custom_statement_descriptor: Custom bank statement descriptor. Must start with WHOP\\**. - global_affiliate_percentage: The commission rate as a percentage that affiliates earn through the global - affiliate program. + description: A written description displayed on the product page. - global_affiliate_status: The different statuses of the global affiliate program for a product. + global_affiliate_percentage: The commission rate affiliates earn. - headline: A short marketing headline displayed prominently on the product page. + global_affiliate_status: The enrollment status in the global affiliate program. - member_affiliate_percentage: The commission rate as a percentage that members earn through the member - affiliate program. + headline: A short marketing headline for the product page. - member_affiliate_status: The different statuses of the global affiliate program for a product. + member_affiliate_percentage: The commission rate members earn. - metadata: Custom key-value pairs to store on the product. Included in webhook payloads for - payment and membership events. Max 50 keys, 500 chars per key, 5000 chars per - value. + member_affiliate_status: The enrollment status in the member affiliate program. - plan_options: Configuration for an automatically generated plan to attach to this product. + metadata: Custom key-value pairs to store on the product. - product_tax_code_id: The unique identifier of the tax classification code to apply to this product. + product_tax_code_id: The unique identifier of the tax classification code. - redirect_purchase_url: A URL to redirect the customer to after completing a purchase. + redirect_purchase_url: A URL to redirect the customer to after purchase. route: The URL slug for the product's public link. - send_welcome_message: Whether to send an automated welcome message via support chat when a user joins - this product. Defaults to true. - - visibility: Visibility of a resource + visibility: Whether the product is visible to customers. extra_headers: Send extra headers @@ -157,25 +126,22 @@ def create( "/products", body=maybe_transform( { - "company_id": company_id, "title": title, "collect_shipping_address": collect_shipping_address, + "company_id": company_id, "custom_cta": custom_cta, "custom_cta_url": custom_cta_url, "custom_statement_descriptor": custom_statement_descriptor, "description": description, - "experience_ids": experience_ids, "global_affiliate_percentage": global_affiliate_percentage, "global_affiliate_status": global_affiliate_status, "headline": headline, "member_affiliate_percentage": member_affiliate_percentage, "member_affiliate_status": member_affiliate_status, "metadata": metadata, - "plan_options": plan_options, "product_tax_code_id": product_tax_code_id, "redirect_purchase_url": redirect_purchase_url, "route": route, - "send_welcome_message": send_welcome_message, "visibility": visibility, }, product_create_params.ProductCreateParams, @@ -197,12 +163,10 @@ def retrieve( extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> Product: - """ - Retrieves the details of an existing product. - - Required permissions: + """Retrieves the details of an existing product. - - `access_pass:basic:read` + This endpoint is publicly + accessible. Args: extra_headers: Send extra headers @@ -227,25 +191,11 @@ def update( self, id: str, *, - collect_shipping_address: Optional[bool] | Omit = omit, - custom_cta: Optional[CustomCta] | Omit = omit, - custom_cta_url: Optional[str] | Omit = omit, - custom_statement_descriptor: Optional[str] | Omit = omit, description: Optional[str] | Omit = omit, - gallery_images: Optional[Iterable[product_update_params.GalleryImage]] | Omit = omit, - global_affiliate_percentage: Optional[float] | Omit = omit, - global_affiliate_status: Optional[GlobalAffiliateStatus] | Omit = omit, headline: Optional[str] | Omit = omit, - member_affiliate_percentage: Optional[float] | Omit = omit, - member_affiliate_status: Optional[GlobalAffiliateStatus] | Omit = omit, - metadata: Optional[Dict[str, object]] | Omit = omit, - product_tax_code_id: Optional[str] | Omit = omit, - redirect_purchase_url: Optional[str] | Omit = omit, - route: Optional[str] | Omit = omit, - send_welcome_message: Optional[bool] | Omit = omit, - store_page_config: Optional[product_update_params.StorePageConfig] | Omit = omit, - title: Optional[str] | Omit = omit, - visibility: Optional[Visibility] | Omit = omit, + metadata: Optional[object] | Omit = omit, + title: str | Omit = omit, + visibility: 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, @@ -254,59 +204,18 @@ def update( timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> Product: """ - Update a product's title, description, visibility, and other settings. - - Required permissions: - - - `access_pass:update` - - `access_pass:basic:read` + Updates an existing product. Args: - collect_shipping_address: Whether the checkout flow collects a shipping address from the customer. - - custom_cta: The different types of custom CTAs that can be selected. - - custom_cta_url: A URL that the call-to-action button links to instead of the default checkout - flow. - - custom_statement_descriptor: A custom text label that appears on the customer's bank statement. Must be 5-22 - characters, contain at least one letter, and not contain <, >, \\,, ', or " - characters. - - description: A written description of the product displayed on its product page. - - gallery_images: The gallery images for the product. + description: A written description displayed on the product page. - global_affiliate_percentage: The commission rate as a percentage that affiliates earn through the global - affiliate program. + headline: A short marketing headline for the product page. - global_affiliate_status: The different statuses of the global affiliate program for a product. + metadata: Custom key-value pairs to store on the product. - headline: A short marketing headline displayed prominently on the product page. + title: The display name of the product. - member_affiliate_percentage: The commission rate as a percentage that members earn through the member - affiliate program. - - member_affiliate_status: The different statuses of the global affiliate program for a product. - - metadata: Custom key-value pairs to store on the product. Included in webhook payloads for - payment and membership events. Max 50 keys, 500 chars per key, 5000 chars per - value. - - product_tax_code_id: The unique identifier of the tax classification code to apply to this product. - - redirect_purchase_url: A URL to redirect the customer to after completing a purchase. - - route: The URL slug for the product's public link. - - send_welcome_message: Whether to send an automated welcome message via support chat when a user joins - this product. - - store_page_config: Layout and display configuration for this product on the company's store page. - - title: The display name of the product. Maximum 80 characters. - - visibility: Visibility of a resource + visibility: Whether the product is visible to customers. extra_headers: Send extra headers @@ -322,23 +231,9 @@ def update( path_template("/products/{id}", id=id), body=maybe_transform( { - "collect_shipping_address": collect_shipping_address, - "custom_cta": custom_cta, - "custom_cta_url": custom_cta_url, - "custom_statement_descriptor": custom_statement_descriptor, "description": description, - "gallery_images": gallery_images, - "global_affiliate_percentage": global_affiliate_percentage, - "global_affiliate_status": global_affiliate_status, "headline": headline, - "member_affiliate_percentage": member_affiliate_percentage, - "member_affiliate_status": member_affiliate_status, "metadata": metadata, - "product_tax_code_id": product_tax_code_id, - "redirect_purchase_url": redirect_purchase_url, - "route": route, - "send_welcome_message": send_welcome_message, - "store_page_config": store_page_config, "title": title, "visibility": visibility, }, @@ -354,16 +249,14 @@ 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, - direction: Optional[Direction] | Omit = omit, - first: Optional[int] | Omit = omit, - last: Optional[int] | Omit = omit, - order: Optional[Literal["active_memberships_count", "created_at", "usd_gmv", "usd_gmv_30_days"]] | Omit = omit, - product_types: Optional[List[AccessPassType]] | Omit = omit, - visibilities: Optional[List[VisibilityFilter]] | Omit = omit, + access_pass_types: SequenceNotStr[str] | Omit = omit, + after: str | Omit = omit, + before: str | Omit = omit, + direction: Literal["asc", "desc"] | Omit = omit, + first: int | Omit = omit, + last: int | Omit = omit, + order: str | Omit = omit, + visibilities: 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, @@ -372,33 +265,24 @@ def list( timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> SyncCursorPage[ProductListItem]: """ - Returns a paginated list of products belonging to a company, with optional - filtering by type, visibility, and creation date. - - Required permissions: - - - `access_pass:basic:read` + Returns a paginated list of products belonging to a company. Args: company_id: The unique identifier of the company to list products 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. + access_pass_types: Filter to only products matching these types. - created_after: Only return products created after this timestamp. + after: A cursor; returns products after this position. - created_before: Only return products created before this timestamp. + before: A cursor; returns products before this position. - direction: The direction of the sort. + direction: The sort direction for results. Defaults to descending. - first: Returns the first _n_ elements from the list. + first: The number of products to return (default and max 100). - last: Returns the last _n_ elements from the list. + last: The number of products to return from the end of the range. - order: The ways a relation of AccessPasses can be ordered - - product_types: Filter to only products matching these type classifications. + order: The field to sort results by. Defaults to created_at. visibilities: Filter to only products matching these visibility states. @@ -421,15 +305,13 @@ def list( query=maybe_transform( { "company_id": company_id, + "access_pass_types": access_pass_types, "after": after, "before": before, - "created_after": created_after, - "created_before": created_before, "direction": direction, "first": first, "last": last, "order": order, - "product_types": product_types, "visibilities": visibilities, }, product_list_params.ProductListParams, @@ -449,12 +331,10 @@ def delete( extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> ProductDeleteResponse: - """ - Permanently delete a product and remove it from the company's catalog. - - Required permissions: + """Deletes a product. - - `access_pass:delete` + Only products with no memberships, entries, reviews, or + invoices can be deleted. Args: extra_headers: Send extra headers @@ -501,26 +381,23 @@ def with_streaming_response(self) -> AsyncProductsResourceWithStreamingResponse: async def create( self, *, - company_id: str, title: str, collect_shipping_address: Optional[bool] | Omit = omit, - custom_cta: Optional[CustomCta] | Omit = omit, + company_id: str | Omit = omit, + custom_cta: Optional[str] | Omit = omit, custom_cta_url: Optional[str] | Omit = omit, custom_statement_descriptor: Optional[str] | Omit = omit, description: Optional[str] | Omit = omit, - experience_ids: Optional[SequenceNotStr[str]] | Omit = omit, global_affiliate_percentage: Optional[float] | Omit = omit, - global_affiliate_status: Optional[GlobalAffiliateStatus] | Omit = omit, + global_affiliate_status: str | Omit = omit, headline: Optional[str] | Omit = omit, member_affiliate_percentage: Optional[float] | Omit = omit, - member_affiliate_status: Optional[GlobalAffiliateStatus] | Omit = omit, - metadata: Optional[Dict[str, object]] | Omit = omit, - plan_options: Optional[product_create_params.PlanOptions] | Omit = omit, + member_affiliate_status: str | Omit = omit, + metadata: Optional[object] | Omit = omit, product_tax_code_id: Optional[str] | Omit = omit, redirect_purchase_url: Optional[str] | Omit = omit, route: Optional[str] | Omit = omit, - send_welcome_message: Optional[bool] | Omit = omit, - visibility: Optional[Visibility] | Omit = omit, + visibility: 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, @@ -528,64 +405,43 @@ async def create( extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> Product: - """Create a new product for a company. - - The product serves as the top-level - container for plans and experiences. - - Required permissions: - - - `access_pass:create` - - `access_pass:basic:read` + """ + Creates a new product for a company. Args: - company_id: The unique identifier of the company to create this product for. - title: The display name of the product. Maximum 80 characters. - collect_shipping_address: Whether the checkout flow collects a shipping address from the customer. - - custom_cta: The different types of custom CTAs that can be selected. + collect_shipping_address: Whether to collect a shipping address at checkout. - custom_cta_url: A URL that the call-to-action button links to instead of the default checkout - flow. + company_id: The unique identifier of the company to create this product for. - custom_statement_descriptor: A custom text label that appears on the customer's bank statement. Must be 5-22 - characters, contain at least one letter, and not contain <, >, \\,, ', or " - characters. + custom_cta: The call-to-action button label. - description: A written description of the product displayed on its product page. + custom_cta_url: A URL the call-to-action button links to. - experience_ids: The unique identifiers of experiences to connect to this product. + custom_statement_descriptor: Custom bank statement descriptor. Must start with WHOP\\**. - global_affiliate_percentage: The commission rate as a percentage that affiliates earn through the global - affiliate program. + description: A written description displayed on the product page. - global_affiliate_status: The different statuses of the global affiliate program for a product. + global_affiliate_percentage: The commission rate affiliates earn. - headline: A short marketing headline displayed prominently on the product page. + global_affiliate_status: The enrollment status in the global affiliate program. - member_affiliate_percentage: The commission rate as a percentage that members earn through the member - affiliate program. + headline: A short marketing headline for the product page. - member_affiliate_status: The different statuses of the global affiliate program for a product. + member_affiliate_percentage: The commission rate members earn. - metadata: Custom key-value pairs to store on the product. Included in webhook payloads for - payment and membership events. Max 50 keys, 500 chars per key, 5000 chars per - value. + member_affiliate_status: The enrollment status in the member affiliate program. - plan_options: Configuration for an automatically generated plan to attach to this product. + metadata: Custom key-value pairs to store on the product. - product_tax_code_id: The unique identifier of the tax classification code to apply to this product. + product_tax_code_id: The unique identifier of the tax classification code. - redirect_purchase_url: A URL to redirect the customer to after completing a purchase. + redirect_purchase_url: A URL to redirect the customer to after purchase. route: The URL slug for the product's public link. - send_welcome_message: Whether to send an automated welcome message via support chat when a user joins - this product. Defaults to true. - - visibility: Visibility of a resource + visibility: Whether the product is visible to customers. extra_headers: Send extra headers @@ -599,25 +455,22 @@ async def create( "/products", body=await async_maybe_transform( { - "company_id": company_id, "title": title, "collect_shipping_address": collect_shipping_address, + "company_id": company_id, "custom_cta": custom_cta, "custom_cta_url": custom_cta_url, "custom_statement_descriptor": custom_statement_descriptor, "description": description, - "experience_ids": experience_ids, "global_affiliate_percentage": global_affiliate_percentage, "global_affiliate_status": global_affiliate_status, "headline": headline, "member_affiliate_percentage": member_affiliate_percentage, "member_affiliate_status": member_affiliate_status, "metadata": metadata, - "plan_options": plan_options, "product_tax_code_id": product_tax_code_id, "redirect_purchase_url": redirect_purchase_url, "route": route, - "send_welcome_message": send_welcome_message, "visibility": visibility, }, product_create_params.ProductCreateParams, @@ -639,12 +492,10 @@ async def retrieve( extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> Product: - """ - Retrieves the details of an existing product. - - Required permissions: + """Retrieves the details of an existing product. - - `access_pass:basic:read` + This endpoint is publicly + accessible. Args: extra_headers: Send extra headers @@ -669,25 +520,11 @@ async def update( self, id: str, *, - collect_shipping_address: Optional[bool] | Omit = omit, - custom_cta: Optional[CustomCta] | Omit = omit, - custom_cta_url: Optional[str] | Omit = omit, - custom_statement_descriptor: Optional[str] | Omit = omit, description: Optional[str] | Omit = omit, - gallery_images: Optional[Iterable[product_update_params.GalleryImage]] | Omit = omit, - global_affiliate_percentage: Optional[float] | Omit = omit, - global_affiliate_status: Optional[GlobalAffiliateStatus] | Omit = omit, headline: Optional[str] | Omit = omit, - member_affiliate_percentage: Optional[float] | Omit = omit, - member_affiliate_status: Optional[GlobalAffiliateStatus] | Omit = omit, - metadata: Optional[Dict[str, object]] | Omit = omit, - product_tax_code_id: Optional[str] | Omit = omit, - redirect_purchase_url: Optional[str] | Omit = omit, - route: Optional[str] | Omit = omit, - send_welcome_message: Optional[bool] | Omit = omit, - store_page_config: Optional[product_update_params.StorePageConfig] | Omit = omit, - title: Optional[str] | Omit = omit, - visibility: Optional[Visibility] | Omit = omit, + metadata: Optional[object] | Omit = omit, + title: str | Omit = omit, + visibility: 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, @@ -696,59 +533,18 @@ async def update( timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> Product: """ - Update a product's title, description, visibility, and other settings. - - Required permissions: - - - `access_pass:update` - - `access_pass:basic:read` + Updates an existing product. Args: - collect_shipping_address: Whether the checkout flow collects a shipping address from the customer. - - custom_cta: The different types of custom CTAs that can be selected. - - custom_cta_url: A URL that the call-to-action button links to instead of the default checkout - flow. - - custom_statement_descriptor: A custom text label that appears on the customer's bank statement. Must be 5-22 - characters, contain at least one letter, and not contain <, >, \\,, ', or " - characters. - - description: A written description of the product displayed on its product page. - - gallery_images: The gallery images for the product. + description: A written description displayed on the product page. - global_affiliate_percentage: The commission rate as a percentage that affiliates earn through the global - affiliate program. + headline: A short marketing headline for the product page. - global_affiliate_status: The different statuses of the global affiliate program for a product. + metadata: Custom key-value pairs to store on the product. - headline: A short marketing headline displayed prominently on the product page. + title: The display name of the product. - member_affiliate_percentage: The commission rate as a percentage that members earn through the member - affiliate program. - - member_affiliate_status: The different statuses of the global affiliate program for a product. - - metadata: Custom key-value pairs to store on the product. Included in webhook payloads for - payment and membership events. Max 50 keys, 500 chars per key, 5000 chars per - value. - - product_tax_code_id: The unique identifier of the tax classification code to apply to this product. - - redirect_purchase_url: A URL to redirect the customer to after completing a purchase. - - route: The URL slug for the product's public link. - - send_welcome_message: Whether to send an automated welcome message via support chat when a user joins - this product. - - store_page_config: Layout and display configuration for this product on the company's store page. - - title: The display name of the product. Maximum 80 characters. - - visibility: Visibility of a resource + visibility: Whether the product is visible to customers. extra_headers: Send extra headers @@ -764,23 +560,9 @@ async def update( path_template("/products/{id}", id=id), body=await async_maybe_transform( { - "collect_shipping_address": collect_shipping_address, - "custom_cta": custom_cta, - "custom_cta_url": custom_cta_url, - "custom_statement_descriptor": custom_statement_descriptor, "description": description, - "gallery_images": gallery_images, - "global_affiliate_percentage": global_affiliate_percentage, - "global_affiliate_status": global_affiliate_status, "headline": headline, - "member_affiliate_percentage": member_affiliate_percentage, - "member_affiliate_status": member_affiliate_status, "metadata": metadata, - "product_tax_code_id": product_tax_code_id, - "redirect_purchase_url": redirect_purchase_url, - "route": route, - "send_welcome_message": send_welcome_message, - "store_page_config": store_page_config, "title": title, "visibility": visibility, }, @@ -796,16 +578,14 @@ 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, - direction: Optional[Direction] | Omit = omit, - first: Optional[int] | Omit = omit, - last: Optional[int] | Omit = omit, - order: Optional[Literal["active_memberships_count", "created_at", "usd_gmv", "usd_gmv_30_days"]] | Omit = omit, - product_types: Optional[List[AccessPassType]] | Omit = omit, - visibilities: Optional[List[VisibilityFilter]] | Omit = omit, + access_pass_types: SequenceNotStr[str] | Omit = omit, + after: str | Omit = omit, + before: str | Omit = omit, + direction: Literal["asc", "desc"] | Omit = omit, + first: int | Omit = omit, + last: int | Omit = omit, + order: str | Omit = omit, + visibilities: 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, @@ -814,33 +594,24 @@ def list( timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> AsyncPaginator[ProductListItem, AsyncCursorPage[ProductListItem]]: """ - Returns a paginated list of products belonging to a company, with optional - filtering by type, visibility, and creation date. - - Required permissions: - - - `access_pass:basic:read` + Returns a paginated list of products belonging to a company. Args: company_id: The unique identifier of the company to list products 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. + access_pass_types: Filter to only products matching these types. - created_after: Only return products created after this timestamp. + after: A cursor; returns products after this position. - created_before: Only return products created before this timestamp. + before: A cursor; returns products before this position. - direction: The direction of the sort. + direction: The sort direction for results. Defaults to descending. - first: Returns the first _n_ elements from the list. + first: The number of products to return (default and max 100). - last: Returns the last _n_ elements from the list. + last: The number of products to return from the end of the range. - order: The ways a relation of AccessPasses can be ordered - - product_types: Filter to only products matching these type classifications. + order: The field to sort results by. Defaults to created_at. visibilities: Filter to only products matching these visibility states. @@ -863,15 +634,13 @@ def list( query=maybe_transform( { "company_id": company_id, + "access_pass_types": access_pass_types, "after": after, "before": before, - "created_after": created_after, - "created_before": created_before, "direction": direction, "first": first, "last": last, "order": order, - "product_types": product_types, "visibilities": visibilities, }, product_list_params.ProductListParams, @@ -891,12 +660,10 @@ async def delete( extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> ProductDeleteResponse: - """ - Permanently delete a product and remove it from the company's catalog. - - Required permissions: + """Deletes a product. - - `access_pass:delete` + Only products with no memberships, entries, reviews, or + invoices can be deleted. Args: extra_headers: Send extra headers diff --git a/src/whop_sdk/types/product_create_params.py b/src/whop_sdk/types/product_create_params.py index 103fbc3d..29597ede 100644 --- a/src/whop_sdk/types/product_create_params.py +++ b/src/whop_sdk/types/product_create_params.py @@ -2,151 +2,60 @@ from __future__ import annotations -from typing import Dict, Iterable, Optional -from typing_extensions import Literal, Required, TypedDict +from typing import Optional +from typing_extensions import Required, TypedDict -from .._types import SequenceNotStr -from .shared.currency import Currency -from .shared.plan_type import PlanType -from .shared.custom_cta import CustomCta -from .shared.visibility import Visibility -from .shared.release_method import ReleaseMethod -from .shared.global_affiliate_status import GlobalAffiliateStatus - -__all__ = ["ProductCreateParams", "PlanOptions", "PlanOptionsCustomField"] +__all__ = ["ProductCreateParams"] class ProductCreateParams(TypedDict, total=False): - company_id: Required[str] - """The unique identifier of the company to create this product for.""" - title: Required[str] """The display name of the product. Maximum 80 characters.""" collect_shipping_address: Optional[bool] - """Whether the checkout flow collects a shipping address from the customer.""" + """Whether to collect a shipping address at checkout.""" + + company_id: str + """The unique identifier of the company to create this product for.""" - custom_cta: Optional[CustomCta] - """The different types of custom CTAs that can be selected.""" + custom_cta: Optional[str] + """The call-to-action button label.""" custom_cta_url: Optional[str] - """ - A URL that the call-to-action button links to instead of the default checkout - flow. - """ + """A URL the call-to-action button links to.""" custom_statement_descriptor: Optional[str] - """A custom text label that appears on the customer's bank statement. - - Must be 5-22 characters, contain at least one letter, and not contain <, >, \\,, - ', or " characters. - """ + """Custom bank statement descriptor. Must start with WHOP\\**.""" description: Optional[str] - """A written description of the product displayed on its product page.""" - - experience_ids: Optional[SequenceNotStr[str]] - """The unique identifiers of experiences to connect to this product.""" + """A written description displayed on the product page.""" global_affiliate_percentage: Optional[float] - """ - The commission rate as a percentage that affiliates earn through the global - affiliate program. - """ + """The commission rate affiliates earn.""" - global_affiliate_status: Optional[GlobalAffiliateStatus] - """The different statuses of the global affiliate program for a product.""" + global_affiliate_status: str + """The enrollment status in the global affiliate program.""" headline: Optional[str] - """A short marketing headline displayed prominently on the product page.""" + """A short marketing headline for the product page.""" member_affiliate_percentage: Optional[float] - """ - The commission rate as a percentage that members earn through the member - affiliate program. - """ - - member_affiliate_status: Optional[GlobalAffiliateStatus] - """The different statuses of the global affiliate program for a product.""" + """The commission rate members earn.""" - metadata: Optional[Dict[str, object]] - """Custom key-value pairs to store on the product. + member_affiliate_status: str + """The enrollment status in the member affiliate program.""" - Included in webhook payloads for payment and membership events. Max 50 keys, 500 - chars per key, 5000 chars per value. - """ - - plan_options: Optional[PlanOptions] - """Configuration for an automatically generated plan to attach to this product.""" + metadata: Optional[object] + """Custom key-value pairs to store on the product.""" product_tax_code_id: Optional[str] - """The unique identifier of the tax classification code to apply to this product.""" + """The unique identifier of the tax classification code.""" redirect_purchase_url: Optional[str] - """A URL to redirect the customer to after completing a purchase.""" + """A URL to redirect the customer to after purchase.""" route: Optional[str] """The URL slug for the product's public link.""" - send_welcome_message: Optional[bool] - """ - Whether to send an automated welcome message via support chat when a user joins - this product. Defaults to true. - """ - - visibility: Optional[Visibility] - """Visibility of a resource""" - - -class PlanOptionsCustomField(TypedDict, total=False): - field_type: Required[Literal["text"]] - """The type of the custom field.""" - - name: Required[str] - """The name of the custom field.""" - - id: Optional[str] - """The ID of the custom field (if being updated)""" - - order: Optional[int] - """The order of the field.""" - - placeholder: Optional[str] - """The placeholder value of the field.""" - - required: Optional[bool] - """Whether or not the field is required.""" - - -class PlanOptions(TypedDict, total=False): - """Configuration for an automatically generated plan to attach to this product.""" - - base_currency: Optional[Currency] - """The available currencies on the platform""" - - billing_period: Optional[int] - """The interval at which the plan charges (renewal plans).""" - - custom_fields: Optional[Iterable[PlanOptionsCustomField]] - """An array of custom field objects.""" - - initial_price: Optional[float] - """An additional amount charged upon first purchase. - - Provided as a number in the specified currency. Eg: 10.43 for $10.43 USD. - """ - - plan_type: Optional[PlanType] - """The type of plan that can be attached to a product""" - - release_method: Optional[ReleaseMethod] - """The methods of how a plan can be released.""" - - renewal_price: Optional[float] - """The amount the customer is charged every billing period. - - Provided as a number in the specified currency. Eg: 10.43 for $10.43 USD. - """ - - visibility: Optional[Visibility] - """Visibility of a resource""" + visibility: str + """Whether the product is visible to customers.""" diff --git a/src/whop_sdk/types/product_list_params.py b/src/whop_sdk/types/product_list_params.py index 47862a61..5b666c38 100644 --- a/src/whop_sdk/types/product_list_params.py +++ b/src/whop_sdk/types/product_list_params.py @@ -2,14 +2,9 @@ from __future__ import annotations -from typing import List, Union, Optional -from datetime import datetime -from typing_extensions import Literal, Required, Annotated, TypedDict +from typing_extensions import Literal, Required, TypedDict -from .._utils import PropertyInfo -from .shared.direction import Direction -from .shared.access_pass_type import AccessPassType -from .shared.visibility_filter import VisibilityFilter +from .._types import SequenceNotStr __all__ = ["ProductListParams"] @@ -18,32 +13,26 @@ class ProductListParams(TypedDict, total=False): company_id: Required[str] """The unique identifier of the company to list products for.""" - after: Optional[str] - """Returns the elements in the list that come after the specified cursor.""" + access_pass_types: SequenceNotStr[str] + """Filter to only products matching these types.""" - before: Optional[str] - """Returns the elements in the list that come before the specified cursor.""" + after: str + """A cursor; returns products after this position.""" - created_after: Annotated[Union[str, datetime, None], PropertyInfo(format="iso8601")] - """Only return products created after this timestamp.""" + before: str + """A cursor; returns products before this position.""" - created_before: Annotated[Union[str, datetime, None], PropertyInfo(format="iso8601")] - """Only return products created before this timestamp.""" + direction: Literal["asc", "desc"] + """The sort direction for results. Defaults to descending.""" - direction: Optional[Direction] - """The direction of the sort.""" + first: int + """The number of products to return (default and max 100).""" - first: Optional[int] - """Returns the first _n_ elements from the list.""" + last: int + """The number of products to return from the end of the range.""" - last: Optional[int] - """Returns the last _n_ elements from the list.""" + order: str + """The field to sort results by. Defaults to created_at.""" - order: Optional[Literal["active_memberships_count", "created_at", "usd_gmv", "usd_gmv_30_days"]] - """The ways a relation of AccessPasses can be ordered""" - - product_types: Optional[List[AccessPassType]] - """Filter to only products matching these type classifications.""" - - visibilities: Optional[List[VisibilityFilter]] + visibilities: SequenceNotStr[str] """Filter to only products matching these visibility states.""" diff --git a/src/whop_sdk/types/product_update_params.py b/src/whop_sdk/types/product_update_params.py index 64a7c8c1..d9d48096 100644 --- a/src/whop_sdk/types/product_update_params.py +++ b/src/whop_sdk/types/product_update_params.py @@ -2,107 +2,24 @@ from __future__ import annotations -from typing import Dict, Iterable, Optional -from typing_extensions import Required, TypedDict +from typing import Optional +from typing_extensions import TypedDict -from .shared.custom_cta import CustomCta -from .shared.visibility import Visibility -from .shared.global_affiliate_status import GlobalAffiliateStatus - -__all__ = ["ProductUpdateParams", "GalleryImage", "StorePageConfig"] +__all__ = ["ProductUpdateParams"] class ProductUpdateParams(TypedDict, total=False): - collect_shipping_address: Optional[bool] - """Whether the checkout flow collects a shipping address from the customer.""" - - custom_cta: Optional[CustomCta] - """The different types of custom CTAs that can be selected.""" - - custom_cta_url: Optional[str] - """ - A URL that the call-to-action button links to instead of the default checkout - flow. - """ - - custom_statement_descriptor: Optional[str] - """A custom text label that appears on the customer's bank statement. - - Must be 5-22 characters, contain at least one letter, and not contain <, >, \\,, - ', or " characters. - """ - description: Optional[str] - """A written description of the product displayed on its product page.""" - - gallery_images: Optional[Iterable[GalleryImage]] - """The gallery images for the product.""" - - global_affiliate_percentage: Optional[float] - """ - The commission rate as a percentage that affiliates earn through the global - affiliate program. - """ - - global_affiliate_status: Optional[GlobalAffiliateStatus] - """The different statuses of the global affiliate program for a product.""" + """A written description displayed on the product page.""" headline: Optional[str] - """A short marketing headline displayed prominently on the product page.""" - - member_affiliate_percentage: Optional[float] - """ - The commission rate as a percentage that members earn through the member - affiliate program. - """ - - member_affiliate_status: Optional[GlobalAffiliateStatus] - """The different statuses of the global affiliate program for a product.""" - - metadata: Optional[Dict[str, object]] - """Custom key-value pairs to store on the product. - - Included in webhook payloads for payment and membership events. Max 50 keys, 500 - chars per key, 5000 chars per value. - """ - - product_tax_code_id: Optional[str] - """The unique identifier of the tax classification code to apply to this product.""" - - redirect_purchase_url: Optional[str] - """A URL to redirect the customer to after completing a purchase.""" - - route: Optional[str] - """The URL slug for the product's public link.""" - - send_welcome_message: Optional[bool] - """ - Whether to send an automated welcome message via support chat when a user joins - this product. - """ - - store_page_config: Optional[StorePageConfig] - """Layout and display configuration for this product on the company's store page.""" - - title: Optional[str] - """The display name of the product. Maximum 80 characters.""" - - visibility: Optional[Visibility] - """Visibility of a resource""" - - -class GalleryImage(TypedDict, total=False): - """Input for an attachment""" - - id: Required[str] - """The ID of an existing file object.""" - + """A short marketing headline for the product page.""" -class StorePageConfig(TypedDict, total=False): - """Layout and display configuration for this product on the company's store page.""" + metadata: Optional[object] + """Custom key-value pairs to store on the product.""" - custom_cta: Optional[str] - """Custom call-to-action text for the product's store page.""" + title: str + """The display name of the product.""" - show_price: Optional[bool] - """Whether or not to show the price on the product's store page.""" + visibility: str + """Whether the product is visible to customers.""" diff --git a/src/whop_sdk/types/shared_params/__init__.py b/src/whop_sdk/types/shared_params/__init__.py index eb838c16..4ae01cc7 100644 --- a/src/whop_sdk/types/shared_params/__init__.py +++ b/src/whop_sdk/types/shared_params/__init__.py @@ -4,7 +4,6 @@ from .tax_type import TaxType as TaxType from .direction import Direction as Direction from .plan_type import PlanType as PlanType -from .custom_cta import CustomCta as CustomCta from .promo_type import PromoType as PromoType from .visibility import Visibility as Visibility from .access_level import AccessLevel as AccessLevel @@ -17,10 +16,8 @@ from .receipt_status import ReceiptStatus as ReceiptStatus from .release_method import ReleaseMethod as ReleaseMethod from .member_statuses import MemberStatuses as MemberStatuses -from .access_pass_type import AccessPassType as AccessPassType from .collection_method import CollectionMethod as CollectionMethod from .membership_status import MembershipStatus as MembershipStatus -from .visibility_filter import VisibilityFilter as VisibilityFilter from .app_build_statuses import AppBuildStatuses as AppBuildStatuses from .who_can_post_types import WhoCanPostTypes as WhoCanPostTypes from .app_build_platforms import AppBuildPlatforms as AppBuildPlatforms diff --git a/src/whop_sdk/types/shared_params/access_pass_type.py b/src/whop_sdk/types/shared_params/access_pass_type.py deleted file mode 100644 index 3f760c67..00000000 --- a/src/whop_sdk/types/shared_params/access_pass_type.py +++ /dev/null @@ -1,9 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from __future__ import annotations - -from typing_extensions import Literal, TypeAlias - -__all__ = ["AccessPassType"] - -AccessPassType: TypeAlias = Literal["regular", "app", "experience_upsell", "api_only"] diff --git a/src/whop_sdk/types/shared_params/custom_cta.py b/src/whop_sdk/types/shared_params/custom_cta.py deleted file mode 100644 index 0c2f5d19..00000000 --- a/src/whop_sdk/types/shared_params/custom_cta.py +++ /dev/null @@ -1,23 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from __future__ import annotations - -from typing_extensions import Literal, TypeAlias - -__all__ = ["CustomCta"] - -CustomCta: TypeAlias = Literal[ - "get_access", - "join", - "order_now", - "shop_now", - "call_now", - "donate_now", - "contact_us", - "sign_up", - "subscribe", - "purchase", - "get_offer", - "apply_now", - "complete_order", -] diff --git a/src/whop_sdk/types/shared_params/visibility_filter.py b/src/whop_sdk/types/shared_params/visibility_filter.py deleted file mode 100644 index d01e6cb1..00000000 --- a/src/whop_sdk/types/shared_params/visibility_filter.py +++ /dev/null @@ -1,11 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from __future__ import annotations - -from typing_extensions import Literal, TypeAlias - -__all__ = ["VisibilityFilter"] - -VisibilityFilter: TypeAlias = Literal[ - "visible", "hidden", "archived", "quick_link", "all", "not_quick_link", "not_archived" -] diff --git a/tests/api_resources/test_products.py b/tests/api_resources/test_products.py index b9a1c329..10201f78 100644 --- a/tests/api_resources/test_products.py +++ b/tests/api_resources/test_products.py @@ -9,10 +9,7 @@ from whop_sdk import Whop, AsyncWhop from tests.utils import assert_matches_type -from whop_sdk.types import ( - ProductDeleteResponse, -) -from whop_sdk._utils import parse_datetime +from whop_sdk.types import ProductDeleteResponse from whop_sdk.pagination import SyncCursorPage, AsyncCursorPage from whop_sdk.types.shared import Product, ProductListItem @@ -26,7 +23,6 @@ class TestProducts: @parametrize def test_method_create(self, client: Whop) -> None: product = client.products.create( - company_id="biz_xxxxxxxxxxxxxx", title="title", ) assert_matches_type(Product, product, path=["response"]) @@ -35,44 +31,23 @@ def test_method_create(self, client: Whop) -> None: @parametrize def test_method_create_with_all_params(self, client: Whop) -> None: product = client.products.create( - company_id="biz_xxxxxxxxxxxxxx", title="title", collect_shipping_address=True, - custom_cta="get_access", + company_id="company_id", + custom_cta="custom_cta", custom_cta_url="custom_cta_url", custom_statement_descriptor="custom_statement_descriptor", description="description", - experience_ids=["string"], - global_affiliate_percentage=6.9, - global_affiliate_status="enabled", + global_affiliate_percentage=0, + global_affiliate_status="global_affiliate_status", headline="headline", - member_affiliate_percentage=6.9, - member_affiliate_status="enabled", - metadata={"foo": "bar"}, - plan_options={ - "base_currency": "usd", - "billing_period": 42, - "custom_fields": [ - { - "field_type": "text", - "name": "name", - "id": "id", - "order": 42, - "placeholder": "placeholder", - "required": True, - } - ], - "initial_price": 6.9, - "plan_type": "renewal", - "release_method": "buy_now", - "renewal_price": 6.9, - "visibility": "visible", - }, - product_tax_code_id="ptc_xxxxxxxxxxxxxx", + member_affiliate_percentage=0, + member_affiliate_status="member_affiliate_status", + metadata={}, + product_tax_code_id="product_tax_code_id", redirect_purchase_url="redirect_purchase_url", route="route", - send_welcome_message=True, - visibility="visible", + visibility="visibility", ) assert_matches_type(Product, product, path=["response"]) @@ -80,7 +55,6 @@ def test_method_create_with_all_params(self, client: Whop) -> None: @parametrize def test_raw_response_create(self, client: Whop) -> None: response = client.products.with_raw_response.create( - company_id="biz_xxxxxxxxxxxxxx", title="title", ) @@ -93,7 +67,6 @@ def test_raw_response_create(self, client: Whop) -> None: @parametrize def test_streaming_response_create(self, client: Whop) -> None: with client.products.with_streaming_response.create( - company_id="biz_xxxxxxxxxxxxxx", title="title", ) as response: assert not response.is_closed @@ -108,7 +81,7 @@ def test_streaming_response_create(self, client: Whop) -> None: @parametrize def test_method_retrieve(self, client: Whop) -> None: product = client.products.retrieve( - "prod_xxxxxxxxxxxxx", + "id", ) assert_matches_type(Product, product, path=["response"]) @@ -116,7 +89,7 @@ def test_method_retrieve(self, client: Whop) -> None: @parametrize def test_raw_response_retrieve(self, client: Whop) -> None: response = client.products.with_raw_response.retrieve( - "prod_xxxxxxxxxxxxx", + "id", ) assert response.is_closed is True @@ -128,7 +101,7 @@ def test_raw_response_retrieve(self, client: Whop) -> None: @parametrize def test_streaming_response_retrieve(self, client: Whop) -> None: with client.products.with_streaming_response.retrieve( - "prod_xxxxxxxxxxxxx", + "id", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -150,7 +123,7 @@ def test_path_params_retrieve(self, client: Whop) -> None: @parametrize def test_method_update(self, client: Whop) -> None: product = client.products.update( - id="prod_xxxxxxxxxxxxx", + id="id", ) assert_matches_type(Product, product, path=["response"]) @@ -158,29 +131,12 @@ def test_method_update(self, client: Whop) -> None: @parametrize def test_method_update_with_all_params(self, client: Whop) -> None: product = client.products.update( - id="prod_xxxxxxxxxxxxx", - collect_shipping_address=True, - custom_cta="get_access", - custom_cta_url="custom_cta_url", - custom_statement_descriptor="custom_statement_descriptor", + id="id", description="description", - gallery_images=[{"id": "id"}], - global_affiliate_percentage=6.9, - global_affiliate_status="enabled", headline="headline", - member_affiliate_percentage=6.9, - member_affiliate_status="enabled", - metadata={"foo": "bar"}, - product_tax_code_id="ptc_xxxxxxxxxxxxxx", - redirect_purchase_url="redirect_purchase_url", - route="route", - send_welcome_message=True, - store_page_config={ - "custom_cta": "custom_cta", - "show_price": True, - }, + metadata={}, title="title", - visibility="visible", + visibility="visibility", ) assert_matches_type(Product, product, path=["response"]) @@ -188,7 +144,7 @@ def test_method_update_with_all_params(self, client: Whop) -> None: @parametrize def test_raw_response_update(self, client: Whop) -> None: response = client.products.with_raw_response.update( - id="prod_xxxxxxxxxxxxx", + id="id", ) assert response.is_closed is True @@ -200,7 +156,7 @@ def test_raw_response_update(self, client: Whop) -> None: @parametrize def test_streaming_response_update(self, client: Whop) -> None: with client.products.with_streaming_response.update( - id="prod_xxxxxxxxxxxxx", + id="id", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -222,7 +178,7 @@ def test_path_params_update(self, client: Whop) -> None: @parametrize def test_method_list(self, client: Whop) -> None: product = client.products.list( - company_id="biz_xxxxxxxxxxxxxx", + company_id="company_id", ) assert_matches_type(SyncCursorPage[ProductListItem], product, path=["response"]) @@ -230,17 +186,15 @@ def test_method_list(self, client: Whop) -> None: @parametrize def test_method_list_with_all_params(self, client: Whop) -> None: product = client.products.list( - company_id="biz_xxxxxxxxxxxxxx", + company_id="company_id", + access_pass_types=["string"], after="after", before="before", - created_after=parse_datetime("2023-12-01T05:00:00.401Z"), - created_before=parse_datetime("2023-12-01T05:00:00.401Z"), direction="asc", - first=42, - last=42, - order="active_memberships_count", - product_types=["regular"], - visibilities=["visible"], + first=0, + last=0, + order="order", + visibilities=["string"], ) assert_matches_type(SyncCursorPage[ProductListItem], product, path=["response"]) @@ -248,7 +202,7 @@ def test_method_list_with_all_params(self, client: Whop) -> None: @parametrize def test_raw_response_list(self, client: Whop) -> None: response = client.products.with_raw_response.list( - company_id="biz_xxxxxxxxxxxxxx", + company_id="company_id", ) assert response.is_closed is True @@ -260,7 +214,7 @@ def test_raw_response_list(self, client: Whop) -> None: @parametrize def test_streaming_response_list(self, client: Whop) -> None: with client.products.with_streaming_response.list( - company_id="biz_xxxxxxxxxxxxxx", + company_id="company_id", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -274,7 +228,7 @@ def test_streaming_response_list(self, client: Whop) -> None: @parametrize def test_method_delete(self, client: Whop) -> None: product = client.products.delete( - "prod_xxxxxxxxxxxxx", + "id", ) assert_matches_type(ProductDeleteResponse, product, path=["response"]) @@ -282,7 +236,7 @@ def test_method_delete(self, client: Whop) -> None: @parametrize def test_raw_response_delete(self, client: Whop) -> None: response = client.products.with_raw_response.delete( - "prod_xxxxxxxxxxxxx", + "id", ) assert response.is_closed is True @@ -294,7 +248,7 @@ def test_raw_response_delete(self, client: Whop) -> None: @parametrize def test_streaming_response_delete(self, client: Whop) -> None: with client.products.with_streaming_response.delete( - "prod_xxxxxxxxxxxxx", + "id", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -322,7 +276,6 @@ class TestAsyncProducts: @parametrize async def test_method_create(self, async_client: AsyncWhop) -> None: product = await async_client.products.create( - company_id="biz_xxxxxxxxxxxxxx", title="title", ) assert_matches_type(Product, product, path=["response"]) @@ -331,44 +284,23 @@ async def test_method_create(self, async_client: AsyncWhop) -> None: @parametrize async def test_method_create_with_all_params(self, async_client: AsyncWhop) -> None: product = await async_client.products.create( - company_id="biz_xxxxxxxxxxxxxx", title="title", collect_shipping_address=True, - custom_cta="get_access", + company_id="company_id", + custom_cta="custom_cta", custom_cta_url="custom_cta_url", custom_statement_descriptor="custom_statement_descriptor", description="description", - experience_ids=["string"], - global_affiliate_percentage=6.9, - global_affiliate_status="enabled", + global_affiliate_percentage=0, + global_affiliate_status="global_affiliate_status", headline="headline", - member_affiliate_percentage=6.9, - member_affiliate_status="enabled", - metadata={"foo": "bar"}, - plan_options={ - "base_currency": "usd", - "billing_period": 42, - "custom_fields": [ - { - "field_type": "text", - "name": "name", - "id": "id", - "order": 42, - "placeholder": "placeholder", - "required": True, - } - ], - "initial_price": 6.9, - "plan_type": "renewal", - "release_method": "buy_now", - "renewal_price": 6.9, - "visibility": "visible", - }, - product_tax_code_id="ptc_xxxxxxxxxxxxxx", + member_affiliate_percentage=0, + member_affiliate_status="member_affiliate_status", + metadata={}, + product_tax_code_id="product_tax_code_id", redirect_purchase_url="redirect_purchase_url", route="route", - send_welcome_message=True, - visibility="visible", + visibility="visibility", ) assert_matches_type(Product, product, path=["response"]) @@ -376,7 +308,6 @@ async def test_method_create_with_all_params(self, async_client: AsyncWhop) -> N @parametrize async def test_raw_response_create(self, async_client: AsyncWhop) -> None: response = await async_client.products.with_raw_response.create( - company_id="biz_xxxxxxxxxxxxxx", title="title", ) @@ -389,7 +320,6 @@ async def test_raw_response_create(self, async_client: AsyncWhop) -> None: @parametrize async def test_streaming_response_create(self, async_client: AsyncWhop) -> None: async with async_client.products.with_streaming_response.create( - company_id="biz_xxxxxxxxxxxxxx", title="title", ) as response: assert not response.is_closed @@ -404,7 +334,7 @@ async def test_streaming_response_create(self, async_client: AsyncWhop) -> None: @parametrize async def test_method_retrieve(self, async_client: AsyncWhop) -> None: product = await async_client.products.retrieve( - "prod_xxxxxxxxxxxxx", + "id", ) assert_matches_type(Product, product, path=["response"]) @@ -412,7 +342,7 @@ async def test_method_retrieve(self, async_client: AsyncWhop) -> None: @parametrize async def test_raw_response_retrieve(self, async_client: AsyncWhop) -> None: response = await async_client.products.with_raw_response.retrieve( - "prod_xxxxxxxxxxxxx", + "id", ) assert response.is_closed is True @@ -424,7 +354,7 @@ async def test_raw_response_retrieve(self, async_client: AsyncWhop) -> None: @parametrize async def test_streaming_response_retrieve(self, async_client: AsyncWhop) -> None: async with async_client.products.with_streaming_response.retrieve( - "prod_xxxxxxxxxxxxx", + "id", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -446,7 +376,7 @@ async def test_path_params_retrieve(self, async_client: AsyncWhop) -> None: @parametrize async def test_method_update(self, async_client: AsyncWhop) -> None: product = await async_client.products.update( - id="prod_xxxxxxxxxxxxx", + id="id", ) assert_matches_type(Product, product, path=["response"]) @@ -454,29 +384,12 @@ async def test_method_update(self, async_client: AsyncWhop) -> None: @parametrize async def test_method_update_with_all_params(self, async_client: AsyncWhop) -> None: product = await async_client.products.update( - id="prod_xxxxxxxxxxxxx", - collect_shipping_address=True, - custom_cta="get_access", - custom_cta_url="custom_cta_url", - custom_statement_descriptor="custom_statement_descriptor", + id="id", description="description", - gallery_images=[{"id": "id"}], - global_affiliate_percentage=6.9, - global_affiliate_status="enabled", headline="headline", - member_affiliate_percentage=6.9, - member_affiliate_status="enabled", - metadata={"foo": "bar"}, - product_tax_code_id="ptc_xxxxxxxxxxxxxx", - redirect_purchase_url="redirect_purchase_url", - route="route", - send_welcome_message=True, - store_page_config={ - "custom_cta": "custom_cta", - "show_price": True, - }, + metadata={}, title="title", - visibility="visible", + visibility="visibility", ) assert_matches_type(Product, product, path=["response"]) @@ -484,7 +397,7 @@ async def test_method_update_with_all_params(self, async_client: AsyncWhop) -> N @parametrize async def test_raw_response_update(self, async_client: AsyncWhop) -> None: response = await async_client.products.with_raw_response.update( - id="prod_xxxxxxxxxxxxx", + id="id", ) assert response.is_closed is True @@ -496,7 +409,7 @@ async def test_raw_response_update(self, async_client: AsyncWhop) -> None: @parametrize async def test_streaming_response_update(self, async_client: AsyncWhop) -> None: async with async_client.products.with_streaming_response.update( - id="prod_xxxxxxxxxxxxx", + id="id", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -518,7 +431,7 @@ async def test_path_params_update(self, async_client: AsyncWhop) -> None: @parametrize async def test_method_list(self, async_client: AsyncWhop) -> None: product = await async_client.products.list( - company_id="biz_xxxxxxxxxxxxxx", + company_id="company_id", ) assert_matches_type(AsyncCursorPage[ProductListItem], product, path=["response"]) @@ -526,17 +439,15 @@ async def test_method_list(self, async_client: AsyncWhop) -> None: @parametrize async def test_method_list_with_all_params(self, async_client: AsyncWhop) -> None: product = await async_client.products.list( - company_id="biz_xxxxxxxxxxxxxx", + company_id="company_id", + access_pass_types=["string"], after="after", before="before", - created_after=parse_datetime("2023-12-01T05:00:00.401Z"), - created_before=parse_datetime("2023-12-01T05:00:00.401Z"), direction="asc", - first=42, - last=42, - order="active_memberships_count", - product_types=["regular"], - visibilities=["visible"], + first=0, + last=0, + order="order", + visibilities=["string"], ) assert_matches_type(AsyncCursorPage[ProductListItem], product, path=["response"]) @@ -544,7 +455,7 @@ async def test_method_list_with_all_params(self, async_client: AsyncWhop) -> Non @parametrize async def test_raw_response_list(self, async_client: AsyncWhop) -> None: response = await async_client.products.with_raw_response.list( - company_id="biz_xxxxxxxxxxxxxx", + company_id="company_id", ) assert response.is_closed is True @@ -556,7 +467,7 @@ async def test_raw_response_list(self, async_client: AsyncWhop) -> None: @parametrize async def test_streaming_response_list(self, async_client: AsyncWhop) -> None: async with async_client.products.with_streaming_response.list( - company_id="biz_xxxxxxxxxxxxxx", + company_id="company_id", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -570,7 +481,7 @@ async def test_streaming_response_list(self, async_client: AsyncWhop) -> None: @parametrize async def test_method_delete(self, async_client: AsyncWhop) -> None: product = await async_client.products.delete( - "prod_xxxxxxxxxxxxx", + "id", ) assert_matches_type(ProductDeleteResponse, product, path=["response"]) @@ -578,7 +489,7 @@ async def test_method_delete(self, async_client: AsyncWhop) -> None: @parametrize async def test_raw_response_delete(self, async_client: AsyncWhop) -> None: response = await async_client.products.with_raw_response.delete( - "prod_xxxxxxxxxxxxx", + "id", ) assert response.is_closed is True @@ -590,7 +501,7 @@ async def test_raw_response_delete(self, async_client: AsyncWhop) -> None: @parametrize async def test_streaming_response_delete(self, async_client: AsyncWhop) -> None: async with async_client.products.with_streaming_response.delete( - "prod_xxxxxxxxxxxxx", + "id", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" From d754ce20351c8438bd9d91f51a6e809ff1046a86 Mon Sep 17 00:00:00 2001 From: stlc-bot Date: Sat, 13 Jun 2026 02:35:30 +0000 Subject: [PATCH 040/109] Let advertisers set a desired cost per result on the campaign Stainless-Generated-From: 35810b4d3977ca1ee2ddce099af1f71c8282dddf --- src/whop_sdk/resources/ad_campaigns.py | 22 +++++++++++++++++-- .../types/ad_campaign_update_params.py | 3 +++ tests/api_resources/test_ad_campaigns.py | 2 ++ 3 files changed, 25 insertions(+), 2 deletions(-) diff --git a/src/whop_sdk/resources/ad_campaigns.py b/src/whop_sdk/resources/ad_campaigns.py index eb6f5d65..f166f0ce 100644 --- a/src/whop_sdk/resources/ad_campaigns.py +++ b/src/whop_sdk/resources/ad_campaigns.py @@ -116,6 +116,7 @@ def update( id: str, *, budget: Optional[float] | Omit = omit, + desired_cpr: 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, @@ -135,6 +136,8 @@ def update( budget: The campaign budget in dollars. The interpretation (daily or lifetime) follows the campaign's existing budget type. + desired_cpr: The advertiser's desired cost per result in dollars. + extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -147,7 +150,13 @@ def update( 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({"budget": budget}, ad_campaign_update_params.AdCampaignUpdateParams), + body=maybe_transform( + { + "budget": budget, + "desired_cpr": desired_cpr, + }, + ad_campaign_update_params.AdCampaignUpdateParams, + ), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -427,6 +436,7 @@ async def update( id: str, *, budget: Optional[float] | Omit = omit, + desired_cpr: 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, @@ -446,6 +456,8 @@ async def update( budget: The campaign budget in dollars. The interpretation (daily or lifetime) follows the campaign's existing budget type. + desired_cpr: The advertiser's desired cost per result in dollars. + extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -458,7 +470,13 @@ async def update( 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({"budget": budget}, ad_campaign_update_params.AdCampaignUpdateParams), + body=await async_maybe_transform( + { + "budget": budget, + "desired_cpr": desired_cpr, + }, + ad_campaign_update_params.AdCampaignUpdateParams, + ), 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/ad_campaign_update_params.py b/src/whop_sdk/types/ad_campaign_update_params.py index 7c476aea..63afb37f 100644 --- a/src/whop_sdk/types/ad_campaign_update_params.py +++ b/src/whop_sdk/types/ad_campaign_update_params.py @@ -15,3 +15,6 @@ class AdCampaignUpdateParams(TypedDict, total=False): The interpretation (daily or lifetime) follows the campaign's existing budget type. """ + + desired_cpr: Optional[float] + """The advertiser's desired cost per result in dollars.""" diff --git a/tests/api_resources/test_ad_campaigns.py b/tests/api_resources/test_ad_campaigns.py index f7323e0b..7a23db43 100644 --- a/tests/api_resources/test_ad_campaigns.py +++ b/tests/api_resources/test_ad_campaigns.py @@ -88,6 +88,7 @@ def test_method_update_with_all_params(self, client: Whop) -> None: ad_campaign = client.ad_campaigns.update( id="adcamp_xxxxxxxxxxx", budget=6.9, + desired_cpr=6.9, ) assert_matches_type(AdCampaign, ad_campaign, path=["response"]) @@ -329,6 +330,7 @@ async def test_method_update_with_all_params(self, async_client: AsyncWhop) -> N ad_campaign = await async_client.ad_campaigns.update( id="adcamp_xxxxxxxxxxx", budget=6.9, + desired_cpr=6.9, ) assert_matches_type(AdCampaign, ad_campaign, path=["response"]) From 9c5146424be59389dc291f2b03f6aaf8cd0a0ebd Mon Sep 17 00:00:00 2001 From: stlc-bot Date: Sat, 13 Jun 2026 02:43:14 +0000 Subject: [PATCH 041/109] Add `ledger_account.funds_available` webhook for when funds become withdrawable Stainless-Generated-From: 71c43929715e81dc1d205d50d615b5f812428620 --- api.md | 1 + src/whop_sdk/types/__init__.py | 3 + ...r_account_funds_available_webhook_event.py | 253 ++++++++++++++++++ src/whop_sdk/types/unwrap_webhook_event.py | 2 + src/whop_sdk/types/webhook_event.py | 1 + 5 files changed, 260 insertions(+) create mode 100644 src/whop_sdk/types/ledger_account_funds_available_webhook_event.py diff --git a/api.md b/api.md index 1faf21c0..cae65e50 100644 --- a/api.md +++ b/api.md @@ -170,6 +170,7 @@ from whop_sdk.types import ( InvoicePaidWebhookEvent, InvoicePastDueWebhookEvent, InvoiceVoidedWebhookEvent, + LedgerAccountFundsAvailableWebhookEvent, MembershipActivatedWebhookEvent, MembershipCancelAtPeriodEndChangedWebhookEvent, MembershipDeactivatedWebhookEvent, diff --git a/src/whop_sdk/types/__init__.py b/src/whop_sdk/types/__init__.py index 515c4777..7e85327c 100644 --- a/src/whop_sdk/types/__init__.py +++ b/src/whop_sdk/types/__init__.py @@ -461,6 +461,9 @@ from .payout_account_status_updated_webhook_event import ( PayoutAccountStatusUpdatedWebhookEvent as PayoutAccountStatusUpdatedWebhookEvent, ) +from .ledger_account_funds_available_webhook_event import ( + LedgerAccountFundsAvailableWebhookEvent as LedgerAccountFundsAvailableWebhookEvent, +) from .resolution_center_case_created_webhook_event import ( ResolutionCenterCaseCreatedWebhookEvent as ResolutionCenterCaseCreatedWebhookEvent, ) diff --git a/src/whop_sdk/types/ledger_account_funds_available_webhook_event.py b/src/whop_sdk/types/ledger_account_funds_available_webhook_event.py new file mode 100644 index 00000000..4deabdc1 --- /dev/null +++ b/src/whop_sdk/types/ledger_account_funds_available_webhook_event.py @@ -0,0 +1,253 @@ +# 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 .shared.currency import Currency +from .verification_status import VerificationStatus +from .verification_error_code import VerificationErrorCode +from .payout_account_calculated_statuses import PayoutAccountCalculatedStatuses + +__all__ = [ + "LedgerAccountFundsAvailableWebhookEvent", + "Data", + "DataBalance", + "DataOwner", + "DataOwnerUser", + "DataOwnerCompany", + "DataPayoutAccountDetails", + "DataPayoutAccountDetailsAddress", + "DataPayoutAccountDetailsBusinessRepresentative", + "DataPayoutAccountDetailsLatestVerification", + "DataTreasuryBalance", +] + + +class DataBalance(BaseModel): + """A cached balance for a LedgerAccount in respect to a currency.""" + + balance: float + """The amount of the balance.""" + + currency: Currency + """The currency of the balance.""" + + pending_balance: float + """The amount of the balance that is pending.""" + + reserve_balance: float + """The amount of the balance that is reserved.""" + + +class DataOwnerUser(BaseModel): + """A user account on Whop. + + Contains profile information, identity details, and social connections. + """ + + id: str + """The unique identifier for the user.""" + + name: Optional[str] = None + """The user's display name shown on their public profile.""" + + typename: Literal["User"] + """The typename of this object""" + + username: str + """The user's unique username shown on their public profile.""" + + +class DataOwnerCompany(BaseModel): + """A company is a seller on Whop. + + Companies own products, manage members, and receive payouts. + """ + + id: str + """The unique identifier for the company.""" + + route: str + """ + The URL slug for the company's store page (e.g., 'pickaxe' in whop.com/pickaxe). + """ + + title: str + """The display name of the company shown to customers.""" + + typename: Literal["Company"] + """The typename of this object""" + + +DataOwner: TypeAlias = Annotated[ + Union[Optional[DataOwnerUser], Optional[DataOwnerCompany]], PropertyInfo(discriminator="typename") +] + + +class DataPayoutAccountDetailsAddress(BaseModel): + """The physical address associated with this payout account""" + + city: Optional[str] = None + """The city of the address.""" + + country: Optional[str] = None + """The country of the address.""" + + line1: Optional[str] = None + """The line 1 of the address.""" + + line2: Optional[str] = None + """The line 2 of the address.""" + + postal_code: Optional[str] = None + """The postal code of the address.""" + + state: Optional[str] = None + """The state of the address.""" + + +class DataPayoutAccountDetailsBusinessRepresentative(BaseModel): + """The business representative for this payout account""" + + date_of_birth: Optional[str] = None + """ + The date of birth of the business representative in ISO 8601 format + (YYYY-MM-DD). + """ + + first_name: Optional[str] = None + """The first name of the business representative.""" + + last_name: Optional[str] = None + """The last name of the business representative.""" + + middle_name: Optional[str] = None + """The middle name of the business representative.""" + + +class DataPayoutAccountDetailsLatestVerification(BaseModel): + """The latest verification for the connected account.""" + + id: str + """The numeric id of the verification record.""" + + last_error_code: Optional[VerificationErrorCode] = None + """An error code for a verification attempt.""" + + last_error_reason: Optional[str] = None + """A human-readable explanation of the most recent verification error. + + Null if no error has occurred. + """ + + status: VerificationStatus + """The current status of this verification session.""" + + +class DataPayoutAccountDetails(BaseModel): + """The payout account associated with the LedgerAccount, if any.""" + + id: str + """The unique identifier for the payout account.""" + + address: Optional[DataPayoutAccountDetailsAddress] = None + """The physical address associated with this payout account""" + + business_name: Optional[str] = None + """The company's legal name""" + + business_representative: Optional[DataPayoutAccountDetailsBusinessRepresentative] = None + """The business representative for this payout account""" + + email: Optional[str] = None + """The email address of the representative""" + + latest_verification: Optional[DataPayoutAccountDetailsLatestVerification] = None + """The latest verification for the connected account.""" + + phone: Optional[str] = None + """The business representative's phone""" + + status: Optional[PayoutAccountCalculatedStatuses] = None + """ + The granular calculated statuses reflecting payout account KYC and withdrawal + readiness. + """ + + +class DataTreasuryBalance(BaseModel): + """The balance cache associated with the account by currency.""" + + balance: float + """The amount of the balance.""" + + balance_usd: float + """The balance converted to USD.""" + + currency: Currency + """The currency of the balance.""" + + pending_balance: float + """The amount of the balance that is pending.""" + + reserve_balance: float + """The amount of the balance that is reserved.""" + + total_withdrawable_balance: float + """The amount of the balance that is withdrawable.""" + + +class Data(BaseModel): + """ + A ledger account represents a financial account on Whop that can hold many balances. + """ + + id: str + """The unique identifier for the ledger account.""" + + balances: List[DataBalance] + """The balances associated with the account.""" + + ledger_type: Literal["primary", "pool"] + """The type of ledger account.""" + + owner: DataOwner + """The owner of the ledger account.""" + + payments_approval_status: Optional[Literal["pending", "approved", "monitoring", "rejected"]] = None + """The different approval statuses an account can have.""" + + payout_account_details: Optional[DataPayoutAccountDetails] = None + """The payout account associated with the LedgerAccount, if any.""" + + transfer_fee: Optional[float] = None + """The fee for transfers, if applicable.""" + + treasury_balance: Optional[DataTreasuryBalance] = None + """The balance cache associated with the account by currency.""" + + +class LedgerAccountFundsAvailableWebhookEvent(BaseModel): + id: str + """A unique ID for every single webhook request""" + + api_version: Literal["v1"] + """The API version for this webhook""" + + data: Data + """ + A ledger account represents a financial account on Whop that can hold many + balances. + """ + + timestamp: datetime + """The timestamp in ISO 8601 format that the webhook was sent at on the server""" + + type: Literal["ledger_account.funds_available"] + """The webhook event type""" + + company_id: Optional[str] = None + """The company ID that this webhook event is associated with""" diff --git a/src/whop_sdk/types/unwrap_webhook_event.py b/src/whop_sdk/types/unwrap_webhook_event.py index dde154cc..8f6e21a6 100644 --- a/src/whop_sdk/types/unwrap_webhook_event.py +++ b/src/whop_sdk/types/unwrap_webhook_event.py @@ -36,6 +36,7 @@ from .setup_intent_requires_action_webhook_event import SetupIntentRequiresActionWebhookEvent from .identity_profile_needs_action_webhook_event import IdentityProfileNeedsActionWebhookEvent from .payout_account_status_updated_webhook_event import PayoutAccountStatusUpdatedWebhookEvent +from .ledger_account_funds_available_webhook_event import LedgerAccountFundsAvailableWebhookEvent from .resolution_center_case_created_webhook_event import ResolutionCenterCaseCreatedWebhookEvent from .resolution_center_case_decided_webhook_event import ResolutionCenterCaseDecidedWebhookEvent from .resolution_center_case_updated_webhook_event import ResolutionCenterCaseUpdatedWebhookEvent @@ -63,6 +64,7 @@ InvoicePaidWebhookEvent, InvoicePastDueWebhookEvent, InvoiceVoidedWebhookEvent, + LedgerAccountFundsAvailableWebhookEvent, MembershipActivatedWebhookEvent, MembershipCancelAtPeriodEndChangedWebhookEvent, MembershipDeactivatedWebhookEvent, diff --git a/src/whop_sdk/types/webhook_event.py b/src/whop_sdk/types/webhook_event.py index 3b76647a..c51bd170 100644 --- a/src/whop_sdk/types/webhook_event.py +++ b/src/whop_sdk/types/webhook_event.py @@ -19,6 +19,7 @@ "setup_intent.requires_action", "setup_intent.succeeded", "setup_intent.canceled", + "ledger_account.funds_available", "withdrawal.created", "withdrawal.updated", "course_lesson_interaction.completed", From 061313663f88bc7f00fc49529bb393d978565edf Mon Sep 17 00:00:00 2001 From: stlc-bot Date: Sat, 13 Jun 2026 03:02:12 +0000 Subject: [PATCH 042/109] Add `membership.trial_ending_soon` webhook (fires ~72h before a trial ends) Stainless-Generated-From: 8b515701a58b3b4e26d41a3976c2d428b3f7dba3 --- api.md | 1 + src/whop_sdk/types/__init__.py | 3 ++ ...bership_trial_ending_soon_webhook_event.py | 33 +++++++++++++++++++ src/whop_sdk/types/unwrap_webhook_event.py | 2 ++ src/whop_sdk/types/webhook_event.py | 1 + 5 files changed, 40 insertions(+) create mode 100644 src/whop_sdk/types/membership_trial_ending_soon_webhook_event.py diff --git a/api.md b/api.md index cae65e50..593e3c5f 100644 --- a/api.md +++ b/api.md @@ -174,6 +174,7 @@ from whop_sdk.types import ( MembershipActivatedWebhookEvent, MembershipCancelAtPeriodEndChangedWebhookEvent, MembershipDeactivatedWebhookEvent, + MembershipTrialEndingSoonWebhookEvent, PaymentCreatedWebhookEvent, PaymentFailedWebhookEvent, PaymentPendingWebhookEvent, diff --git a/src/whop_sdk/types/__init__.py b/src/whop_sdk/types/__init__.py index 7e85327c..cfef4e8a 100644 --- a/src/whop_sdk/types/__init__.py +++ b/src/whop_sdk/types/__init__.py @@ -452,6 +452,9 @@ from .invoice_marked_uncollectible_webhook_event import ( InvoiceMarkedUncollectibleWebhookEvent as InvoiceMarkedUncollectibleWebhookEvent, ) +from .membership_trial_ending_soon_webhook_event import ( + MembershipTrialEndingSoonWebhookEvent as MembershipTrialEndingSoonWebhookEvent, +) from .setup_intent_requires_action_webhook_event import ( SetupIntentRequiresActionWebhookEvent as SetupIntentRequiresActionWebhookEvent, ) diff --git a/src/whop_sdk/types/membership_trial_ending_soon_webhook_event.py b/src/whop_sdk/types/membership_trial_ending_soon_webhook_event.py new file mode 100644 index 00000000..fc6d3b8e --- /dev/null +++ b/src/whop_sdk/types/membership_trial_ending_soon_webhook_event.py @@ -0,0 +1,33 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from datetime import datetime +from typing_extensions import Literal + +from .._models import BaseModel +from .shared.membership import Membership + +__all__ = ["MembershipTrialEndingSoonWebhookEvent"] + + +class MembershipTrialEndingSoonWebhookEvent(BaseModel): + id: str + """A unique ID for every single webhook request""" + + api_version: Literal["v1"] + """The API version for this webhook""" + + data: Membership + """A membership represents an active relationship between a user and a product. + + It tracks the user's access, billing status, and renewal schedule. + """ + + timestamp: datetime + """The timestamp in ISO 8601 format that the webhook was sent at on the server""" + + type: Literal["membership.trial_ending_soon"] + """The webhook event type""" + + company_id: Optional[str] = None + """The company ID that this webhook event is associated with""" diff --git a/src/whop_sdk/types/unwrap_webhook_event.py b/src/whop_sdk/types/unwrap_webhook_event.py index 8f6e21a6..b21ff447 100644 --- a/src/whop_sdk/types/unwrap_webhook_event.py +++ b/src/whop_sdk/types/unwrap_webhook_event.py @@ -33,6 +33,7 @@ from .identity_profile_approved_webhook_event import IdentityProfileApprovedWebhookEvent from .identity_profile_rejected_webhook_event import IdentityProfileRejectedWebhookEvent from .invoice_marked_uncollectible_webhook_event import InvoiceMarkedUncollectibleWebhookEvent +from .membership_trial_ending_soon_webhook_event import MembershipTrialEndingSoonWebhookEvent from .setup_intent_requires_action_webhook_event import SetupIntentRequiresActionWebhookEvent from .identity_profile_needs_action_webhook_event import IdentityProfileNeedsActionWebhookEvent from .payout_account_status_updated_webhook_event import PayoutAccountStatusUpdatedWebhookEvent @@ -68,6 +69,7 @@ MembershipActivatedWebhookEvent, MembershipCancelAtPeriodEndChangedWebhookEvent, MembershipDeactivatedWebhookEvent, + MembershipTrialEndingSoonWebhookEvent, PaymentCreatedWebhookEvent, PaymentFailedWebhookEvent, PaymentPendingWebhookEvent, diff --git a/src/whop_sdk/types/webhook_event.py b/src/whop_sdk/types/webhook_event.py index c51bd170..da9b387d 100644 --- a/src/whop_sdk/types/webhook_event.py +++ b/src/whop_sdk/types/webhook_event.py @@ -12,6 +12,7 @@ "invoice.voided", "membership.activated", "membership.deactivated", + "membership.trial_ending_soon", "entry.created", "entry.approved", "entry.denied", From 98be847a374048e83ada9deb1dce3b08b4565b5e Mon Sep 17 00:00:00 2001 From: stlc-bot Date: Sun, 14 Jun 2026 18:35:54 +0000 Subject: [PATCH 043/109] Prevent ads Meta-sync from crashing on over-long campaign/ad-set/ad names Stainless-Generated-From: b1ee62861139cc3a911fde6179234705eb60fe59 --- src/whop_sdk/resources/ad_groups.py | 4 ++-- src/whop_sdk/types/ad_group_update_params.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/whop_sdk/resources/ad_groups.py b/src/whop_sdk/resources/ad_groups.py index 851fba8c..73bb01cf 100644 --- a/src/whop_sdk/resources/ad_groups.py +++ b/src/whop_sdk/resources/ad_groups.py @@ -134,7 +134,7 @@ def update( budget: Budget amount in dollars. The interpretation (daily or lifetime) follows the ad group's existing budget type. - title: Human-readable ad group title. + title: Human-readable ad group title. Max 255 characters. extra_headers: Send extra headers @@ -508,7 +508,7 @@ async def update( budget: Budget amount in dollars. The interpretation (daily or lifetime) follows the ad group's existing budget type. - title: Human-readable ad group title. + title: Human-readable ad group title. Max 255 characters. extra_headers: Send extra headers diff --git a/src/whop_sdk/types/ad_group_update_params.py b/src/whop_sdk/types/ad_group_update_params.py index 649dac4f..71db1f9b 100644 --- a/src/whop_sdk/types/ad_group_update_params.py +++ b/src/whop_sdk/types/ad_group_update_params.py @@ -17,4 +17,4 @@ class AdGroupUpdateParams(TypedDict, total=False): """ title: Optional[str] - """Human-readable ad group title.""" + """Human-readable ad group title. Max 255 characters.""" From 745a701a1df08670c8db5d48bf3a8095d0bf9d28 Mon Sep 17 00:00:00 2001 From: stlc-bot Date: Sun, 14 Jun 2026 22:16:54 +0000 Subject: [PATCH 044/109] Add public GET /api/v1/cards endpoint for listing issued Whop Cards Stainless-Generated-From: b67574515f3d324e3491d0758fa37038ad316d13 --- .stats.yml | 2 +- api.md | 12 ++ src/whop_sdk/_client.py | 38 ++++ src/whop_sdk/resources/__init__.py | 14 ++ src/whop_sdk/resources/cards.py | 223 +++++++++++++++++++++++ src/whop_sdk/types/__init__.py | 2 + src/whop_sdk/types/card_list_params.py | 24 +++ src/whop_sdk/types/card_list_response.py | 100 ++++++++++ tests/api_resources/test_cards.py | 102 +++++++++++ 9 files changed, 516 insertions(+), 1 deletion(-) create mode 100644 src/whop_sdk/resources/cards.py create mode 100644 src/whop_sdk/types/card_list_params.py create mode 100644 src/whop_sdk/types/card_list_response.py create mode 100644 tests/api_resources/test_cards.py diff --git a/.stats.yml b/.stats.yml index 9d0edd97..5aab750e 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1 +1 @@ -configured_endpoints: 232 +configured_endpoints: 233 diff --git a/api.md b/api.md index 593e3c5f..558d0767 100644 --- a/api.md +++ b/api.md @@ -764,6 +764,18 @@ Methods: - client.payouts.list(\*\*params) -> SyncCursorPage[PayoutListResponse] +# Cards + +Types: + +```python +from whop_sdk.types import CardListResponse +``` + +Methods: + +- client.cards.list(\*\*params) -> CardListResponse + # Swaps Types: diff --git a/src/whop_sdk/_client.py b/src/whop_sdk/_client.py index f23f4e9e..b45b8a6d 100644 --- a/src/whop_sdk/_client.py +++ b/src/whop_sdk/_client.py @@ -38,6 +38,7 @@ from .resources import ( ads, apps, + cards, files, leads, plans, @@ -104,6 +105,7 @@ ) from .resources.ads import AdsResource, AsyncAdsResource from .resources.apps import AppsResource, AsyncAppsResource + from .resources.cards import CardsResource, AsyncCardsResource from .resources.files import FilesResource, AsyncFilesResource from .resources.leads import LeadsResource, AsyncLeadsResource from .resources.plans import PlansResource, AsyncPlansResource @@ -543,6 +545,12 @@ def payouts(self) -> PayoutsResource: return PayoutsResource(self) + @cached_property + def cards(self) -> CardsResource: + from .resources.cards import CardsResource + + return CardsResource(self) + @cached_property def swaps(self) -> SwapsResource: from .resources.swaps import SwapsResource @@ -1196,6 +1204,12 @@ def payouts(self) -> AsyncPayoutsResource: return AsyncPayoutsResource(self) + @cached_property + def cards(self) -> AsyncCardsResource: + from .resources.cards import AsyncCardsResource + + return AsyncCardsResource(self) + @cached_property def swaps(self) -> AsyncSwapsResource: from .resources.swaps import AsyncSwapsResource @@ -1769,6 +1783,12 @@ def payouts(self) -> payouts.PayoutsResourceWithRawResponse: return PayoutsResourceWithRawResponse(self._client.payouts) + @cached_property + def cards(self) -> cards.CardsResourceWithRawResponse: + from .resources.cards import CardsResourceWithRawResponse + + return CardsResourceWithRawResponse(self._client.cards) + @cached_property def swaps(self) -> swaps.SwapsResourceWithRawResponse: from .resources.swaps import SwapsResourceWithRawResponse @@ -2224,6 +2244,12 @@ def payouts(self) -> payouts.AsyncPayoutsResourceWithRawResponse: return AsyncPayoutsResourceWithRawResponse(self._client.payouts) + @cached_property + def cards(self) -> cards.AsyncCardsResourceWithRawResponse: + from .resources.cards import AsyncCardsResourceWithRawResponse + + return AsyncCardsResourceWithRawResponse(self._client.cards) + @cached_property def swaps(self) -> swaps.AsyncSwapsResourceWithRawResponse: from .resources.swaps import AsyncSwapsResourceWithRawResponse @@ -2681,6 +2707,12 @@ def payouts(self) -> payouts.PayoutsResourceWithStreamingResponse: return PayoutsResourceWithStreamingResponse(self._client.payouts) + @cached_property + def cards(self) -> cards.CardsResourceWithStreamingResponse: + from .resources.cards import CardsResourceWithStreamingResponse + + return CardsResourceWithStreamingResponse(self._client.cards) + @cached_property def swaps(self) -> swaps.SwapsResourceWithStreamingResponse: from .resources.swaps import SwapsResourceWithStreamingResponse @@ -3140,6 +3172,12 @@ def payouts(self) -> payouts.AsyncPayoutsResourceWithStreamingResponse: return AsyncPayoutsResourceWithStreamingResponse(self._client.payouts) + @cached_property + def cards(self) -> cards.AsyncCardsResourceWithStreamingResponse: + from .resources.cards import AsyncCardsResourceWithStreamingResponse + + return AsyncCardsResourceWithStreamingResponse(self._client.cards) + @cached_property def swaps(self) -> swaps.AsyncSwapsResourceWithStreamingResponse: from .resources.swaps import AsyncSwapsResourceWithStreamingResponse diff --git a/src/whop_sdk/resources/__init__.py b/src/whop_sdk/resources/__init__.py index 0e8ba666..e056c88e 100644 --- a/src/whop_sdk/resources/__init__.py +++ b/src/whop_sdk/resources/__init__.py @@ -16,6 +16,14 @@ AppsResourceWithStreamingResponse, AsyncAppsResourceWithStreamingResponse, ) +from .cards import ( + CardsResource, + AsyncCardsResource, + CardsResourceWithRawResponse, + AsyncCardsResourceWithRawResponse, + CardsResourceWithStreamingResponse, + AsyncCardsResourceWithStreamingResponse, +) from .files import ( FilesResource, AsyncFilesResource, @@ -768,6 +776,12 @@ "AsyncPayoutsResourceWithRawResponse", "PayoutsResourceWithStreamingResponse", "AsyncPayoutsResourceWithStreamingResponse", + "CardsResource", + "AsyncCardsResource", + "CardsResourceWithRawResponse", + "AsyncCardsResourceWithRawResponse", + "CardsResourceWithStreamingResponse", + "AsyncCardsResourceWithStreamingResponse", "SwapsResource", "AsyncSwapsResource", "SwapsResourceWithRawResponse", diff --git a/src/whop_sdk/resources/cards.py b/src/whop_sdk/resources/cards.py new file mode 100644 index 00000000..59fe1107 --- /dev/null +++ b/src/whop_sdk/resources/cards.py @@ -0,0 +1,223 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import httpx + +from ..types import card_list_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.card_list_response import CardListResponse + +__all__ = ["CardsResource", "AsyncCardsResource"] + + +class CardsResource(SyncAPIResource): + @cached_property + def with_raw_response(self) -> CardsResourceWithRawResponse: + """ + 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 CardsResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> CardsResourceWithStreamingResponse: + """ + 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 CardsResourceWithStreamingResponse(self) + + def list( + self, + *, + account_id: str | Omit = omit, + card_id: str | Omit = omit, + reveal_secrets: bool | Omit = omit, + user_id: 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, + ) -> CardListResponse: + """ + Lists the issued (Whop Card) virtual and physical cards for a ledger account, + including pending invitation cards that have not been issued by the card + provider yet. The ledger's owner is passed as exactly one of account*id (a biz* + identifier) or user*id (a user* identifier). Pass card_id to address a single + card. Pass reveal_secrets=true to include the full card number and CVC for + active cards. Non-owner team members only see cards assigned to them. Users + without the payout:account:read scope can still list cards assigned to them (for + example moderators or external cardholders). + + Args: + account_id: The owning account ID (a biz\\__ identifier). Provide this or user_id. + + card_id: An icrd\\__ identifier. When provided, only that card is returned. + + reveal_secrets: When true, each active card includes a secrets object with the full card number + (pan), cvc, and cardholder name. + + user_id: The owning user ID (a user\\__ identifier). Provide this or account_id. + + 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( + "/cards", + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "account_id": account_id, + "card_id": card_id, + "reveal_secrets": reveal_secrets, + "user_id": user_id, + }, + card_list_params.CardListParams, + ), + ), + cast_to=CardListResponse, + ) + + +class AsyncCardsResource(AsyncAPIResource): + @cached_property + def with_raw_response(self) -> AsyncCardsResourceWithRawResponse: + """ + 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 AsyncCardsResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncCardsResourceWithStreamingResponse: + """ + 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 AsyncCardsResourceWithStreamingResponse(self) + + async def list( + self, + *, + account_id: str | Omit = omit, + card_id: str | Omit = omit, + reveal_secrets: bool | Omit = omit, + user_id: 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, + ) -> CardListResponse: + """ + Lists the issued (Whop Card) virtual and physical cards for a ledger account, + including pending invitation cards that have not been issued by the card + provider yet. The ledger's owner is passed as exactly one of account*id (a biz* + identifier) or user*id (a user* identifier). Pass card_id to address a single + card. Pass reveal_secrets=true to include the full card number and CVC for + active cards. Non-owner team members only see cards assigned to them. Users + without the payout:account:read scope can still list cards assigned to them (for + example moderators or external cardholders). + + Args: + account_id: The owning account ID (a biz\\__ identifier). Provide this or user_id. + + card_id: An icrd\\__ identifier. When provided, only that card is returned. + + reveal_secrets: When true, each active card includes a secrets object with the full card number + (pan), cvc, and cardholder name. + + user_id: The owning user ID (a user\\__ identifier). Provide this or account_id. + + 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( + "/cards", + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=await async_maybe_transform( + { + "account_id": account_id, + "card_id": card_id, + "reveal_secrets": reveal_secrets, + "user_id": user_id, + }, + card_list_params.CardListParams, + ), + ), + cast_to=CardListResponse, + ) + + +class CardsResourceWithRawResponse: + def __init__(self, cards: CardsResource) -> None: + self._cards = cards + + self.list = to_raw_response_wrapper( + cards.list, + ) + + +class AsyncCardsResourceWithRawResponse: + def __init__(self, cards: AsyncCardsResource) -> None: + self._cards = cards + + self.list = async_to_raw_response_wrapper( + cards.list, + ) + + +class CardsResourceWithStreamingResponse: + def __init__(self, cards: CardsResource) -> None: + self._cards = cards + + self.list = to_streamed_response_wrapper( + cards.list, + ) + + +class AsyncCardsResourceWithStreamingResponse: + def __init__(self, cards: AsyncCardsResource) -> None: + self._cards = cards + + self.list = async_to_streamed_response_wrapper( + cards.list, + ) diff --git a/src/whop_sdk/types/__init__.py b/src/whop_sdk/types/__init__.py index cfef4e8a..b466c2c8 100644 --- a/src/whop_sdk/types/__init__.py +++ b/src/whop_sdk/types/__init__.py @@ -106,6 +106,7 @@ from .fee_markup_type import FeeMarkupType as FeeMarkupType from .file_visibility import FileVisibility as FileVisibility from .ad_list_response import AdListResponse as AdListResponse +from .card_list_params import CardListParams as CardListParams from .dispute_statuses import DisputeStatuses as DisputeStatuses from .lead_list_params import LeadListParams as LeadListParams from .payment_provider import PaymentProvider as PaymentProvider @@ -124,6 +125,7 @@ from .ad_campaign_status import AdCampaignStatus as AdCampaignStatus from .ad_retrieve_params import AdRetrieveParams as AdRetrieveParams from .bounty_list_params import BountyListParams as BountyListParams +from .card_list_response import CardListResponse as CardListResponse from .course_list_params import CourseListParams as CourseListParams from .dispute_alert_type import DisputeAlertType as DisputeAlertType from .external_ad_status import ExternalAdStatus as ExternalAdStatus diff --git a/src/whop_sdk/types/card_list_params.py b/src/whop_sdk/types/card_list_params.py new file mode 100644 index 00000000..77f65474 --- /dev/null +++ b/src/whop_sdk/types/card_list_params.py @@ -0,0 +1,24 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import TypedDict + +__all__ = ["CardListParams"] + + +class CardListParams(TypedDict, total=False): + account_id: str + """The owning account ID (a biz\\__ identifier). Provide this or user_id.""" + + card_id: str + """An icrd\\__ identifier. When provided, only that card is returned.""" + + reveal_secrets: bool + """ + When true, each active card includes a secrets object with the full card number + (pan), cvc, and cardholder name. + """ + + user_id: str + """The owning user ID (a user\\__ identifier). Provide this or account_id.""" diff --git a/src/whop_sdk/types/card_list_response.py b/src/whop_sdk/types/card_list_response.py new file mode 100644 index 00000000..c7c82c86 --- /dev/null +++ b/src/whop_sdk/types/card_list_response.py @@ -0,0 +1,100 @@ +# 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__ = ["CardListResponse", "Data", "DataBilling", "DataLimit", "DataSecrets"] + + +class DataBilling(BaseModel): + """The billing address.""" + + city: Optional[str] = None + + country_code: Optional[str] = None + + line1: Optional[str] = None + + line2: Optional[str] = None + + postal_code: Optional[str] = None + + region: Optional[str] = None + + +class DataLimit(BaseModel): + """The spending limit configuration.""" + + amount: int + """The limit amount in cents.""" + + frequency: str + """The limit window, for example per24HourPeriod or perAuthorization.""" + + +class DataSecrets(BaseModel): + """The card's sensitive details. + + Only present when reveal_secrets=true; null for cards that are not active or whose details could not be retrieved. + """ + + cvc: str + """The card verification code.""" + + name_on_card: Optional[str] = None + """The cardholder name printed on the card.""" + + pan: str + """The full card number.""" + + +class Data(BaseModel): + id: str + """The icrd\\__ identifier of the card.""" + + billing: Optional[DataBilling] = None + """The billing address.""" + + canceled_at: Optional[datetime] = None + + created_at: Optional[datetime] = None + + expiration_month: Optional[str] = None + + expiration_year: Optional[str] = None + + last4: Optional[str] = None + """The last 4 digits of the card number. Null for pending invitation cards.""" + + limit: Optional[DataLimit] = None + """The spending limit configuration.""" + + name: Optional[str] = None + + object: Literal["card"] + + spent_last_month_cents: Optional[int] = None + """Total spend in the last 30 days, in cents.""" + + status: Optional[str] = None + """The card status, for example active, frozen, canceled, or invited.""" + + type: Optional[Literal["virtual", "physical"]] = None + """The card type.""" + + user_id: Optional[str] = None + """The user\\__ identifier of the cardholder, when assigned.""" + + secrets: Optional[DataSecrets] = None + """The card's sensitive details. + + Only present when reveal_secrets=true; null for cards that are not active or + whose details could not be retrieved. + """ + + +class CardListResponse(BaseModel): + data: List[Data] diff --git a/tests/api_resources/test_cards.py b/tests/api_resources/test_cards.py new file mode 100644 index 00000000..d644667b --- /dev/null +++ b/tests/api_resources/test_cards.py @@ -0,0 +1,102 @@ +# 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 CardListResponse + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestCards: + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_list(self, client: Whop) -> None: + card = client.cards.list() + assert_matches_type(CardListResponse, card, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_list_with_all_params(self, client: Whop) -> None: + card = client.cards.list( + account_id="account_id", + card_id="card_id", + reveal_secrets=True, + user_id="user_id", + ) + assert_matches_type(CardListResponse, card, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_raw_response_list(self, client: Whop) -> None: + response = client.cards.with_raw_response.list() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + card = response.parse() + assert_matches_type(CardListResponse, card, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_streaming_response_list(self, client: Whop) -> None: + with client.cards.with_streaming_response.list() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + card = response.parse() + assert_matches_type(CardListResponse, card, path=["response"]) + + assert cast(Any, response.is_closed) is True + + +class TestAsyncCards: + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_list(self, async_client: AsyncWhop) -> None: + card = await async_client.cards.list() + assert_matches_type(CardListResponse, card, 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: + card = await async_client.cards.list( + account_id="account_id", + card_id="card_id", + reveal_secrets=True, + user_id="user_id", + ) + assert_matches_type(CardListResponse, card, 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.cards.with_raw_response.list() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + card = await response.parse() + assert_matches_type(CardListResponse, card, 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.cards.with_streaming_response.list() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + card = await response.parse() + assert_matches_type(CardListResponse, card, path=["response"]) + + assert cast(Any, response.is_closed) is True From 9d549938871524f921bbe8056c7c5e8cf670f6d6 Mon Sep 17 00:00:00 2001 From: stlc-bot Date: Sun, 14 Jun 2026 23:36:24 +0000 Subject: [PATCH 045/109] feat(payments): add settlement-date dimension to financial-activity API Stainless-Generated-From: f7c8318259bd32e8e1942794c5f0b4bf2976a961 --- src/whop_sdk/resources/financial_activity.py | 24 ++++++++++++++++++- .../types/financial_activity_list_params.py | 15 +++++++++++- .../types/financial_activity_list_response.py | 10 ++++++++ .../api_resources/test_financial_activity.py | 6 ++++- 4 files changed, 52 insertions(+), 3 deletions(-) diff --git a/src/whop_sdk/resources/financial_activity.py b/src/whop_sdk/resources/financial_activity.py index 9867d947..90a4e652 100644 --- a/src/whop_sdk/resources/financial_activity.py +++ b/src/whop_sdk/resources/financial_activity.py @@ -3,7 +3,7 @@ from __future__ import annotations from typing import Union -from datetime import datetime +from datetime import date, datetime import httpx @@ -48,6 +48,8 @@ def list( self, *, account_id: str | Omit = omit, + available_after: Union[str, date] | Omit = omit, + available_before: Union[str, date] | Omit = omit, currency: str | Omit = omit, cursor: str | Omit = omit, limit: int | Omit = omit, @@ -72,6 +74,13 @@ def list( Args: account_id: The owning account ID (a biz\\__ identifier). Provide this or user_id. + available_after: Only include rows whose funds became withdrawable on or after this YYYY-MM-DD + settlement date (UTC), distinct from posted_at. Requires currency. + + available_before: Only include rows whose funds became withdrawable on or before this YYYY-MM-DD + settlement date (UTC). Set equal to available_after for a single day. Requires + currency. + currency: Optional currency code filter, for example usd. cursor: Cursor returned by the previous page. @@ -106,6 +115,8 @@ def list( query=maybe_transform( { "account_id": account_id, + "available_after": available_after, + "available_before": available_before, "currency": currency, "cursor": cursor, "limit": limit, @@ -145,6 +156,8 @@ async def list( self, *, account_id: str | Omit = omit, + available_after: Union[str, date] | Omit = omit, + available_before: Union[str, date] | Omit = omit, currency: str | Omit = omit, cursor: str | Omit = omit, limit: int | Omit = omit, @@ -169,6 +182,13 @@ async def list( Args: account_id: The owning account ID (a biz\\__ identifier). Provide this or user_id. + available_after: Only include rows whose funds became withdrawable on or after this YYYY-MM-DD + settlement date (UTC), distinct from posted_at. Requires currency. + + available_before: Only include rows whose funds became withdrawable on or before this YYYY-MM-DD + settlement date (UTC). Set equal to available_after for a single day. Requires + currency. + currency: Optional currency code filter, for example usd. cursor: Cursor returned by the previous page. @@ -203,6 +223,8 @@ async def list( query=await async_maybe_transform( { "account_id": account_id, + "available_after": available_after, + "available_before": available_before, "currency": currency, "cursor": cursor, "limit": limit, diff --git a/src/whop_sdk/types/financial_activity_list_params.py b/src/whop_sdk/types/financial_activity_list_params.py index 8d1b4392..623ab738 100644 --- a/src/whop_sdk/types/financial_activity_list_params.py +++ b/src/whop_sdk/types/financial_activity_list_params.py @@ -3,7 +3,7 @@ from __future__ import annotations from typing import Union -from datetime import datetime +from datetime import date, datetime from typing_extensions import Annotated, TypedDict from .._types import SequenceNotStr @@ -16,6 +16,19 @@ class FinancialActivityListParams(TypedDict, total=False): account_id: str """The owning account ID (a biz\\__ identifier). Provide this or user_id.""" + available_after: Annotated[Union[str, date], PropertyInfo(format="iso8601")] + """ + Only include rows whose funds became withdrawable on or after this YYYY-MM-DD + settlement date (UTC), distinct from posted_at. Requires currency. + """ + + available_before: Annotated[Union[str, date], PropertyInfo(format="iso8601")] + """ + Only include rows whose funds became withdrawable on or before this YYYY-MM-DD + settlement date (UTC). Set equal to available_after for a single day. Requires + currency. + """ + currency: str """Optional currency code filter, for example usd.""" diff --git a/src/whop_sdk/types/financial_activity_list_response.py b/src/whop_sdk/types/financial_activity_list_response.py index f9940d02..849c4f88 100644 --- a/src/whop_sdk/types/financial_activity_list_response.py +++ b/src/whop_sdk/types/financial_activity_list_response.py @@ -278,6 +278,16 @@ class Data(BaseModel): amount: str """Signed amount in the currency's smallest precision units.""" + available_at: Optional[datetime] = None + """ + ISO 8601 timestamp these funds became (or are scheduled to become) withdrawable: + the posted time for already-settled funds, or 00:00:00 UTC on the scheduled + release date for pending funds. Present only on inflows entering the balance + (payments, top-ups, incoming transfers/affiliate); null on withdrawals, refunds, + disputes and on-chain rows. The available_after/before filters window on its UTC + settlement date. + """ + created_at: Optional[datetime] = None currency: DataCurrency diff --git a/tests/api_resources/test_financial_activity.py b/tests/api_resources/test_financial_activity.py index 957a46d6..985c2c85 100644 --- a/tests/api_resources/test_financial_activity.py +++ b/tests/api_resources/test_financial_activity.py @@ -10,7 +10,7 @@ from whop_sdk import Whop, AsyncWhop from tests.utils import assert_matches_type from whop_sdk.types import FinancialActivityListResponse -from whop_sdk._utils import parse_datetime +from whop_sdk._utils import parse_date, parse_datetime base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") @@ -29,6 +29,8 @@ def test_method_list(self, client: Whop) -> None: def test_method_list_with_all_params(self, client: Whop) -> None: financial_activity = client.financial_activity.list( account_id="account_id", + available_after=parse_date("2019-12-27"), + available_before=parse_date("2019-12-27"), currency="currency", cursor="cursor", limit=100, @@ -78,6 +80,8 @@ async def test_method_list(self, async_client: AsyncWhop) -> None: async def test_method_list_with_all_params(self, async_client: AsyncWhop) -> None: financial_activity = await async_client.financial_activity.list( account_id="account_id", + available_after=parse_date("2019-12-27"), + available_before=parse_date("2019-12-27"), currency="currency", cursor="cursor", limit=100, From 3b931110c1a63e327f6cc54e69464b330a386914 Mon Sep 17 00:00:00 2001 From: stlc-bot Date: Mon, 15 Jun 2026 19:55:39 +0000 Subject: [PATCH 046/109] docs(api): replace "company" with "account" in Experimental API surface Stainless-Generated-From: 1af6e3df4ff05cefe0390f6132b7a2adab8a4924 --- src/whop_sdk/resources/transfers.py | 4 ++-- src/whop_sdk/resources/users.py | 8 ++++---- src/whop_sdk/types/transfer_create_params.py | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/whop_sdk/resources/transfers.py b/src/whop_sdk/resources/transfers.py index b017736c..e44d4506 100644 --- a/src/whop_sdk/resources/transfers.py +++ b/src/whop_sdk/resources/transfers.py @@ -79,7 +79,7 @@ def create( Args: amount: The amount to move, in the transfer currency. For example 25.00. - origin_id: The account sending the funds. A user ID (user_xxx), company ID (biz_xxx), or + origin_id: The account sending the funds. A user ID (user_xxx), account ID (biz_xxx), or ledger account ID (ldgr_xxx). currency: The currency, such as usd. Required for ledger transfers. @@ -302,7 +302,7 @@ async def create( Args: amount: The amount to move, in the transfer currency. For example 25.00. - origin_id: The account sending the funds. A user ID (user_xxx), company ID (biz_xxx), or + origin_id: The account sending the funds. A user ID (user_xxx), account ID (biz_xxx), or ledger account ID (ldgr_xxx). currency: The currency, such as usd. Required for ledger transfers. diff --git a/src/whop_sdk/resources/users.py b/src/whop_sdk/resources/users.py index 39c16355..0393836e 100644 --- a/src/whop_sdk/resources/users.py +++ b/src/whop_sdk/resources/users.py @@ -213,8 +213,8 @@ def check_access( timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> UserCheckAccessResponse: """ - Checks whether a user has access to a company, product, or experience the caller - can reach. + Checks whether a user has access to an account, product, or experience the + caller can reach. Args: extra_headers: Send extra headers @@ -480,8 +480,8 @@ async def check_access( timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> UserCheckAccessResponse: """ - Checks whether a user has access to a company, product, or experience the caller - can reach. + Checks whether a user has access to an account, product, or experience the + caller can reach. Args: extra_headers: Send extra headers diff --git a/src/whop_sdk/types/transfer_create_params.py b/src/whop_sdk/types/transfer_create_params.py index 04f239f7..4d028a14 100644 --- a/src/whop_sdk/types/transfer_create_params.py +++ b/src/whop_sdk/types/transfer_create_params.py @@ -18,7 +18,7 @@ class TransferCreateParams(TypedDict, total=False): origin_id: Required[str] """The account sending the funds. - A user ID (user_xxx), company ID (biz_xxx), or ledger account ID (ldgr_xxx). + A user ID (user_xxx), account ID (biz_xxx), or ledger account ID (ldgr_xxx). """ currency: str From 341bf9a7e9409b67db3ec3582d396be04415dd1d Mon Sep 17 00:00:00 2001 From: stlc-bot Date: Mon, 15 Jun 2026 21:50:55 +0000 Subject: [PATCH 047/109] docs(metadata): fix API metadata limits (100 chars/key, 500 chars/value) Stainless-Generated-From: f180d07373b938e5c0b2a5ad53fae38982aecef6 --- src/whop_sdk/resources/plans.py | 12 ++++++++---- src/whop_sdk/resources/transfers.py | 6 ++++-- src/whop_sdk/types/membership_list_response.py | 8 +++++--- src/whop_sdk/types/payment_list_response.py | 6 ++++-- src/whop_sdk/types/plan_create_params.py | 3 ++- src/whop_sdk/types/plan_update_params.py | 3 ++- src/whop_sdk/types/refund_created_webhook_event.py | 6 ++++-- src/whop_sdk/types/refund_retrieve_response.py | 6 ++++-- src/whop_sdk/types/refund_updated_webhook_event.py | 6 ++++-- src/whop_sdk/types/shared/membership.py | 8 +++++--- src/whop_sdk/types/shared/payment.py | 6 ++++-- src/whop_sdk/types/shared/product.py | 3 ++- src/whop_sdk/types/shared/product_list_item.py | 3 ++- src/whop_sdk/types/transfer_create_params.py | 6 +++++- 14 files changed, 55 insertions(+), 27 deletions(-) diff --git a/src/whop_sdk/resources/plans.py b/src/whop_sdk/resources/plans.py index 9f6f5b91..59c15215 100644 --- a/src/whop_sdk/resources/plans.py +++ b/src/whop_sdk/resources/plans.py @@ -119,7 +119,8 @@ def create( legacy_payment_method_controls: Whether this plan uses legacy payment method controls. metadata: Custom key-value pairs to store on the plan. Included in webhook payloads for - payment and membership events. + payment and membership events. Max 50 keys, 100 chars per key, 500 chars per + string value. override_tax_type: Override the default tax classification for this specific plan. @@ -296,7 +297,8 @@ def update( legacy_payment_method_controls: Whether this plan uses legacy payment method controls. metadata: Custom key-value pairs to store on the plan. Included in webhook payloads for - payment and membership events. + payment and membership events. Max 50 keys, 100 chars per key, 500 chars per + string value. offer_cancel_discount: Whether to offer a retention discount when a customer attempts to cancel. @@ -643,7 +645,8 @@ async def create( legacy_payment_method_controls: Whether this plan uses legacy payment method controls. metadata: Custom key-value pairs to store on the plan. Included in webhook payloads for - payment and membership events. + payment and membership events. Max 50 keys, 100 chars per key, 500 chars per + string value. override_tax_type: Override the default tax classification for this specific plan. @@ -820,7 +823,8 @@ async def update( legacy_payment_method_controls: Whether this plan uses legacy payment method controls. metadata: Custom key-value pairs to store on the plan. Included in webhook payloads for - payment and membership events. + payment and membership events. Max 50 keys, 100 chars per key, 500 chars per + string value. offer_cancel_discount: Whether to offer a retention discount when a customer attempts to cancel. diff --git a/src/whop_sdk/resources/transfers.py b/src/whop_sdk/resources/transfers.py index e44d4506..4472c431 100644 --- a/src/whop_sdk/resources/transfers.py +++ b/src/whop_sdk/resources/transfers.py @@ -92,7 +92,8 @@ def create( idempotence_key: Ledger transfers only. A unique key to prevent duplicate transfers. - metadata: Ledger transfers only. Custom key-value pairs attached to the transfer. + metadata: Ledger transfers only. Custom key-value pairs attached to the transfer. Max 50 + keys, 100 chars per key, 500 chars per string value. notes: Ledger transfers only. A short note describing the transfer. @@ -315,7 +316,8 @@ async def create( idempotence_key: Ledger transfers only. A unique key to prevent duplicate transfers. - metadata: Ledger transfers only. Custom key-value pairs attached to the transfer. + metadata: Ledger transfers only. Custom key-value pairs attached to the transfer. Max 50 + keys, 100 chars per key, 500 chars per string value. notes: Ledger transfers only. A short note describing the transfer. diff --git a/src/whop_sdk/types/membership_list_response.py b/src/whop_sdk/types/membership_list_response.py index 89bccb93..2910aad0 100644 --- a/src/whop_sdk/types/membership_list_response.py +++ b/src/whop_sdk/types/membership_list_response.py @@ -40,7 +40,8 @@ class Plan(BaseModel): metadata: Optional[Dict[str, object]] = None """Custom key-value pairs stored on the plan. - Included in webhook payloads for payment and membership events. + Included in webhook payloads for payment and membership events. Max 50 keys, 100 + chars per key, 500 chars per string value. """ @@ -53,7 +54,8 @@ class Product(BaseModel): metadata: Optional[Dict[str, object]] = None """Custom key-value pairs stored on the product. - Included in webhook payloads for payment and membership events. + Included in webhook payloads for payment and membership events. Max 50 keys, 100 + chars per key, 500 chars per string value. """ title: str @@ -170,7 +172,7 @@ class MembershipListResponse(BaseModel): metadata: Optional[Dict[str, object]] = None """ Custom key-value pairs for the membership (commonly used for software licensing, - e.g., HWID). Max 50 keys, 500 chars per key, 5000 chars per value. + e.g., HWID). Max 50 keys, 100 chars per key, 500 chars per string value. """ payment_collection_paused: bool diff --git a/src/whop_sdk/types/payment_list_response.py b/src/whop_sdk/types/payment_list_response.py index 44a2275d..25f09d10 100644 --- a/src/whop_sdk/types/payment_list_response.py +++ b/src/whop_sdk/types/payment_list_response.py @@ -167,7 +167,8 @@ class Plan(BaseModel): metadata: Optional[Dict[str, object]] = None """Custom key-value pairs stored on the plan. - Included in webhook payloads for payment and membership events. + Included in webhook payloads for payment and membership events. Max 50 keys, 100 + chars per key, 500 chars per string value. """ @@ -180,7 +181,8 @@ class Product(BaseModel): metadata: Optional[Dict[str, object]] = None """Custom key-value pairs stored on the product. - Included in webhook payloads for payment and membership events. + Included in webhook payloads for payment and membership events. Max 50 keys, 100 + chars per key, 500 chars per string value. """ route: str diff --git a/src/whop_sdk/types/plan_create_params.py b/src/whop_sdk/types/plan_create_params.py index 350aec97..6e742c65 100644 --- a/src/whop_sdk/types/plan_create_params.py +++ b/src/whop_sdk/types/plan_create_params.py @@ -62,7 +62,8 @@ class PlanCreateParams(TypedDict, total=False): metadata: Optional[object] """Custom key-value pairs to store on the plan. - Included in webhook payloads for payment and membership events. + Included in webhook payloads for payment and membership events. Max 50 keys, 100 + chars per key, 500 chars per string value. """ override_tax_type: str diff --git a/src/whop_sdk/types/plan_update_params.py b/src/whop_sdk/types/plan_update_params.py index c6e1d0ed..335ffdf3 100644 --- a/src/whop_sdk/types/plan_update_params.py +++ b/src/whop_sdk/types/plan_update_params.py @@ -56,7 +56,8 @@ class PlanUpdateParams(TypedDict, total=False): metadata: Optional[object] """Custom key-value pairs to store on the plan. - Included in webhook payloads for payment and membership events. + Included in webhook payloads for payment and membership events. Max 50 keys, 100 + chars per key, 500 chars per string value. """ offer_cancel_discount: Optional[bool] diff --git a/src/whop_sdk/types/refund_created_webhook_event.py b/src/whop_sdk/types/refund_created_webhook_event.py index 611640f0..199467d7 100644 --- a/src/whop_sdk/types/refund_created_webhook_event.py +++ b/src/whop_sdk/types/refund_created_webhook_event.py @@ -57,7 +57,8 @@ class DataPaymentPlan(BaseModel): metadata: Optional[Dict[str, object]] = None """Custom key-value pairs stored on the plan. - Included in webhook payloads for payment and membership events. + Included in webhook payloads for payment and membership events. Max 50 keys, 100 + chars per key, 500 chars per string value. """ @@ -70,7 +71,8 @@ class DataPaymentProduct(BaseModel): metadata: Optional[Dict[str, object]] = None """Custom key-value pairs stored on the product. - Included in webhook payloads for payment and membership events. + Included in webhook payloads for payment and membership events. Max 50 keys, 100 + chars per key, 500 chars per string value. """ diff --git a/src/whop_sdk/types/refund_retrieve_response.py b/src/whop_sdk/types/refund_retrieve_response.py index e1f0ad14..e09cbf69 100644 --- a/src/whop_sdk/types/refund_retrieve_response.py +++ b/src/whop_sdk/types/refund_retrieve_response.py @@ -55,7 +55,8 @@ class PaymentPlan(BaseModel): metadata: Optional[Dict[str, object]] = None """Custom key-value pairs stored on the plan. - Included in webhook payloads for payment and membership events. + Included in webhook payloads for payment and membership events. Max 50 keys, 100 + chars per key, 500 chars per string value. """ @@ -68,7 +69,8 @@ class PaymentProduct(BaseModel): metadata: Optional[Dict[str, object]] = None """Custom key-value pairs stored on the product. - Included in webhook payloads for payment and membership events. + Included in webhook payloads for payment and membership events. Max 50 keys, 100 + chars per key, 500 chars per string value. """ diff --git a/src/whop_sdk/types/refund_updated_webhook_event.py b/src/whop_sdk/types/refund_updated_webhook_event.py index 9fbd5a1a..d73a908e 100644 --- a/src/whop_sdk/types/refund_updated_webhook_event.py +++ b/src/whop_sdk/types/refund_updated_webhook_event.py @@ -57,7 +57,8 @@ class DataPaymentPlan(BaseModel): metadata: Optional[Dict[str, object]] = None """Custom key-value pairs stored on the plan. - Included in webhook payloads for payment and membership events. + Included in webhook payloads for payment and membership events. Max 50 keys, 100 + chars per key, 500 chars per string value. """ @@ -70,7 +71,8 @@ class DataPaymentProduct(BaseModel): metadata: Optional[Dict[str, object]] = None """Custom key-value pairs stored on the product. - Included in webhook payloads for payment and membership events. + Included in webhook payloads for payment and membership events. Max 50 keys, 100 + chars per key, 500 chars per string value. """ diff --git a/src/whop_sdk/types/shared/membership.py b/src/whop_sdk/types/shared/membership.py index 4f8d3fd0..c37fd757 100644 --- a/src/whop_sdk/types/shared/membership.py +++ b/src/whop_sdk/types/shared/membership.py @@ -53,7 +53,8 @@ class Plan(BaseModel): metadata: Optional[Dict[str, object]] = None """Custom key-value pairs stored on the plan. - Included in webhook payloads for payment and membership events. + Included in webhook payloads for payment and membership events. Max 50 keys, 100 + chars per key, 500 chars per string value. """ @@ -66,7 +67,8 @@ class Product(BaseModel): metadata: Optional[Dict[str, object]] = None """Custom key-value pairs stored on the product. - Included in webhook payloads for payment and membership events. + Included in webhook payloads for payment and membership events. Max 50 keys, 100 + chars per key, 500 chars per string value. """ title: str @@ -189,7 +191,7 @@ class Membership(BaseModel): metadata: Optional[Dict[str, object]] = None """ Custom key-value pairs for the membership (commonly used for software licensing, - e.g., HWID). Max 50 keys, 500 chars per key, 5000 chars per value. + e.g., HWID). Max 50 keys, 100 chars per key, 500 chars per string value. """ payment_collection_paused: bool diff --git a/src/whop_sdk/types/shared/payment.py b/src/whop_sdk/types/shared/payment.py index 3c262c6d..3e5f0792 100644 --- a/src/whop_sdk/types/shared/payment.py +++ b/src/whop_sdk/types/shared/payment.py @@ -260,7 +260,8 @@ class Plan(BaseModel): metadata: Optional[Dict[str, object]] = None """Custom key-value pairs stored on the plan. - Included in webhook payloads for payment and membership events. + Included in webhook payloads for payment and membership events. Max 50 keys, 100 + chars per key, 500 chars per string value. """ @@ -273,7 +274,8 @@ class Product(BaseModel): metadata: Optional[Dict[str, object]] = None """Custom key-value pairs stored on the product. - Included in webhook payloads for payment and membership events. + Included in webhook payloads for payment and membership events. Max 50 keys, 100 + chars per key, 500 chars per string value. """ route: str diff --git a/src/whop_sdk/types/shared/product.py b/src/whop_sdk/types/shared/product.py index ce6299b8..3b22f84b 100644 --- a/src/whop_sdk/types/shared/product.py +++ b/src/whop_sdk/types/shared/product.py @@ -162,7 +162,8 @@ class Product(BaseModel): metadata: Optional[Dict[str, object]] = None """Custom key-value pairs stored on the product. - Included in webhook payloads for payment and membership events. + Included in webhook payloads for payment and membership events. Max 50 keys, 100 + chars per key, 500 chars per string value. """ owner_user: OwnerUser diff --git a/src/whop_sdk/types/shared/product_list_item.py b/src/whop_sdk/types/shared/product_list_item.py index af1e59d5..742b735b 100644 --- a/src/whop_sdk/types/shared/product_list_item.py +++ b/src/whop_sdk/types/shared/product_list_item.py @@ -40,7 +40,8 @@ class ProductListItem(BaseModel): metadata: Optional[Dict[str, object]] = None """Custom key-value pairs stored on the product. - Included in webhook payloads for payment and membership events. + Included in webhook payloads for payment and membership events. Max 50 keys, 100 + chars per key, 500 chars per string value. """ published_reviews_count: int diff --git a/src/whop_sdk/types/transfer_create_params.py b/src/whop_sdk/types/transfer_create_params.py index 4d028a14..1718177e 100644 --- a/src/whop_sdk/types/transfer_create_params.py +++ b/src/whop_sdk/types/transfer_create_params.py @@ -41,7 +41,11 @@ class TransferCreateParams(TypedDict, total=False): """Ledger transfers only. A unique key to prevent duplicate transfers.""" metadata: Optional[Dict[str, object]] - """Ledger transfers only. Custom key-value pairs attached to the transfer.""" + """Ledger transfers only. + + Custom key-value pairs attached to the transfer. Max 50 keys, 100 chars per key, + 500 chars per string value. + """ notes: Optional[str] """Ledger transfers only. A short note describing the transfer.""" From 77d11ed8869e6d3a7d9c037814639daebc564db4 Mon Sep 17 00:00:00 2001 From: stlc-bot Date: Mon, 15 Jun 2026 21:59:10 +0000 Subject: [PATCH 048/109] Expose card_id and per-tx details on financial-activity card_transaction Stainless-Generated-From: 295bdf8b8fd4c782e34649b84f686ef239226b76 --- .../types/financial_activity_list_response.py | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/src/whop_sdk/types/financial_activity_list_response.py b/src/whop_sdk/types/financial_activity_list_response.py index 849c4f88..9ed35a95 100644 --- a/src/whop_sdk/types/financial_activity_list_response.py +++ b/src/whop_sdk/types/financial_activity_list_response.py @@ -154,6 +154,12 @@ class DataResourceUnionMember4(BaseModel): class DataResourceUnionMember5(BaseModel): id: str + authorized_at: Optional[datetime] = None + """ISO 8601 timestamp the transaction was authorized.""" + + card_id: Optional[str] = None + """Identifier of the card that the transaction was charged to.""" + cashback_usd: Optional[str] = None """Cashback earned on this transaction as a USD decimal string. @@ -161,6 +167,18 @@ class DataResourceUnionMember5(BaseModel): computed yet. """ + declined_reason: Optional[str] = None + """Reason the transaction was declined (when status is declined).""" + + local_amount: Optional[str] = None + """Amount the merchant charged in their local currency, as a decimal string. + + Pair with local_currency. + """ + + local_currency: Optional[str] = None + """ISO 4217 currency code of the merchant-charged amount in local_amount.""" + merchant_category: Optional[str] = None merchant_icon_url: Optional[str] = None @@ -169,6 +187,9 @@ class DataResourceUnionMember5(BaseModel): object: Literal["card_transaction"] + posted_at: Optional[datetime] = None + """ISO 8601 timestamp the transaction was settled by the card network.""" + status: Optional[str] = None usd_amount: Optional[str] = None From 6c3135db7c89a1b20e44e4c8cbd797c674b5dfa5 Mon Sep 17 00:00:00 2001 From: stlc-bot Date: Tue, 16 Jun 2026 00:51:04 +0000 Subject: [PATCH 049/109] feat(identity): verifications REST API (KYC/KYB) Stainless-Generated-From: 9ad8869a1ed0efb854084fe0d54a2aa90402832b --- .stats.yml | 2 +- api.md | 10 +- src/whop_sdk/resources/verifications.py | 561 +++++++++++++++--- src/whop_sdk/types/__init__.py | 5 + src/whop_sdk/types/account.py | 8 + src/whop_sdk/types/user.py | 8 + .../types/verification_create_params.py | 70 +++ .../types/verification_create_response.py | 67 +++ .../types/verification_delete_response.py | 13 + .../types/verification_list_params.py | 17 +- .../types/verification_list_response.py | 75 ++- .../types/verification_retrieve_response.py | 71 ++- .../types/verification_update_params.py | 57 ++ .../types/verification_update_response.py | 67 +++ tests/api_resources/test_verifications.py | 387 ++++++++++-- 15 files changed, 1244 insertions(+), 174 deletions(-) create mode 100644 src/whop_sdk/types/verification_create_params.py create mode 100644 src/whop_sdk/types/verification_create_response.py create mode 100644 src/whop_sdk/types/verification_delete_response.py create mode 100644 src/whop_sdk/types/verification_update_params.py create mode 100644 src/whop_sdk/types/verification_update_response.py diff --git a/.stats.yml b/.stats.yml index 5aab750e..027e42a1 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1 +1 @@ -configured_endpoints: 233 +configured_endpoints: 236 diff --git a/api.md b/api.md index 558d0767..ab66402a 100644 --- a/api.md +++ b/api.md @@ -879,15 +879,21 @@ Types: from whop_sdk.types import ( VerificationErrorCode, VerificationStatus, + VerificationCreateResponse, VerificationRetrieveResponse, + VerificationUpdateResponse, VerificationListResponse, + VerificationDeleteResponse, ) ``` Methods: -- client.verifications.retrieve(id) -> VerificationRetrieveResponse -- client.verifications.list(\*\*params) -> SyncCursorPage[VerificationListResponse] +- client.verifications.create(\*\*params) -> VerificationCreateResponse +- client.verifications.retrieve(verification_id) -> VerificationRetrieveResponse +- client.verifications.update(verification_id, \*\*params) -> VerificationUpdateResponse +- client.verifications.list(\*\*params) -> VerificationListResponse +- client.verifications.delete(verification_id) -> VerificationDeleteResponse # Leads diff --git a/src/whop_sdk/resources/verifications.py b/src/whop_sdk/resources/verifications.py index 4fce0869..c7b88492 100644 --- a/src/whop_sdk/resources/verifications.py +++ b/src/whop_sdk/resources/verifications.py @@ -2,13 +2,14 @@ from __future__ import annotations -from typing import Optional +from typing import Dict, Iterable +from typing_extensions import Literal import httpx -from ..types import verification_list_params +from ..types import verification_list_params, verification_create_params, verification_update_params from .._types import Body, Omit, Query, Headers, NotGiven, omit, not_given -from .._utils import path_template, maybe_transform +from .._utils import path_template, maybe_transform, async_maybe_transform from .._compat import cached_property from .._resource import SyncAPIResource, AsyncAPIResource from .._response import ( @@ -17,9 +18,11 @@ async_to_raw_response_wrapper, async_to_streamed_response_wrapper, ) -from ..pagination import SyncCursorPage, AsyncCursorPage -from .._base_client import AsyncPaginator, make_request_options +from .._base_client import make_request_options from ..types.verification_list_response import VerificationListResponse +from ..types.verification_create_response import VerificationCreateResponse +from ..types.verification_delete_response import VerificationDeleteResponse +from ..types.verification_update_response import VerificationUpdateResponse from ..types.verification_retrieve_response import VerificationRetrieveResponse __all__ = ["VerificationsResource", "AsyncVerificationsResource"] @@ -47,23 +50,118 @@ def with_streaming_response(self) -> VerificationsResourceWithStreamingResponse: """ return VerificationsResourceWithStreamingResponse(self) - def retrieve( + def create( self, - id: str, *, + account_id: str, + address: Dict[str, object] | Omit = omit, + business_name: str | Omit = omit, + business_structure: str | Omit = omit, + country: str | Omit = omit, + date_of_birth: str | Omit = omit, + first_name: str | Omit = omit, + kind: Literal["individual", "business"] | Omit = omit, + last_name: str | Omit = omit, + phone: str | Omit = omit, + place_of_incorporation: str | Omit = omit, + restart: bool | Omit = omit, + tax_identification_number: 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, - ) -> VerificationRetrieveResponse: + ) -> VerificationCreateResponse: """ - Retrieves the details of an existing verification. + Creates or resumes a verification session for an account. + + Args: + account_id: The account ID to verify (biz\\__ tag). + + address: Address (line1, city, state, postal_code). line1, city and postal_code are + required for individuals when this request sets up the payout account; not + required for businesses. + + business_name: Business name. Required for businesses. + + business_structure: Business structure (e.g. llc, corporation). + + country: Country code. Required. For businesses this is the country of incorporation. + + date_of_birth: Date of birth. Required for individuals when this request sets up the payout + account. + + first_name: First name. Required for individuals when this request sets up the payout + account. + + kind: The verification type. Defaults to individual. + + last_name: Last name. Required for individuals when this request sets up the payout + account. + + phone: Pre-fill the phone number. + + place_of_incorporation: Place of incorporation (state/region). Required for businesses; maps to the + address state. - Required permissions: + restart: Whether to restart an in-flight verification. + + tax_identification_number: Tax identification number — SSN for individuals, EIN for businesses. Required + for business; recommended for individuals. Tokenized in transit, never stored + raw, and pre-fills the payout account's tax-id requirement so no RFI is raised + for it. + + 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( + "/verifications", + body=maybe_transform( + { + "address": address, + "business_name": business_name, + "business_structure": business_structure, + "country": country, + "date_of_birth": date_of_birth, + "first_name": first_name, + "kind": kind, + "last_name": last_name, + "phone": phone, + "place_of_incorporation": place_of_incorporation, + "restart": restart, + "tax_identification_number": tax_identification_number, + }, + verification_create_params.VerificationCreateParams, + ), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform({"account_id": account_id}, verification_create_params.VerificationCreateParams), + ), + cast_to=VerificationCreateResponse, + ) - - `payout:account:read` + def retrieve( + self, + verification_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, + ) -> VerificationRetrieveResponse: + """ + Retrieves a single identity verification profile by its idpf\\__ tag. Args: extra_headers: Send extra headers @@ -74,49 +172,108 @@ def retrieve( 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}") + if not verification_id: + raise ValueError(f"Expected a non-empty value for `verification_id` but received {verification_id!r}") return self._get( - path_template("/verifications/{id}", id=id), + path_template("/verifications/{verification_id}", verification_id=verification_id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), cast_to=VerificationRetrieveResponse, ) - def list( + def update( self, + verification_id: str, *, - payout_account_id: str, - after: Optional[str] | Omit = omit, - before: Optional[str] | Omit = omit, - first: Optional[int] | Omit = omit, - last: Optional[int] | Omit = omit, + business_address: Dict[str, object] | Omit = omit, + business_name: str | Omit = omit, + business_structure: str | Omit = omit, + country: str | Omit = omit, + date_of_birth: str | Omit = omit, + first_name: str | Omit = omit, + last_name: str | Omit = omit, + personal_address: Dict[str, object] | Omit = omit, + rfis: Iterable[verification_update_params.Rfi] | 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[VerificationListResponse]: + ) -> VerificationUpdateResponse: """ - Returns a list of identity verifications for a payout account, ordered by most - recent first. + Updates fields on an identity verification profile, or responds to outstanding + RFIs. - Required permissions: + Args: + business_address: The business address. - - `payout:account:read` + business_name: The business name. - Args: - payout_account_id: The unique identifier of the payout account to list verifications for. + business_structure: The business structure. + + country: The country code. + + date_of_birth: The date of birth. + + first_name: The first name on the verification. + + last_name: The last name on the verification. + + personal_address: The personal address. + + rfis: RFI responses. Each entry must include id and a value, address, or files + payload. + + extra_headers: Send extra headers - after: Returns the elements in the list that come after the specified cursor. + extra_query: Add additional query parameters to the request - before: Returns the elements in the list that come before the specified cursor. + extra_body: Add additional JSON properties to the request - first: Returns the first _n_ elements from the list. + timeout: Override the client-level default timeout for this request, in seconds + """ + if not verification_id: + raise ValueError(f"Expected a non-empty value for `verification_id` but received {verification_id!r}") + return self._patch( + path_template("/verifications/{verification_id}", verification_id=verification_id), + body=maybe_transform( + { + "business_address": business_address, + "business_name": business_name, + "business_structure": business_structure, + "country": country, + "date_of_birth": date_of_birth, + "first_name": first_name, + "last_name": last_name, + "personal_address": personal_address, + "rfis": rfis, + }, + verification_update_params.VerificationUpdateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=VerificationUpdateResponse, + ) - last: Returns the last _n_ elements from the list. + def list( + self, + *, + account_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, + ) -> VerificationListResponse: + """ + Lists identity verification profiles for an account. + + Args: + account_id: The account ID to list verifications for (biz\\__ tag). extra_headers: Send extra headers @@ -126,26 +283,49 @@ def list( timeout: Override the client-level default timeout for this request, in seconds """ - return self._get_api_list( + return self._get( "/verifications", - page=SyncCursorPage[VerificationListResponse], options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout, - query=maybe_transform( - { - "payout_account_id": payout_account_id, - "after": after, - "before": before, - "first": first, - "last": last, - }, - verification_list_params.VerificationListParams, - ), + query=maybe_transform({"account_id": account_id}, verification_list_params.VerificationListParams), + ), + cast_to=VerificationListResponse, + ) + + def delete( + self, + verification_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, + ) -> VerificationDeleteResponse: + """ + Unlinks a verification from the caller's accounts. + + 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 verification_id: + raise ValueError(f"Expected a non-empty value for `verification_id` but received {verification_id!r}") + return self._delete( + path_template("/verifications/{verification_id}", verification_id=verification_id), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), - model=VerificationListResponse, + cast_to=VerificationDeleteResponse, ) @@ -171,23 +351,120 @@ def with_streaming_response(self) -> AsyncVerificationsResourceWithStreamingResp """ return AsyncVerificationsResourceWithStreamingResponse(self) - async def retrieve( + async def create( self, - id: str, *, + account_id: str, + address: Dict[str, object] | Omit = omit, + business_name: str | Omit = omit, + business_structure: str | Omit = omit, + country: str | Omit = omit, + date_of_birth: str | Omit = omit, + first_name: str | Omit = omit, + kind: Literal["individual", "business"] | Omit = omit, + last_name: str | Omit = omit, + phone: str | Omit = omit, + place_of_incorporation: str | Omit = omit, + restart: bool | Omit = omit, + tax_identification_number: 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, - ) -> VerificationRetrieveResponse: + ) -> VerificationCreateResponse: """ - Retrieves the details of an existing verification. + Creates or resumes a verification session for an account. + + Args: + account_id: The account ID to verify (biz\\__ tag). + + address: Address (line1, city, state, postal_code). line1, city and postal_code are + required for individuals when this request sets up the payout account; not + required for businesses. + + business_name: Business name. Required for businesses. - Required permissions: + business_structure: Business structure (e.g. llc, corporation). - - `payout:account:read` + country: Country code. Required. For businesses this is the country of incorporation. + + date_of_birth: Date of birth. Required for individuals when this request sets up the payout + account. + + first_name: First name. Required for individuals when this request sets up the payout + account. + + kind: The verification type. Defaults to individual. + + last_name: Last name. Required for individuals when this request sets up the payout + account. + + phone: Pre-fill the phone number. + + place_of_incorporation: Place of incorporation (state/region). Required for businesses; maps to the + address state. + + restart: Whether to restart an in-flight verification. + + tax_identification_number: Tax identification number — SSN for individuals, EIN for businesses. Required + for business; recommended for individuals. Tokenized in transit, never stored + raw, and pre-fills the payout account's tax-id requirement so no RFI is raised + for it. + + 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( + "/verifications", + body=await async_maybe_transform( + { + "address": address, + "business_name": business_name, + "business_structure": business_structure, + "country": country, + "date_of_birth": date_of_birth, + "first_name": first_name, + "kind": kind, + "last_name": last_name, + "phone": phone, + "place_of_incorporation": place_of_incorporation, + "restart": restart, + "tax_identification_number": tax_identification_number, + }, + verification_create_params.VerificationCreateParams, + ), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=await async_maybe_transform( + {"account_id": account_id}, verification_create_params.VerificationCreateParams + ), + ), + cast_to=VerificationCreateResponse, + ) + + async def retrieve( + self, + verification_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, + ) -> VerificationRetrieveResponse: + """ + Retrieves a single identity verification profile by its idpf\\__ tag. Args: extra_headers: Send extra headers @@ -198,49 +475,59 @@ async def retrieve( 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}") + if not verification_id: + raise ValueError(f"Expected a non-empty value for `verification_id` but received {verification_id!r}") return await self._get( - path_template("/verifications/{id}", id=id), + path_template("/verifications/{verification_id}", verification_id=verification_id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), cast_to=VerificationRetrieveResponse, ) - def list( + async def update( self, + verification_id: str, *, - payout_account_id: str, - after: Optional[str] | Omit = omit, - before: Optional[str] | Omit = omit, - first: Optional[int] | Omit = omit, - last: Optional[int] | Omit = omit, + business_address: Dict[str, object] | Omit = omit, + business_name: str | Omit = omit, + business_structure: str | Omit = omit, + country: str | Omit = omit, + date_of_birth: str | Omit = omit, + first_name: str | Omit = omit, + last_name: str | Omit = omit, + personal_address: Dict[str, object] | Omit = omit, + rfis: Iterable[verification_update_params.Rfi] | 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[VerificationListResponse, AsyncCursorPage[VerificationListResponse]]: + ) -> VerificationUpdateResponse: """ - Returns a list of identity verifications for a payout account, ordered by most - recent first. + Updates fields on an identity verification profile, or responds to outstanding + RFIs. - Required permissions: + Args: + business_address: The business address. - - `payout:account:read` + business_name: The business name. - Args: - payout_account_id: The unique identifier of the payout account to list verifications for. + business_structure: The business structure. + + country: The country code. - after: Returns the elements in the list that come after the specified cursor. + date_of_birth: The date of birth. - before: Returns the elements in the list that come before the specified cursor. + first_name: The first name on the verification. - first: Returns the first _n_ elements from the list. + last_name: The last name on the verification. - last: Returns the last _n_ elements from the list. + personal_address: The personal address. + + rfis: RFI responses. Each entry must include id and a value, address, or files + payload. extra_headers: Send extra headers @@ -250,26 +537,100 @@ def list( timeout: Override the client-level default timeout for this request, in seconds """ - return self._get_api_list( + if not verification_id: + raise ValueError(f"Expected a non-empty value for `verification_id` but received {verification_id!r}") + return await self._patch( + path_template("/verifications/{verification_id}", verification_id=verification_id), + body=await async_maybe_transform( + { + "business_address": business_address, + "business_name": business_name, + "business_structure": business_structure, + "country": country, + "date_of_birth": date_of_birth, + "first_name": first_name, + "last_name": last_name, + "personal_address": personal_address, + "rfis": rfis, + }, + verification_update_params.VerificationUpdateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=VerificationUpdateResponse, + ) + + async def list( + self, + *, + account_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, + ) -> VerificationListResponse: + """ + Lists identity verification profiles for an account. + + Args: + account_id: The account ID to list verifications for (biz\\__ tag). + + 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( "/verifications", - page=AsyncCursorPage[VerificationListResponse], options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout, - query=maybe_transform( - { - "payout_account_id": payout_account_id, - "after": after, - "before": before, - "first": first, - "last": last, - }, - verification_list_params.VerificationListParams, + query=await async_maybe_transform( + {"account_id": account_id}, verification_list_params.VerificationListParams ), ), - model=VerificationListResponse, + cast_to=VerificationListResponse, + ) + + async def delete( + self, + verification_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, + ) -> VerificationDeleteResponse: + """ + Unlinks a verification from the caller's accounts. + + 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 verification_id: + raise ValueError(f"Expected a non-empty value for `verification_id` but received {verification_id!r}") + return await self._delete( + path_template("/verifications/{verification_id}", verification_id=verification_id), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=VerificationDeleteResponse, ) @@ -277,45 +638,81 @@ class VerificationsResourceWithRawResponse: def __init__(self, verifications: VerificationsResource) -> None: self._verifications = verifications + self.create = to_raw_response_wrapper( + verifications.create, + ) self.retrieve = to_raw_response_wrapper( verifications.retrieve, ) + self.update = to_raw_response_wrapper( + verifications.update, + ) self.list = to_raw_response_wrapper( verifications.list, ) + self.delete = to_raw_response_wrapper( + verifications.delete, + ) class AsyncVerificationsResourceWithRawResponse: def __init__(self, verifications: AsyncVerificationsResource) -> None: self._verifications = verifications + self.create = async_to_raw_response_wrapper( + verifications.create, + ) self.retrieve = async_to_raw_response_wrapper( verifications.retrieve, ) + self.update = async_to_raw_response_wrapper( + verifications.update, + ) self.list = async_to_raw_response_wrapper( verifications.list, ) + self.delete = async_to_raw_response_wrapper( + verifications.delete, + ) class VerificationsResourceWithStreamingResponse: def __init__(self, verifications: VerificationsResource) -> None: self._verifications = verifications + self.create = to_streamed_response_wrapper( + verifications.create, + ) self.retrieve = to_streamed_response_wrapper( verifications.retrieve, ) + self.update = to_streamed_response_wrapper( + verifications.update, + ) self.list = to_streamed_response_wrapper( verifications.list, ) + self.delete = to_streamed_response_wrapper( + verifications.delete, + ) class AsyncVerificationsResourceWithStreamingResponse: def __init__(self, verifications: AsyncVerificationsResource) -> None: self._verifications = verifications + self.create = async_to_streamed_response_wrapper( + verifications.create, + ) self.retrieve = async_to_streamed_response_wrapper( verifications.retrieve, ) + self.update = async_to_streamed_response_wrapper( + verifications.update, + ) self.list = async_to_streamed_response_wrapper( verifications.list, ) + self.delete = async_to_streamed_response_wrapper( + verifications.delete, + ) diff --git a/src/whop_sdk/types/__init__.py b/src/whop_sdk/types/__init__.py index b466c2c8..ba5f9d1e 100644 --- a/src/whop_sdk/types/__init__.py +++ b/src/whop_sdk/types/__init__.py @@ -328,7 +328,9 @@ from .swap_create_quote_response import SwapCreateQuoteResponse as SwapCreateQuoteResponse from .transfer_retrieve_response import TransferRetrieveResponse as TransferRetrieveResponse from .user_check_access_response import UserCheckAccessResponse as UserCheckAccessResponse +from .verification_create_params import VerificationCreateParams as VerificationCreateParams from .verification_list_response import VerificationListResponse as VerificationListResponse +from .verification_update_params import VerificationUpdateParams as VerificationUpdateParams from .ad_campaign_retrieve_params import AdCampaignRetrieveParams as AdCampaignRetrieveParams from .ad_report_retrieve_response import AdReportRetrieveResponse as AdReportRetrieveResponse from .authorized_user_list_params import AuthorizedUserListParams as AuthorizedUserListParams @@ -358,6 +360,9 @@ 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 .verification_create_response import VerificationCreateResponse as VerificationCreateResponse +from .verification_delete_response import VerificationDeleteResponse as VerificationDeleteResponse +from .verification_update_response import VerificationUpdateResponse as VerificationUpdateResponse 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 diff --git a/src/whop_sdk/types/account.py b/src/whop_sdk/types/account.py index dafcc3b7..e751eec5 100644 --- a/src/whop_sdk/types/account.py +++ b/src/whop_sdk/types/account.py @@ -143,5 +143,13 @@ class Account(BaseModel): use_logo_as_opengraph_image_fallback: bool """Whether the account uses its logo as the fallback Open Graph image""" + verification: object + """The account's identity-verification status. + + `individual` is KYC, `business` is KYB; each is null when that profile has not + been created, otherwise { status } where status is one of not_started, pending, + approved, rejected + """ + wallet: Optional[AccountWallet] = None """The account's primary crypto wallet, or null if none has been provisioned""" diff --git a/src/whop_sdk/types/user.py b/src/whop_sdk/types/user.py index cac11115..9295b603 100644 --- a/src/whop_sdk/types/user.py +++ b/src/whop_sdk/types/user.py @@ -67,3 +67,11 @@ class User(BaseModel): username: str """The user's unique username""" + + verification: object + """The user's identity-verification status. + + `individual` is KYC, `business` is KYB; each is null when that profile has not + been created, otherwise { status } where status is one of not_started, pending, + approved, rejected + """ diff --git a/src/whop_sdk/types/verification_create_params.py b/src/whop_sdk/types/verification_create_params.py new file mode 100644 index 00000000..4e128ffd --- /dev/null +++ b/src/whop_sdk/types/verification_create_params.py @@ -0,0 +1,70 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Dict +from typing_extensions import Literal, Required, TypedDict + +__all__ = ["VerificationCreateParams"] + + +class VerificationCreateParams(TypedDict, total=False): + account_id: Required[str] + """The account ID to verify (biz\\__ tag).""" + + address: Dict[str, object] + """Address (line1, city, state, postal_code). + + line1, city and postal_code are required for individuals when this request sets + up the payout account; not required for businesses. + """ + + business_name: str + """Business name. Required for businesses.""" + + business_structure: str + """Business structure (e.g. llc, corporation).""" + + country: str + """Country code. Required. For businesses this is the country of incorporation.""" + + date_of_birth: str + """Date of birth. + + Required for individuals when this request sets up the payout account. + """ + + first_name: str + """First name. + + Required for individuals when this request sets up the payout account. + """ + + kind: Literal["individual", "business"] + """The verification type. Defaults to individual.""" + + last_name: str + """Last name. + + Required for individuals when this request sets up the payout account. + """ + + phone: str + """Pre-fill the phone number.""" + + place_of_incorporation: str + """Place of incorporation (state/region). + + Required for businesses; maps to the address state. + """ + + restart: bool + """Whether to restart an in-flight verification.""" + + tax_identification_number: str + """Tax identification number — SSN for individuals, EIN for businesses. + + Required for business; recommended for individuals. Tokenized in transit, never + stored raw, and pre-fills the payout account's tax-id requirement so no RFI is + raised for it. + """ diff --git a/src/whop_sdk/types/verification_create_response.py b/src/whop_sdk/types/verification_create_response.py new file mode 100644 index 00000000..895ffc00 --- /dev/null +++ b/src/whop_sdk/types/verification_create_response.py @@ -0,0 +1,67 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Optional +from typing_extensions import Literal + +from .._models import BaseModel + +__all__ = ["VerificationCreateResponse", "Rfi", "RfiRequestedFile"] + + +class RfiRequestedFile(BaseModel): + category: Optional[str] = None + + is_optional: Optional[bool] = None + + kind: Optional[str] = None + + +class Rfi(BaseModel): + id: Optional[str] = None + + created_at: Optional[str] = None + + description: Optional[str] = None + + error_message: Optional[str] = None + + requested_files: Optional[List[RfiRequestedFile]] = None + """Documents the provider is requesting (file-upload RFIs). + + The `kind` is what to send back when answering. + """ + + status: Optional[Literal["outstanding", "invalid"]] = None + + type: Optional[str] = None + + +class VerificationCreateResponse(BaseModel): + id: Optional[str] = None + """The verification ID, e.g. idpf\\__\\**""" + + address: Optional[object] = None + + business_name: Optional[str] = None + + business_structure: Optional[str] = None + + country: Optional[str] = None + + created_at: Optional[str] = None + + date_of_birth: Optional[str] = None + + first_name: Optional[str] = None + + kind: Optional[Literal["individual", "business"]] = None + + last_name: Optional[str] = None + + rfis: Optional[List[Rfi]] = None + + session_url: Optional[str] = None + + status: Optional[Literal["not_started", "pending", "approved", "rejected", "action_required"]] = None + + updated_at: Optional[str] = None diff --git a/src/whop_sdk/types/verification_delete_response.py b/src/whop_sdk/types/verification_delete_response.py new file mode 100644 index 00000000..3448aef4 --- /dev/null +++ b/src/whop_sdk/types/verification_delete_response.py @@ -0,0 +1,13 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional + +from .._models import BaseModel + +__all__ = ["VerificationDeleteResponse"] + + +class VerificationDeleteResponse(BaseModel): + id: Optional[str] = None + + deleted: Optional[bool] = None diff --git a/src/whop_sdk/types/verification_list_params.py b/src/whop_sdk/types/verification_list_params.py index 64ad9314..9b376d72 100644 --- a/src/whop_sdk/types/verification_list_params.py +++ b/src/whop_sdk/types/verification_list_params.py @@ -2,24 +2,11 @@ from __future__ import annotations -from typing import Optional from typing_extensions import Required, TypedDict __all__ = ["VerificationListParams"] class VerificationListParams(TypedDict, total=False): - payout_account_id: Required[str] - """The unique identifier of the payout account to list verifications 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.""" - - first: Optional[int] - """Returns the first _n_ elements from the list.""" - - last: Optional[int] - """Returns the last _n_ elements from the list.""" + account_id: Required[str] + """The account ID to list verifications for (biz\\__ tag).""" diff --git a/src/whop_sdk/types/verification_list_response.py b/src/whop_sdk/types/verification_list_response.py index 25d3f1e6..5a98c45d 100644 --- a/src/whop_sdk/types/verification_list_response.py +++ b/src/whop_sdk/types/verification_list_response.py @@ -1,30 +1,71 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -from typing import Optional +from typing import List, Optional +from typing_extensions import Literal from .._models import BaseModel -from .verification_status import VerificationStatus -from .verification_error_code import VerificationErrorCode -__all__ = ["VerificationListResponse"] +__all__ = ["VerificationListResponse", "Data", "DataRfi", "DataRfiRequestedFile"] -class VerificationListResponse(BaseModel): - """ - An identity verification session used to confirm a person or entity's identity for payout account eligibility. - """ +class DataRfiRequestedFile(BaseModel): + category: Optional[str] = None + + is_optional: Optional[bool] = None + + kind: Optional[str] = None + + +class DataRfi(BaseModel): + id: Optional[str] = None - id: str - """The numeric id of the verification record.""" + created_at: Optional[str] = None - last_error_code: Optional[VerificationErrorCode] = None - """An error code for a verification attempt.""" + description: Optional[str] = None - last_error_reason: Optional[str] = None - """A human-readable explanation of the most recent verification error. + error_message: Optional[str] = None - Null if no error has occurred. + requested_files: Optional[List[DataRfiRequestedFile]] = None + """Documents the provider is requesting (file-upload RFIs). + + The `kind` is what to send back when answering. """ - status: VerificationStatus - """The current status of this verification session.""" + status: Optional[Literal["outstanding", "invalid"]] = None + + type: Optional[str] = None + + +class Data(BaseModel): + id: Optional[str] = None + """The verification ID, e.g. idpf\\__\\**""" + + address: Optional[object] = None + + business_name: Optional[str] = None + + business_structure: Optional[str] = None + + country: Optional[str] = None + + created_at: Optional[str] = None + + date_of_birth: Optional[str] = None + + first_name: Optional[str] = None + + kind: Optional[Literal["individual", "business"]] = None + + last_name: Optional[str] = None + + rfis: Optional[List[DataRfi]] = None + + session_url: Optional[str] = None + + status: Optional[Literal["not_started", "pending", "approved", "rejected", "action_required"]] = None + + updated_at: Optional[str] = None + + +class VerificationListResponse(BaseModel): + data: Optional[List[Data]] = None diff --git a/src/whop_sdk/types/verification_retrieve_response.py b/src/whop_sdk/types/verification_retrieve_response.py index 9228eef9..9018a263 100644 --- a/src/whop_sdk/types/verification_retrieve_response.py +++ b/src/whop_sdk/types/verification_retrieve_response.py @@ -1,30 +1,67 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -from typing import Optional +from typing import List, Optional +from typing_extensions import Literal from .._models import BaseModel -from .verification_status import VerificationStatus -from .verification_error_code import VerificationErrorCode -__all__ = ["VerificationRetrieveResponse"] +__all__ = ["VerificationRetrieveResponse", "Rfi", "RfiRequestedFile"] -class VerificationRetrieveResponse(BaseModel): - """ - An identity verification session used to confirm a person or entity's identity for payout account eligibility. - """ +class RfiRequestedFile(BaseModel): + category: Optional[str] = None + + is_optional: Optional[bool] = None + + kind: Optional[str] = None + + +class Rfi(BaseModel): + id: Optional[str] = None - id: str - """The numeric id of the verification record.""" + created_at: Optional[str] = None - last_error_code: Optional[VerificationErrorCode] = None - """An error code for a verification attempt.""" + description: Optional[str] = None - last_error_reason: Optional[str] = None - """A human-readable explanation of the most recent verification error. + error_message: Optional[str] = None - Null if no error has occurred. + requested_files: Optional[List[RfiRequestedFile]] = None + """Documents the provider is requesting (file-upload RFIs). + + The `kind` is what to send back when answering. """ - status: VerificationStatus - """The current status of this verification session.""" + status: Optional[Literal["outstanding", "invalid"]] = None + + type: Optional[str] = None + + +class VerificationRetrieveResponse(BaseModel): + id: Optional[str] = None + """The verification ID, e.g. idpf\\__\\**""" + + address: Optional[object] = None + + business_name: Optional[str] = None + + business_structure: Optional[str] = None + + country: Optional[str] = None + + created_at: Optional[str] = None + + date_of_birth: Optional[str] = None + + first_name: Optional[str] = None + + kind: Optional[Literal["individual", "business"]] = None + + last_name: Optional[str] = None + + rfis: Optional[List[Rfi]] = None + + session_url: Optional[str] = None + + status: Optional[Literal["not_started", "pending", "approved", "rejected", "action_required"]] = None + + updated_at: Optional[str] = None diff --git a/src/whop_sdk/types/verification_update_params.py b/src/whop_sdk/types/verification_update_params.py new file mode 100644 index 00000000..1e1b3b3e --- /dev/null +++ b/src/whop_sdk/types/verification_update_params.py @@ -0,0 +1,57 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Dict, Iterable +from typing_extensions import Literal, Required, TypedDict + +__all__ = ["VerificationUpdateParams", "Rfi"] + + +class VerificationUpdateParams(TypedDict, total=False): + business_address: Dict[str, object] + """The business address.""" + + business_name: str + """The business name.""" + + business_structure: str + """The business structure.""" + + country: str + """The country code.""" + + date_of_birth: str + """The date of birth.""" + + first_name: str + """The first name on the verification.""" + + last_name: str + """The last name on the verification.""" + + personal_address: Dict[str, object] + """The personal address.""" + + rfis: Iterable[Rfi] + """RFI responses. + + Each entry must include id and a value, address, or files payload. + """ + + +class Rfi(TypedDict, total=False): + id: Required[str] + """The RFI tag (paa\\__\\**).""" + + address: Dict[str, object] + """Address payload for address RFIs.""" + + files: Iterable[object] + """File upload payload for document RFIs.""" + + value: str + """The value for text/date/phone RFIs.""" + + value_type: Literal["raw", "vault_token"] + """Defaults to raw.""" diff --git a/src/whop_sdk/types/verification_update_response.py b/src/whop_sdk/types/verification_update_response.py new file mode 100644 index 00000000..b10f4f03 --- /dev/null +++ b/src/whop_sdk/types/verification_update_response.py @@ -0,0 +1,67 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Optional +from typing_extensions import Literal + +from .._models import BaseModel + +__all__ = ["VerificationUpdateResponse", "Rfi", "RfiRequestedFile"] + + +class RfiRequestedFile(BaseModel): + category: Optional[str] = None + + is_optional: Optional[bool] = None + + kind: Optional[str] = None + + +class Rfi(BaseModel): + id: Optional[str] = None + + created_at: Optional[str] = None + + description: Optional[str] = None + + error_message: Optional[str] = None + + requested_files: Optional[List[RfiRequestedFile]] = None + """Documents the provider is requesting (file-upload RFIs). + + The `kind` is what to send back when answering. + """ + + status: Optional[Literal["outstanding", "invalid"]] = None + + type: Optional[str] = None + + +class VerificationUpdateResponse(BaseModel): + id: Optional[str] = None + """The verification ID, e.g. idpf\\__\\**""" + + address: Optional[object] = None + + business_name: Optional[str] = None + + business_structure: Optional[str] = None + + country: Optional[str] = None + + created_at: Optional[str] = None + + date_of_birth: Optional[str] = None + + first_name: Optional[str] = None + + kind: Optional[Literal["individual", "business"]] = None + + last_name: Optional[str] = None + + rfis: Optional[List[Rfi]] = None + + session_url: Optional[str] = None + + status: Optional[Literal["not_started", "pending", "approved", "rejected", "action_required"]] = None + + updated_at: Optional[str] = None diff --git a/tests/api_resources/test_verifications.py b/tests/api_resources/test_verifications.py index e1499aed..5c6fa1f0 100644 --- a/tests/api_resources/test_verifications.py +++ b/tests/api_resources/test_verifications.py @@ -9,8 +9,13 @@ from whop_sdk import Whop, AsyncWhop from tests.utils import assert_matches_type -from whop_sdk.types import VerificationListResponse, VerificationRetrieveResponse -from whop_sdk.pagination import SyncCursorPage, AsyncCursorPage +from whop_sdk.types import ( + VerificationListResponse, + VerificationCreateResponse, + VerificationDeleteResponse, + VerificationUpdateResponse, + VerificationRetrieveResponse, +) base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") @@ -18,11 +23,65 @@ class TestVerifications: 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: + verification = client.verifications.create( + account_id="account_id", + ) + assert_matches_type(VerificationCreateResponse, verification, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_create_with_all_params(self, client: Whop) -> None: + verification = client.verifications.create( + account_id="account_id", + address={"foo": "bar"}, + business_name="business_name", + business_structure="business_structure", + country="country", + date_of_birth="date_of_birth", + first_name="first_name", + kind="individual", + last_name="last_name", + phone="phone", + place_of_incorporation="place_of_incorporation", + restart=True, + tax_identification_number="tax_identification_number", + ) + assert_matches_type(VerificationCreateResponse, verification, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_raw_response_create(self, client: Whop) -> None: + response = client.verifications.with_raw_response.create( + account_id="account_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + verification = response.parse() + assert_matches_type(VerificationCreateResponse, verification, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_streaming_response_create(self, client: Whop) -> None: + with client.verifications.with_streaming_response.create( + account_id="account_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + verification = response.parse() + assert_matches_type(VerificationCreateResponse, verification, 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: verification = client.verifications.retrieve( - "verf_xxxxxxxxxxxxx", + "verification_id", ) assert_matches_type(VerificationRetrieveResponse, verification, path=["response"]) @@ -30,7 +89,7 @@ def test_method_retrieve(self, client: Whop) -> None: @parametrize def test_raw_response_retrieve(self, client: Whop) -> None: response = client.verifications.with_raw_response.retrieve( - "verf_xxxxxxxxxxxxx", + "verification_id", ) assert response.is_closed is True @@ -42,7 +101,7 @@ def test_raw_response_retrieve(self, client: Whop) -> None: @parametrize def test_streaming_response_retrieve(self, client: Whop) -> None: with client.verifications.with_streaming_response.retrieve( - "verf_xxxxxxxxxxxxx", + "verification_id", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -55,68 +114,219 @@ def test_streaming_response_retrieve(self, client: Whop) -> None: @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 ''"): + with pytest.raises(ValueError, match=r"Expected a non-empty value for `verification_id` but received ''"): client.verifications.with_raw_response.retrieve( "", ) @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize - def test_method_list(self, client: Whop) -> None: - verification = client.verifications.list( - payout_account_id="poact_xxxxxxxxxxxx", + def test_method_update(self, client: Whop) -> None: + verification = client.verifications.update( + verification_id="verification_id", + ) + assert_matches_type(VerificationUpdateResponse, verification, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_update_with_all_params(self, client: Whop) -> None: + verification = client.verifications.update( + verification_id="verification_id", + business_address={"foo": "bar"}, + business_name="business_name", + business_structure="business_structure", + country="country", + date_of_birth="date_of_birth", + first_name="first_name", + last_name="last_name", + personal_address={"foo": "bar"}, + rfis=[ + { + "id": "id", + "address": {"foo": "bar"}, + "files": [{}], + "value": "value", + "value_type": "raw", + } + ], + ) + assert_matches_type(VerificationUpdateResponse, verification, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_raw_response_update(self, client: Whop) -> None: + response = client.verifications.with_raw_response.update( + verification_id="verification_id", ) - assert_matches_type(SyncCursorPage[VerificationListResponse], verification, path=["response"]) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + verification = response.parse() + assert_matches_type(VerificationUpdateResponse, verification, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_streaming_response_update(self, client: Whop) -> None: + with client.verifications.with_streaming_response.update( + verification_id="verification_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + verification = response.parse() + assert_matches_type(VerificationUpdateResponse, verification, path=["response"]) + + assert cast(Any, response.is_closed) is True @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize - def test_method_list_with_all_params(self, client: Whop) -> None: + def test_path_params_update(self, client: Whop) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `verification_id` but received ''"): + client.verifications.with_raw_response.update( + verification_id="", + ) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_list(self, client: Whop) -> None: verification = client.verifications.list( - payout_account_id="poact_xxxxxxxxxxxx", - after="after", - before="before", - first=42, - last=42, + account_id="account_id", ) - assert_matches_type(SyncCursorPage[VerificationListResponse], verification, path=["response"]) + assert_matches_type(VerificationListResponse, verification, path=["response"]) @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_raw_response_list(self, client: Whop) -> None: response = client.verifications.with_raw_response.list( - payout_account_id="poact_xxxxxxxxxxxx", + account_id="account_id", ) assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" verification = response.parse() - assert_matches_type(SyncCursorPage[VerificationListResponse], verification, path=["response"]) + assert_matches_type(VerificationListResponse, verification, path=["response"]) @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_streaming_response_list(self, client: Whop) -> None: with client.verifications.with_streaming_response.list( - payout_account_id="poact_xxxxxxxxxxxx", + account_id="account_id", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" verification = response.parse() - assert_matches_type(SyncCursorPage[VerificationListResponse], verification, path=["response"]) + assert_matches_type(VerificationListResponse, verification, 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: + verification = client.verifications.delete( + "verification_id", + ) + assert_matches_type(VerificationDeleteResponse, verification, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_raw_response_delete(self, client: Whop) -> None: + response = client.verifications.with_raw_response.delete( + "verification_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + verification = response.parse() + assert_matches_type(VerificationDeleteResponse, verification, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_streaming_response_delete(self, client: Whop) -> None: + with client.verifications.with_streaming_response.delete( + "verification_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + verification = response.parse() + assert_matches_type(VerificationDeleteResponse, verification, 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 `verification_id` but received ''"): + client.verifications.with_raw_response.delete( + "", + ) + class TestAsyncVerifications: 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: + verification = await async_client.verifications.create( + account_id="account_id", + ) + assert_matches_type(VerificationCreateResponse, verification, 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: + verification = await async_client.verifications.create( + account_id="account_id", + address={"foo": "bar"}, + business_name="business_name", + business_structure="business_structure", + country="country", + date_of_birth="date_of_birth", + first_name="first_name", + kind="individual", + last_name="last_name", + phone="phone", + place_of_incorporation="place_of_incorporation", + restart=True, + tax_identification_number="tax_identification_number", + ) + assert_matches_type(VerificationCreateResponse, verification, 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.verifications.with_raw_response.create( + account_id="account_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + verification = await response.parse() + assert_matches_type(VerificationCreateResponse, verification, 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.verifications.with_streaming_response.create( + account_id="account_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + verification = await response.parse() + assert_matches_type(VerificationCreateResponse, verification, 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: verification = await async_client.verifications.retrieve( - "verf_xxxxxxxxxxxxx", + "verification_id", ) assert_matches_type(VerificationRetrieveResponse, verification, path=["response"]) @@ -124,7 +334,7 @@ async def test_method_retrieve(self, async_client: AsyncWhop) -> None: @parametrize async def test_raw_response_retrieve(self, async_client: AsyncWhop) -> None: response = await async_client.verifications.with_raw_response.retrieve( - "verf_xxxxxxxxxxxxx", + "verification_id", ) assert response.is_closed is True @@ -136,7 +346,7 @@ async def test_raw_response_retrieve(self, async_client: AsyncWhop) -> None: @parametrize async def test_streaming_response_retrieve(self, async_client: AsyncWhop) -> None: async with async_client.verifications.with_streaming_response.retrieve( - "verf_xxxxxxxxxxxxx", + "verification_id", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -149,53 +359,150 @@ async def test_streaming_response_retrieve(self, async_client: AsyncWhop) -> Non @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 ''"): + with pytest.raises(ValueError, match=r"Expected a non-empty value for `verification_id` but received ''"): await async_client.verifications.with_raw_response.retrieve( "", ) @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize - async def test_method_list(self, async_client: AsyncWhop) -> None: - verification = await async_client.verifications.list( - payout_account_id="poact_xxxxxxxxxxxx", + async def test_method_update(self, async_client: AsyncWhop) -> None: + verification = await async_client.verifications.update( + verification_id="verification_id", + ) + assert_matches_type(VerificationUpdateResponse, verification, 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: + verification = await async_client.verifications.update( + verification_id="verification_id", + business_address={"foo": "bar"}, + business_name="business_name", + business_structure="business_structure", + country="country", + date_of_birth="date_of_birth", + first_name="first_name", + last_name="last_name", + personal_address={"foo": "bar"}, + rfis=[ + { + "id": "id", + "address": {"foo": "bar"}, + "files": [{}], + "value": "value", + "value_type": "raw", + } + ], + ) + assert_matches_type(VerificationUpdateResponse, verification, 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.verifications.with_raw_response.update( + verification_id="verification_id", ) - assert_matches_type(AsyncCursorPage[VerificationListResponse], verification, path=["response"]) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + verification = await response.parse() + assert_matches_type(VerificationUpdateResponse, verification, 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.verifications.with_streaming_response.update( + verification_id="verification_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + verification = await response.parse() + assert_matches_type(VerificationUpdateResponse, verification, path=["response"]) + + assert cast(Any, response.is_closed) is True @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize - async def test_method_list_with_all_params(self, async_client: AsyncWhop) -> None: + async def test_path_params_update(self, async_client: AsyncWhop) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `verification_id` but received ''"): + await async_client.verifications.with_raw_response.update( + verification_id="", + ) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_list(self, async_client: AsyncWhop) -> None: verification = await async_client.verifications.list( - payout_account_id="poact_xxxxxxxxxxxx", - after="after", - before="before", - first=42, - last=42, + account_id="account_id", ) - assert_matches_type(AsyncCursorPage[VerificationListResponse], verification, path=["response"]) + assert_matches_type(VerificationListResponse, verification, 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.verifications.with_raw_response.list( - payout_account_id="poact_xxxxxxxxxxxx", + account_id="account_id", ) assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" verification = await response.parse() - assert_matches_type(AsyncCursorPage[VerificationListResponse], verification, path=["response"]) + assert_matches_type(VerificationListResponse, verification, 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.verifications.with_streaming_response.list( - payout_account_id="poact_xxxxxxxxxxxx", + account_id="account_id", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" verification = await response.parse() - assert_matches_type(AsyncCursorPage[VerificationListResponse], verification, path=["response"]) + assert_matches_type(VerificationListResponse, verification, 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: + verification = await async_client.verifications.delete( + "verification_id", + ) + assert_matches_type(VerificationDeleteResponse, verification, 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.verifications.with_raw_response.delete( + "verification_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + verification = await response.parse() + assert_matches_type(VerificationDeleteResponse, verification, 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.verifications.with_streaming_response.delete( + "verification_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + verification = await response.parse() + assert_matches_type(VerificationDeleteResponse, verification, 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 `verification_id` but received ''"): + await async_client.verifications.with_raw_response.delete( + "", + ) From 8b99243e3ae3a528ff9c1783bbe6d8ad5196b254 Mon Sep 17 00:00:00 2001 From: stlc-bot Date: Tue, 16 Jun 2026 06:30:22 +0000 Subject: [PATCH 050/109] Add GET /cards/:card_id with secrets, remove reveal_secrets from list Stainless-Generated-From: 3adf7311c255ee10c9c0b08375ec4a871f45f0a5 --- .stats.yml | 2 +- api.md | 3 +- src/whop_sdk/resources/cards.py | 153 +++++++++++++++---- src/whop_sdk/types/__init__.py | 2 + src/whop_sdk/types/card_list_params.py | 9 -- src/whop_sdk/types/card_list_response.py | 14 +- src/whop_sdk/types/card_retrieve_params.py | 15 ++ src/whop_sdk/types/card_retrieve_response.py | 96 ++++++++++++ tests/api_resources/test_cards.py | 110 ++++++++++++- 9 files changed, 351 insertions(+), 53 deletions(-) create mode 100644 src/whop_sdk/types/card_retrieve_params.py create mode 100644 src/whop_sdk/types/card_retrieve_response.py diff --git a/.stats.yml b/.stats.yml index 027e42a1..dd9fa391 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1 +1 @@ -configured_endpoints: 236 +configured_endpoints: 237 diff --git a/api.md b/api.md index ab66402a..0ae6196c 100644 --- a/api.md +++ b/api.md @@ -769,11 +769,12 @@ Methods: Types: ```python -from whop_sdk.types import CardListResponse +from whop_sdk.types import CardRetrieveResponse, CardListResponse ``` Methods: +- client.cards.retrieve(card_id, \*\*params) -> CardRetrieveResponse - client.cards.list(\*\*params) -> CardListResponse # Swaps diff --git a/src/whop_sdk/resources/cards.py b/src/whop_sdk/resources/cards.py index 59fe1107..87571eba 100644 --- a/src/whop_sdk/resources/cards.py +++ b/src/whop_sdk/resources/cards.py @@ -4,9 +4,9 @@ import httpx -from ..types import card_list_params +from ..types import card_list_params, card_retrieve_params from .._types import Body, Omit, Query, Headers, NotGiven, omit, not_given -from .._utils import maybe_transform, async_maybe_transform +from .._utils import path_template, maybe_transform, async_maybe_transform from .._compat import cached_property from .._resource import SyncAPIResource, AsyncAPIResource from .._response import ( @@ -17,6 +17,7 @@ ) from .._base_client import make_request_options from ..types.card_list_response import CardListResponse +from ..types.card_retrieve_response import CardRetrieveResponse __all__ = ["CardsResource", "AsyncCardsResource"] @@ -41,12 +42,60 @@ def with_streaming_response(self) -> CardsResourceWithStreamingResponse: """ return CardsResourceWithStreamingResponse(self) + def retrieve( + self, + card_id: str, + *, + account_id: str | Omit = omit, + user_id: 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, + ) -> CardRetrieveResponse: + """ + Retrieves a single card by its icrd\\__ identifier, including its secrets (full + card number, CVC, and cardholder name) for active cards. + + Args: + account_id: The owning account ID (a biz\\__ identifier). Provide this or user_id. + + user_id: The owning user ID (a user\\__ identifier). Provide this or account_id. + + 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 card_id: + raise ValueError(f"Expected a non-empty value for `card_id` but received {card_id!r}") + return self._get( + path_template("/cards/{card_id}", card_id=card_id), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "account_id": account_id, + "user_id": user_id, + }, + card_retrieve_params.CardRetrieveParams, + ), + ), + cast_to=CardRetrieveResponse, + ) + def list( self, *, account_id: str | Omit = omit, - card_id: str | Omit = omit, - reveal_secrets: bool | Omit = omit, user_id: 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. @@ -59,20 +108,14 @@ def list( Lists the issued (Whop Card) virtual and physical cards for a ledger account, including pending invitation cards that have not been issued by the card provider yet. The ledger's owner is passed as exactly one of account*id (a biz* - identifier) or user*id (a user* identifier). Pass card_id to address a single - card. Pass reveal_secrets=true to include the full card number and CVC for - active cards. Non-owner team members only see cards assigned to them. Users - without the payout:account:read scope can still list cards assigned to them (for - example moderators or external cardholders). + identifier) or user*id (a user* identifier). Non-owner team members only see + cards assigned to them. Users without the payout:account:read scope can still + list cards assigned to them (for example moderators or external cardholders). + Use GET /cards/:card_id to retrieve a single card with its secrets. Args: account_id: The owning account ID (a biz\\__ identifier). Provide this or user_id. - card_id: An icrd\\__ identifier. When provided, only that card is returned. - - reveal_secrets: When true, each active card includes a secrets object with the full card number - (pan), cvc, and cardholder name. - user_id: The owning user ID (a user\\__ identifier). Provide this or account_id. extra_headers: Send extra headers @@ -93,8 +136,6 @@ def list( query=maybe_transform( { "account_id": account_id, - "card_id": card_id, - "reveal_secrets": reveal_secrets, "user_id": user_id, }, card_list_params.CardListParams, @@ -124,12 +165,60 @@ def with_streaming_response(self) -> AsyncCardsResourceWithStreamingResponse: """ return AsyncCardsResourceWithStreamingResponse(self) + async def retrieve( + self, + card_id: str, + *, + account_id: str | Omit = omit, + user_id: 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, + ) -> CardRetrieveResponse: + """ + Retrieves a single card by its icrd\\__ identifier, including its secrets (full + card number, CVC, and cardholder name) for active cards. + + Args: + account_id: The owning account ID (a biz\\__ identifier). Provide this or user_id. + + user_id: The owning user ID (a user\\__ identifier). Provide this or account_id. + + 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 card_id: + raise ValueError(f"Expected a non-empty value for `card_id` but received {card_id!r}") + return await self._get( + path_template("/cards/{card_id}", card_id=card_id), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=await async_maybe_transform( + { + "account_id": account_id, + "user_id": user_id, + }, + card_retrieve_params.CardRetrieveParams, + ), + ), + cast_to=CardRetrieveResponse, + ) + async def list( self, *, account_id: str | Omit = omit, - card_id: str | Omit = omit, - reveal_secrets: bool | Omit = omit, user_id: 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. @@ -142,20 +231,14 @@ async def list( Lists the issued (Whop Card) virtual and physical cards for a ledger account, including pending invitation cards that have not been issued by the card provider yet. The ledger's owner is passed as exactly one of account*id (a biz* - identifier) or user*id (a user* identifier). Pass card_id to address a single - card. Pass reveal_secrets=true to include the full card number and CVC for - active cards. Non-owner team members only see cards assigned to them. Users - without the payout:account:read scope can still list cards assigned to them (for - example moderators or external cardholders). + identifier) or user*id (a user* identifier). Non-owner team members only see + cards assigned to them. Users without the payout:account:read scope can still + list cards assigned to them (for example moderators or external cardholders). + Use GET /cards/:card_id to retrieve a single card with its secrets. Args: account_id: The owning account ID (a biz\\__ identifier). Provide this or user_id. - card_id: An icrd\\__ identifier. When provided, only that card is returned. - - reveal_secrets: When true, each active card includes a secrets object with the full card number - (pan), cvc, and cardholder name. - user_id: The owning user ID (a user\\__ identifier). Provide this or account_id. extra_headers: Send extra headers @@ -176,8 +259,6 @@ async def list( query=await async_maybe_transform( { "account_id": account_id, - "card_id": card_id, - "reveal_secrets": reveal_secrets, "user_id": user_id, }, card_list_params.CardListParams, @@ -191,6 +272,9 @@ class CardsResourceWithRawResponse: def __init__(self, cards: CardsResource) -> None: self._cards = cards + self.retrieve = to_raw_response_wrapper( + cards.retrieve, + ) self.list = to_raw_response_wrapper( cards.list, ) @@ -200,6 +284,9 @@ class AsyncCardsResourceWithRawResponse: def __init__(self, cards: AsyncCardsResource) -> None: self._cards = cards + self.retrieve = async_to_raw_response_wrapper( + cards.retrieve, + ) self.list = async_to_raw_response_wrapper( cards.list, ) @@ -209,6 +296,9 @@ class CardsResourceWithStreamingResponse: def __init__(self, cards: CardsResource) -> None: self._cards = cards + self.retrieve = to_streamed_response_wrapper( + cards.retrieve, + ) self.list = to_streamed_response_wrapper( cards.list, ) @@ -218,6 +308,9 @@ class AsyncCardsResourceWithStreamingResponse: def __init__(self, cards: AsyncCardsResource) -> None: self._cards = cards + self.retrieve = async_to_streamed_response_wrapper( + cards.retrieve, + ) self.list = async_to_streamed_response_wrapper( cards.list, ) diff --git a/src/whop_sdk/types/__init__.py b/src/whop_sdk/types/__init__.py index ba5f9d1e..9adcea41 100644 --- a/src/whop_sdk/types/__init__.py +++ b/src/whop_sdk/types/__init__.py @@ -167,6 +167,7 @@ from .ad_group_list_params import AdGroupListParams as AdGroupListParams from .bounty_create_params import BountyCreateParams as BountyCreateParams from .bounty_list_response import BountyListResponse as BountyListResponse +from .card_retrieve_params import CardRetrieveParams as CardRetrieveParams from .course_create_params import CourseCreateParams as CourseCreateParams from .course_list_response import CourseListResponse as CourseListResponse from .course_update_params import CourseUpdateParams as CourseUpdateParams @@ -222,6 +223,7 @@ 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 .card_retrieve_response import CardRetrieveResponse as CardRetrieveResponse 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 diff --git a/src/whop_sdk/types/card_list_params.py b/src/whop_sdk/types/card_list_params.py index 77f65474..2487fd2e 100644 --- a/src/whop_sdk/types/card_list_params.py +++ b/src/whop_sdk/types/card_list_params.py @@ -11,14 +11,5 @@ class CardListParams(TypedDict, total=False): account_id: str """The owning account ID (a biz\\__ identifier). Provide this or user_id.""" - card_id: str - """An icrd\\__ identifier. When provided, only that card is returned.""" - - reveal_secrets: bool - """ - When true, each active card includes a secrets object with the full card number - (pan), cvc, and cardholder name. - """ - user_id: str """The owning user ID (a user\\__ identifier). Provide this or account_id.""" diff --git a/src/whop_sdk/types/card_list_response.py b/src/whop_sdk/types/card_list_response.py index c7c82c86..2aa57689 100644 --- a/src/whop_sdk/types/card_list_response.py +++ b/src/whop_sdk/types/card_list_response.py @@ -38,18 +38,18 @@ class DataLimit(BaseModel): class DataSecrets(BaseModel): """The card's sensitive details. - Only present when reveal_secrets=true; null for cards that are not active or whose details could not be retrieved. + Only present on GET /cards/:card_id (retrieve); null for cards that are not active or whose details could not be retrieved. """ + card_number: str + """The full card number.""" + cvc: str """The card verification code.""" name_on_card: Optional[str] = None """The cardholder name printed on the card.""" - pan: str - """The full card number.""" - class Data(BaseModel): id: str @@ -76,7 +76,7 @@ class Data(BaseModel): object: Literal["card"] - spent_last_month_cents: Optional[int] = None + spent_last_month: Optional[int] = None """Total spend in the last 30 days, in cents.""" status: Optional[str] = None @@ -91,8 +91,8 @@ class Data(BaseModel): secrets: Optional[DataSecrets] = None """The card's sensitive details. - Only present when reveal_secrets=true; null for cards that are not active or - whose details could not be retrieved. + Only present on GET /cards/:card_id (retrieve); null for cards that are not + active or whose details could not be retrieved. """ diff --git a/src/whop_sdk/types/card_retrieve_params.py b/src/whop_sdk/types/card_retrieve_params.py new file mode 100644 index 00000000..b325260c --- /dev/null +++ b/src/whop_sdk/types/card_retrieve_params.py @@ -0,0 +1,15 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import TypedDict + +__all__ = ["CardRetrieveParams"] + + +class CardRetrieveParams(TypedDict, total=False): + account_id: str + """The owning account ID (a biz\\__ identifier). Provide this or user_id.""" + + user_id: str + """The owning user ID (a user\\__ identifier). Provide this or account_id.""" diff --git a/src/whop_sdk/types/card_retrieve_response.py b/src/whop_sdk/types/card_retrieve_response.py new file mode 100644 index 00000000..35d5afad --- /dev/null +++ b/src/whop_sdk/types/card_retrieve_response.py @@ -0,0 +1,96 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from datetime import datetime +from typing_extensions import Literal + +from .._models import BaseModel + +__all__ = ["CardRetrieveResponse", "Billing", "Limit", "Secrets"] + + +class Billing(BaseModel): + """The billing address.""" + + city: Optional[str] = None + + country_code: Optional[str] = None + + line1: Optional[str] = None + + line2: Optional[str] = None + + postal_code: Optional[str] = None + + region: Optional[str] = None + + +class Limit(BaseModel): + """The spending limit configuration.""" + + amount: int + """The limit amount in cents.""" + + frequency: str + """The limit window, for example per24HourPeriod or perAuthorization.""" + + +class Secrets(BaseModel): + """The card's sensitive details. + + Only present on GET /cards/:card_id (retrieve); null for cards that are not active or whose details could not be retrieved. + """ + + card_number: str + """The full card number.""" + + cvc: str + """The card verification code.""" + + name_on_card: Optional[str] = None + """The cardholder name printed on the card.""" + + +class CardRetrieveResponse(BaseModel): + id: str + """The icrd\\__ identifier of the card.""" + + billing: Optional[Billing] = None + """The billing address.""" + + canceled_at: Optional[datetime] = None + + created_at: Optional[datetime] = None + + expiration_month: Optional[str] = None + + expiration_year: Optional[str] = None + + last4: Optional[str] = None + """The last 4 digits of the card number. Null for pending invitation cards.""" + + limit: Optional[Limit] = None + """The spending limit configuration.""" + + name: Optional[str] = None + + object: Literal["card"] + + spent_last_month: Optional[int] = None + """Total spend in the last 30 days, in cents.""" + + status: Optional[str] = None + """The card status, for example active, frozen, canceled, or invited.""" + + type: Optional[Literal["virtual", "physical"]] = None + """The card type.""" + + user_id: Optional[str] = None + """The user\\__ identifier of the cardholder, when assigned.""" + + secrets: Optional[Secrets] = None + """The card's sensitive details. + + Only present on GET /cards/:card_id (retrieve); null for cards that are not + active or whose details could not be retrieved. + """ diff --git a/tests/api_resources/test_cards.py b/tests/api_resources/test_cards.py index d644667b..f40015c2 100644 --- a/tests/api_resources/test_cards.py +++ b/tests/api_resources/test_cards.py @@ -9,7 +9,7 @@ from whop_sdk import Whop, AsyncWhop from tests.utils import assert_matches_type -from whop_sdk.types import CardListResponse +from whop_sdk.types import CardListResponse, CardRetrieveResponse base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") @@ -17,6 +17,58 @@ class TestCards: parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_retrieve(self, client: Whop) -> None: + card = client.cards.retrieve( + card_id="card_id", + ) + assert_matches_type(CardRetrieveResponse, card, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_retrieve_with_all_params(self, client: Whop) -> None: + card = client.cards.retrieve( + card_id="card_id", + account_id="account_id", + user_id="user_id", + ) + assert_matches_type(CardRetrieveResponse, card, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_raw_response_retrieve(self, client: Whop) -> None: + response = client.cards.with_raw_response.retrieve( + card_id="card_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + card = response.parse() + assert_matches_type(CardRetrieveResponse, card, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_streaming_response_retrieve(self, client: Whop) -> None: + with client.cards.with_streaming_response.retrieve( + card_id="card_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + card = response.parse() + assert_matches_type(CardRetrieveResponse, card, 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 `card_id` but received ''"): + client.cards.with_raw_response.retrieve( + card_id="", + ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_method_list(self, client: Whop) -> None: @@ -28,8 +80,6 @@ def test_method_list(self, client: Whop) -> None: def test_method_list_with_all_params(self, client: Whop) -> None: card = client.cards.list( account_id="account_id", - card_id="card_id", - reveal_secrets=True, user_id="user_id", ) assert_matches_type(CardListResponse, card, path=["response"]) @@ -62,6 +112,58 @@ class TestAsyncCards: "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] ) + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_retrieve(self, async_client: AsyncWhop) -> None: + card = await async_client.cards.retrieve( + card_id="card_id", + ) + assert_matches_type(CardRetrieveResponse, card, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_retrieve_with_all_params(self, async_client: AsyncWhop) -> None: + card = await async_client.cards.retrieve( + card_id="card_id", + account_id="account_id", + user_id="user_id", + ) + assert_matches_type(CardRetrieveResponse, card, 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.cards.with_raw_response.retrieve( + card_id="card_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + card = await response.parse() + assert_matches_type(CardRetrieveResponse, card, 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.cards.with_streaming_response.retrieve( + card_id="card_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + card = await response.parse() + assert_matches_type(CardRetrieveResponse, card, 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 `card_id` but received ''"): + await async_client.cards.with_raw_response.retrieve( + card_id="", + ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_method_list(self, async_client: AsyncWhop) -> None: @@ -73,8 +175,6 @@ async def test_method_list(self, async_client: AsyncWhop) -> None: async def test_method_list_with_all_params(self, async_client: AsyncWhop) -> None: card = await async_client.cards.list( account_id="account_id", - card_id="card_id", - reveal_secrets=True, user_id="user_id", ) assert_matches_type(CardListResponse, card, path=["response"]) From bba0ac7fda0f4df3d4ab41321e47086f5309d4fa Mon Sep 17 00:00:00 2001 From: stlc-bot Date: Tue, 16 Jun 2026 16:27:56 +0000 Subject: [PATCH 051/109] Feat(Txn Risk Scores): Connect transaction risk scores to dashboard payment detail page Stainless-Generated-From: 3a301cb5cd5ec27007ce3aab549b0d9672fb3bef --- src/whop_sdk/types/shared/payment.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/whop_sdk/types/shared/payment.py b/src/whop_sdk/types/shared/payment.py index 3e5f0792..94effb39 100644 --- a/src/whop_sdk/types/shared/payment.py +++ b/src/whop_sdk/types/shared/payment.py @@ -523,6 +523,20 @@ class Payment(BaseModel): otherwise false. Used to decide if Whop can attempt the charge again. """ + risk_score: Optional[int] = None + """ + Whop's in-house fraud risk score for this payment, from 0 (lowest risk) to 100 + (highest risk). Null when the payment has not been scored or scoring has not yet + completed. + """ + + risk_signals: Optional[Dict[str, object]] = None + """ + A curated set of factors behind the risk score, grouped by category (business + transaction history, buyer, device). Each entry has a key, human-readable label, + category, and value. Null when there is no risk assessment for this payment. + """ + settlement_amount: float """ The total amount charged to the customer for this payment, including taxes and From e262c215ee12ecd6ea2a6acb1549a011ed97937e Mon Sep 17 00:00:00 2001 From: stlc-bot Date: Tue, 16 Jun 2026 18:29:30 +0000 Subject: [PATCH 052/109] feat: chat webhooks Stainless-Generated-From: 0294c8ae80237acaa83b159ca6997d390aa159e1 --- api.md | 2 + src/whop_sdk/types/__init__.py | 2 + .../chat_message_created_webhook_event.py | 54 +++++++++++++++++ .../chat_reaction_created_webhook_event.py | 58 +++++++++++++++++++ src/whop_sdk/types/unwrap_webhook_event.py | 4 ++ src/whop_sdk/types/webhook_event.py | 2 + tests/api_resources/test_webhooks.py | 4 +- 7 files changed, 124 insertions(+), 2 deletions(-) create mode 100644 src/whop_sdk/types/chat_message_created_webhook_event.py create mode 100644 src/whop_sdk/types/chat_reaction_created_webhook_event.py diff --git a/api.md b/api.md index 0ae6196c..e3ad068c 100644 --- a/api.md +++ b/api.md @@ -153,6 +153,8 @@ from whop_sdk.types import ( WebhookCreateResponse, WebhookListResponse, WebhookDeleteResponse, + ChatMessageCreatedWebhookEvent, + ChatReactionCreatedWebhookEvent, CourseLessonInteractionCompletedWebhookEvent, DisputeCreatedWebhookEvent, DisputeUpdatedWebhookEvent, diff --git a/src/whop_sdk/types/__init__.py b/src/whop_sdk/types/__init__.py index 9adcea41..4b26d76e 100644 --- a/src/whop_sdk/types/__init__.py +++ b/src/whop_sdk/types/__init__.py @@ -399,10 +399,12 @@ from .withdrawal_created_webhook_event import WithdrawalCreatedWebhookEvent as WithdrawalCreatedWebhookEvent from .withdrawal_updated_webhook_event import WithdrawalUpdatedWebhookEvent as WithdrawalUpdatedWebhookEvent from .resolution_center_case_issue_type import ResolutionCenterCaseIssueType as ResolutionCenterCaseIssueType +from .chat_message_created_webhook_event import ChatMessageCreatedWebhookEvent as ChatMessageCreatedWebhookEvent from .checkout_configuration_list_params import CheckoutConfigurationListParams as CheckoutConfigurationListParams from .membership_activated_webhook_event import MembershipActivatedWebhookEvent as MembershipActivatedWebhookEvent from .payout_account_calculated_statuses import PayoutAccountCalculatedStatuses as PayoutAccountCalculatedStatuses from .resolution_center_case_list_params import ResolutionCenterCaseListParams as ResolutionCenterCaseListParams +from .chat_reaction_created_webhook_event import ChatReactionCreatedWebhookEvent as ChatReactionCreatedWebhookEvent from .dispute_alert_created_webhook_event import DisputeAlertCreatedWebhookEvent as DisputeAlertCreatedWebhookEvent from .invoice_mark_uncollectible_response import InvoiceMarkUncollectibleResponse as InvoiceMarkUncollectibleResponse from .payout_method_created_webhook_event import PayoutMethodCreatedWebhookEvent as PayoutMethodCreatedWebhookEvent diff --git a/src/whop_sdk/types/chat_message_created_webhook_event.py b/src/whop_sdk/types/chat_message_created_webhook_event.py new file mode 100644 index 00000000..6f5c6493 --- /dev/null +++ b/src/whop_sdk/types/chat_message_created_webhook_event.py @@ -0,0 +1,54 @@ +# 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 +from .shared.message import Message + +__all__ = ["ChatMessageCreatedWebhookEvent", "Data", "DataAudience", "DataChannel"] + + +class DataAudience(BaseModel): + type: Literal["channel", "users"] + + user_ids: Optional[List[str]] = None + + +class DataChannel(BaseModel): + id: str + + type: Literal["chat", "direct_message", "support"] + + experience_id: Optional[str] = None + + +class Data(BaseModel): + audience: DataAudience + + channel: DataChannel + + message: Message + """A message sent within an experience chat, direct message, or group chat.""" + + reason: str + + +class ChatMessageCreatedWebhookEvent(BaseModel): + id: str + """A unique ID for every single webhook request""" + + api_version: Literal["v1"] + """The API version for this webhook""" + + data: Data + + timestamp: datetime + """The timestamp in ISO 8601 format that the webhook was sent at on the server""" + + type: Literal["chat.message.created"] + """The webhook event type""" + + company_id: Optional[str] = None + """The company ID that this webhook event is associated with""" diff --git a/src/whop_sdk/types/chat_reaction_created_webhook_event.py b/src/whop_sdk/types/chat_reaction_created_webhook_event.py new file mode 100644 index 00000000..fe79585c --- /dev/null +++ b/src/whop_sdk/types/chat_reaction_created_webhook_event.py @@ -0,0 +1,58 @@ +# 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 +from .shared.message import Message +from .shared.reaction import Reaction + +__all__ = ["ChatReactionCreatedWebhookEvent", "Data", "DataAudience", "DataChannel"] + + +class DataAudience(BaseModel): + type: Literal["channel", "users"] + + user_ids: Optional[List[str]] = None + + +class DataChannel(BaseModel): + id: str + + type: Literal["chat", "direct_message", "support"] + + experience_id: Optional[str] = None + + +class Data(BaseModel): + audience: DataAudience + + channel: DataChannel + + message: Message + """A message sent within an experience chat, direct message, or group chat.""" + + reaction: Reaction + """A single reaction left by a user on a feed post, such as a like or emoji.""" + + reason: str + + +class ChatReactionCreatedWebhookEvent(BaseModel): + id: str + """A unique ID for every single webhook request""" + + api_version: Literal["v1"] + """The API version for this webhook""" + + data: Data + + timestamp: datetime + """The timestamp in ISO 8601 format that the webhook was sent at on the server""" + + type: Literal["chat.reaction.created"] + """The webhook event type""" + + company_id: Optional[str] = None + """The company ID that this webhook event is associated with""" diff --git a/src/whop_sdk/types/unwrap_webhook_event.py b/src/whop_sdk/types/unwrap_webhook_event.py index b21ff447..dc6ad61b 100644 --- a/src/whop_sdk/types/unwrap_webhook_event.py +++ b/src/whop_sdk/types/unwrap_webhook_event.py @@ -22,7 +22,9 @@ from .payment_succeeded_webhook_event import PaymentSucceededWebhookEvent from .withdrawal_created_webhook_event import WithdrawalCreatedWebhookEvent from .withdrawal_updated_webhook_event import WithdrawalUpdatedWebhookEvent +from .chat_message_created_webhook_event import ChatMessageCreatedWebhookEvent from .membership_activated_webhook_event import MembershipActivatedWebhookEvent +from .chat_reaction_created_webhook_event import ChatReactionCreatedWebhookEvent from .dispute_alert_created_webhook_event import DisputeAlertCreatedWebhookEvent from .payout_method_created_webhook_event import PayoutMethodCreatedWebhookEvent from .setup_intent_canceled_webhook_event import SetupIntentCanceledWebhookEvent @@ -48,6 +50,8 @@ UnwrapWebhookEvent: TypeAlias = Annotated[ Union[ + ChatMessageCreatedWebhookEvent, + ChatReactionCreatedWebhookEvent, CourseLessonInteractionCompletedWebhookEvent, DisputeCreatedWebhookEvent, DisputeUpdatedWebhookEvent, diff --git a/src/whop_sdk/types/webhook_event.py b/src/whop_sdk/types/webhook_event.py index da9b387d..7167e81a 100644 --- a/src/whop_sdk/types/webhook_event.py +++ b/src/whop_sdk/types/webhook_event.py @@ -34,6 +34,8 @@ "resolution_center_case.created", "resolution_center_case.updated", "resolution_center_case.decided", + "chat.message.created", + "chat.reaction.created", "payment.created", "payment.succeeded", "payment.failed", diff --git a/tests/api_resources/test_webhooks.py b/tests/api_resources/test_webhooks.py index 61e0d357..ab622922 100644 --- a/tests/api_resources/test_webhooks.py +++ b/tests/api_resources/test_webhooks.py @@ -272,7 +272,7 @@ def test_method_unwrap(self, client: Whop, client_opt: str | None, method_opt: s client = client.with_options(webhook_key=client_opt) - data = """{"id":"msg_xxxxxxxxxxxxxxxxxxxxxxxx","api_version":"v1","data":{"id":"crsli_xxxxxxxxxxxx","completed":true,"course":{"id":"cors_xxxxxxxxxxxxx","experience":{"id":"exp_xxxxxxxxxxxxxx"},"title":"Introduction to Technical Analysis"},"created_at":"2023-12-01T05:00:00.401Z","lesson":{"id":"lesn_xxxxxxxxxxxxx","chapter":{"id":"chap_xxxxxxxxxxxxx"},"title":"Understanding Candlestick Patterns"},"user":{"id":"user_xxxxxxxxxxxxx","name":"John Doe","username":"johndoe42"}},"timestamp":"2025-01-01T00:00:00.000Z","type":"course_lesson_interaction.completed","company_id":"biz_xxxxxxxxxxxxxx"}""" + data = """{"id":"msg_xxxxxxxxxxxxxxxxxxxxxxxx","api_version":"v1","data":{"audience":{"type":"channel","user_ids":["user_xxxxxxxxxxxxxx"]},"channel":{"id":"feed_xxxxxxxxxxxxxx","type":"chat","experience_id":"exp_xxxxxxxxxxxxxx"},"message":{"id":"id","content":"Hey, are you available for a **quick call**?","created_at":"2023-12-01T05:00:00.401Z","is_edited":true,"is_pinned":true,"mentions":["string"],"mentions_everyone":true,"message_type":"regular","poll":{"options":[{"id":"id","text":"text"}]},"poll_votes":[{"count":42,"option_id":"option_id"}],"reaction_counts":[{"count":42,"emoji":"emoji"}],"replying_to_message_id":"replying_to_message_id","updated_at":"2023-12-01T05:00:00.401Z","user":{"id":"user_xxxxxxxxxxxxx","name":"John Doe","username":"johndoe42"},"view_count":42},"reason":"channel_message"},"timestamp":"2025-01-01T00:00:00.000Z","type":"chat.message.created","company_id":"biz_xxxxxxxxxxxxxx"}""" msg_id = "1" timestamp = datetime.now(tz=timezone.utc) sig = hook.sign(msg_id=msg_id, timestamp=timestamp, data=data) @@ -549,7 +549,7 @@ def test_method_unwrap(self, async_client: Whop, client_opt: str | None, method_ async_client = async_client.with_options(webhook_key=client_opt) - data = """{"id":"msg_xxxxxxxxxxxxxxxxxxxxxxxx","api_version":"v1","data":{"id":"crsli_xxxxxxxxxxxx","completed":true,"course":{"id":"cors_xxxxxxxxxxxxx","experience":{"id":"exp_xxxxxxxxxxxxxx"},"title":"Introduction to Technical Analysis"},"created_at":"2023-12-01T05:00:00.401Z","lesson":{"id":"lesn_xxxxxxxxxxxxx","chapter":{"id":"chap_xxxxxxxxxxxxx"},"title":"Understanding Candlestick Patterns"},"user":{"id":"user_xxxxxxxxxxxxx","name":"John Doe","username":"johndoe42"}},"timestamp":"2025-01-01T00:00:00.000Z","type":"course_lesson_interaction.completed","company_id":"biz_xxxxxxxxxxxxxx"}""" + data = """{"id":"msg_xxxxxxxxxxxxxxxxxxxxxxxx","api_version":"v1","data":{"audience":{"type":"channel","user_ids":["user_xxxxxxxxxxxxxx"]},"channel":{"id":"feed_xxxxxxxxxxxxxx","type":"chat","experience_id":"exp_xxxxxxxxxxxxxx"},"message":{"id":"id","content":"Hey, are you available for a **quick call**?","created_at":"2023-12-01T05:00:00.401Z","is_edited":true,"is_pinned":true,"mentions":["string"],"mentions_everyone":true,"message_type":"regular","poll":{"options":[{"id":"id","text":"text"}]},"poll_votes":[{"count":42,"option_id":"option_id"}],"reaction_counts":[{"count":42,"emoji":"emoji"}],"replying_to_message_id":"replying_to_message_id","updated_at":"2023-12-01T05:00:00.401Z","user":{"id":"user_xxxxxxxxxxxxx","name":"John Doe","username":"johndoe42"},"view_count":42},"reason":"channel_message"},"timestamp":"2025-01-01T00:00:00.000Z","type":"chat.message.created","company_id":"biz_xxxxxxxxxxxxxx"}""" msg_id = "1" timestamp = datetime.now(tz=timezone.utc) sig = hook.sign(msg_id=msg_id, timestamp=timestamp, data=data) From a2613c1a2b2c6d4cedb0c1dc652d64ef634081dd Mon Sep 17 00:00:00 2001 From: stlc-bot Date: Tue, 16 Jun 2026 21:44:08 +0000 Subject: [PATCH 053/109] feat(backend): endpoint for /referrals/businesses Stainless-Generated-From: a69cc7b99396ae8d3b4cc2f1602fd6a95dcebbe8 --- .stats.yml | 2 +- api.md | 32 ++ src/whop_sdk/_client.py | 38 ++ src/whop_sdk/resources/__init__.py | 14 + src/whop_sdk/resources/referrals/__init__.py | 33 ++ .../referrals/businesses/__init__.py | 33 ++ .../referrals/businesses/businesses.py | 486 ++++++++++++++++++ .../referrals/businesses/earnings.py | 238 +++++++++ src/whop_sdk/resources/referrals/referrals.py | 102 ++++ src/whop_sdk/types/referrals/__init__.py | 9 + .../business_list_earnings_params.py | 32 ++ .../business_list_earnings_response.py | 112 ++++ .../types/referrals/business_list_params.py | 30 ++ .../types/referrals/business_list_response.py | 55 ++ .../referrals/business_retrieve_response.py | 55 ++ .../types/referrals/businesses/__init__.py | 6 + .../businesses/earning_list_params.py | 32 ++ .../businesses/earning_list_response.py | 112 ++++ tests/api_resources/referrals/__init__.py | 1 + .../referrals/businesses/__init__.py | 1 + .../referrals/businesses/test_earnings.py | 141 +++++ .../referrals/test_businesses.py | 281 ++++++++++ 22 files changed, 1844 insertions(+), 1 deletion(-) create mode 100644 src/whop_sdk/resources/referrals/__init__.py create mode 100644 src/whop_sdk/resources/referrals/businesses/__init__.py create mode 100644 src/whop_sdk/resources/referrals/businesses/businesses.py create mode 100644 src/whop_sdk/resources/referrals/businesses/earnings.py create mode 100644 src/whop_sdk/resources/referrals/referrals.py create mode 100644 src/whop_sdk/types/referrals/__init__.py create mode 100644 src/whop_sdk/types/referrals/business_list_earnings_params.py create mode 100644 src/whop_sdk/types/referrals/business_list_earnings_response.py create mode 100644 src/whop_sdk/types/referrals/business_list_params.py create mode 100644 src/whop_sdk/types/referrals/business_list_response.py create mode 100644 src/whop_sdk/types/referrals/business_retrieve_response.py create mode 100644 src/whop_sdk/types/referrals/businesses/__init__.py create mode 100644 src/whop_sdk/types/referrals/businesses/earning_list_params.py create mode 100644 src/whop_sdk/types/referrals/businesses/earning_list_response.py create mode 100644 tests/api_resources/referrals/__init__.py create mode 100644 tests/api_resources/referrals/businesses/__init__.py create mode 100644 tests/api_resources/referrals/businesses/test_earnings.py create mode 100644 tests/api_resources/referrals/test_businesses.py diff --git a/.stats.yml b/.stats.yml index dd9fa391..04d8692c 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1 +1 @@ -configured_endpoints: 237 +configured_endpoints: 241 diff --git a/api.md b/api.md index e3ad068c..696963c6 100644 --- a/api.md +++ b/api.md @@ -766,6 +766,38 @@ Methods: - client.payouts.list(\*\*params) -> SyncCursorPage[PayoutListResponse] +# Referrals + +## Businesses + +Types: + +```python +from whop_sdk.types.referrals import ( + BusinessRetrieveResponse, + BusinessListResponse, + BusinessListEarningsResponse, +) +``` + +Methods: + +- client.referrals.businesses.retrieve(id) -> BusinessRetrieveResponse +- client.referrals.businesses.list(\*\*params) -> SyncCursorPage[BusinessListResponse] +- client.referrals.businesses.list_earnings(\*\*params) -> SyncCursorPage[BusinessListEarningsResponse] + +### Earnings + +Types: + +```python +from whop_sdk.types.referrals.businesses import EarningListResponse +``` + +Methods: + +- client.referrals.businesses.earnings.list(id, \*\*params) -> SyncCursorPage[EarningListResponse] + # Cards Types: diff --git a/src/whop_sdk/_client.py b/src/whop_sdk/_client.py index b45b8a6d..14ab4263 100644 --- a/src/whop_sdk/_client.py +++ b/src/whop_sdk/_client.py @@ -66,6 +66,7 @@ ad_groups, companies, reactions, + referrals, shipments, transfers, ad_reports, @@ -164,6 +165,7 @@ from .resources.authorized_users import AuthorizedUsersResource, AsyncAuthorizedUsersResource from .resources.support_channels import SupportChannelsResource, AsyncSupportChannelsResource from .resources.financial_activity import FinancialActivityResource, AsyncFinancialActivityResource + from .resources.referrals.referrals import ReferralsResource, AsyncReferralsResource from .resources.affiliates.affiliates import AffiliatesResource, AsyncAffiliatesResource from .resources.checkout_configurations import CheckoutConfigurationsResource, AsyncCheckoutConfigurationsResource from .resources.resolution_center_cases import ResolutionCenterCasesResource, AsyncResolutionCenterCasesResource @@ -545,6 +547,12 @@ def payouts(self) -> PayoutsResource: return PayoutsResource(self) + @cached_property + def referrals(self) -> ReferralsResource: + from .resources.referrals import ReferralsResource + + return ReferralsResource(self) + @cached_property def cards(self) -> CardsResource: from .resources.cards import CardsResource @@ -1204,6 +1212,12 @@ def payouts(self) -> AsyncPayoutsResource: return AsyncPayoutsResource(self) + @cached_property + def referrals(self) -> AsyncReferralsResource: + from .resources.referrals import AsyncReferralsResource + + return AsyncReferralsResource(self) + @cached_property def cards(self) -> AsyncCardsResource: from .resources.cards import AsyncCardsResource @@ -1783,6 +1797,12 @@ def payouts(self) -> payouts.PayoutsResourceWithRawResponse: return PayoutsResourceWithRawResponse(self._client.payouts) + @cached_property + def referrals(self) -> referrals.ReferralsResourceWithRawResponse: + from .resources.referrals import ReferralsResourceWithRawResponse + + return ReferralsResourceWithRawResponse(self._client.referrals) + @cached_property def cards(self) -> cards.CardsResourceWithRawResponse: from .resources.cards import CardsResourceWithRawResponse @@ -2244,6 +2264,12 @@ def payouts(self) -> payouts.AsyncPayoutsResourceWithRawResponse: return AsyncPayoutsResourceWithRawResponse(self._client.payouts) + @cached_property + def referrals(self) -> referrals.AsyncReferralsResourceWithRawResponse: + from .resources.referrals import AsyncReferralsResourceWithRawResponse + + return AsyncReferralsResourceWithRawResponse(self._client.referrals) + @cached_property def cards(self) -> cards.AsyncCardsResourceWithRawResponse: from .resources.cards import AsyncCardsResourceWithRawResponse @@ -2707,6 +2733,12 @@ def payouts(self) -> payouts.PayoutsResourceWithStreamingResponse: return PayoutsResourceWithStreamingResponse(self._client.payouts) + @cached_property + def referrals(self) -> referrals.ReferralsResourceWithStreamingResponse: + from .resources.referrals import ReferralsResourceWithStreamingResponse + + return ReferralsResourceWithStreamingResponse(self._client.referrals) + @cached_property def cards(self) -> cards.CardsResourceWithStreamingResponse: from .resources.cards import CardsResourceWithStreamingResponse @@ -3172,6 +3204,12 @@ def payouts(self) -> payouts.AsyncPayoutsResourceWithStreamingResponse: return AsyncPayoutsResourceWithStreamingResponse(self._client.payouts) + @cached_property + def referrals(self) -> referrals.AsyncReferralsResourceWithStreamingResponse: + from .resources.referrals import AsyncReferralsResourceWithStreamingResponse + + return AsyncReferralsResourceWithStreamingResponse(self._client.referrals) + @cached_property def cards(self) -> cards.AsyncCardsResourceWithStreamingResponse: from .resources.cards import AsyncCardsResourceWithStreamingResponse diff --git a/src/whop_sdk/resources/__init__.py b/src/whop_sdk/resources/__init__.py index e056c88e..cbc4c462 100644 --- a/src/whop_sdk/resources/__init__.py +++ b/src/whop_sdk/resources/__init__.py @@ -240,6 +240,14 @@ ReactionsResourceWithStreamingResponse, AsyncReactionsResourceWithStreamingResponse, ) +from .referrals import ( + ReferralsResource, + AsyncReferralsResource, + ReferralsResourceWithRawResponse, + AsyncReferralsResourceWithRawResponse, + ReferralsResourceWithStreamingResponse, + AsyncReferralsResourceWithStreamingResponse, +) from .shipments import ( ShipmentsResource, AsyncShipmentsResource, @@ -776,6 +784,12 @@ "AsyncPayoutsResourceWithRawResponse", "PayoutsResourceWithStreamingResponse", "AsyncPayoutsResourceWithStreamingResponse", + "ReferralsResource", + "AsyncReferralsResource", + "ReferralsResourceWithRawResponse", + "AsyncReferralsResourceWithRawResponse", + "ReferralsResourceWithStreamingResponse", + "AsyncReferralsResourceWithStreamingResponse", "CardsResource", "AsyncCardsResource", "CardsResourceWithRawResponse", diff --git a/src/whop_sdk/resources/referrals/__init__.py b/src/whop_sdk/resources/referrals/__init__.py new file mode 100644 index 00000000..9d40a31a --- /dev/null +++ b/src/whop_sdk/resources/referrals/__init__.py @@ -0,0 +1,33 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from .referrals import ( + ReferralsResource, + AsyncReferralsResource, + ReferralsResourceWithRawResponse, + AsyncReferralsResourceWithRawResponse, + ReferralsResourceWithStreamingResponse, + AsyncReferralsResourceWithStreamingResponse, +) +from .businesses import ( + BusinessesResource, + AsyncBusinessesResource, + BusinessesResourceWithRawResponse, + AsyncBusinessesResourceWithRawResponse, + BusinessesResourceWithStreamingResponse, + AsyncBusinessesResourceWithStreamingResponse, +) + +__all__ = [ + "BusinessesResource", + "AsyncBusinessesResource", + "BusinessesResourceWithRawResponse", + "AsyncBusinessesResourceWithRawResponse", + "BusinessesResourceWithStreamingResponse", + "AsyncBusinessesResourceWithStreamingResponse", + "ReferralsResource", + "AsyncReferralsResource", + "ReferralsResourceWithRawResponse", + "AsyncReferralsResourceWithRawResponse", + "ReferralsResourceWithStreamingResponse", + "AsyncReferralsResourceWithStreamingResponse", +] diff --git a/src/whop_sdk/resources/referrals/businesses/__init__.py b/src/whop_sdk/resources/referrals/businesses/__init__.py new file mode 100644 index 00000000..bd5fa65b --- /dev/null +++ b/src/whop_sdk/resources/referrals/businesses/__init__.py @@ -0,0 +1,33 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from .earnings import ( + EarningsResource, + AsyncEarningsResource, + EarningsResourceWithRawResponse, + AsyncEarningsResourceWithRawResponse, + EarningsResourceWithStreamingResponse, + AsyncEarningsResourceWithStreamingResponse, +) +from .businesses import ( + BusinessesResource, + AsyncBusinessesResource, + BusinessesResourceWithRawResponse, + AsyncBusinessesResourceWithRawResponse, + BusinessesResourceWithStreamingResponse, + AsyncBusinessesResourceWithStreamingResponse, +) + +__all__ = [ + "EarningsResource", + "AsyncEarningsResource", + "EarningsResourceWithRawResponse", + "AsyncEarningsResourceWithRawResponse", + "EarningsResourceWithStreamingResponse", + "AsyncEarningsResourceWithStreamingResponse", + "BusinessesResource", + "AsyncBusinessesResource", + "BusinessesResourceWithRawResponse", + "AsyncBusinessesResourceWithRawResponse", + "BusinessesResourceWithStreamingResponse", + "AsyncBusinessesResourceWithStreamingResponse", +] diff --git a/src/whop_sdk/resources/referrals/businesses/businesses.py b/src/whop_sdk/resources/referrals/businesses/businesses.py new file mode 100644 index 00000000..fbe717b0 --- /dev/null +++ b/src/whop_sdk/resources/referrals/businesses/businesses.py @@ -0,0 +1,486 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Literal + +import httpx + +from .earnings import ( + EarningsResource, + AsyncEarningsResource, + EarningsResourceWithRawResponse, + AsyncEarningsResourceWithRawResponse, + EarningsResourceWithStreamingResponse, + AsyncEarningsResourceWithStreamingResponse, +) +from ...._types import Body, Omit, Query, Headers, NotGiven, omit, not_given +from ...._utils import path_template, 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.referrals import business_list_params, business_list_earnings_params +from ....types.referrals.business_list_response import BusinessListResponse +from ....types.referrals.business_retrieve_response import BusinessRetrieveResponse +from ....types.referrals.business_list_earnings_response import BusinessListEarningsResponse + +__all__ = ["BusinessesResource", "AsyncBusinessesResource"] + + +class BusinessesResource(SyncAPIResource): + @cached_property + def earnings(self) -> EarningsResource: + return EarningsResource(self._client) + + @cached_property + def with_raw_response(self) -> BusinessesResourceWithRawResponse: + """ + 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 BusinessesResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> BusinessesResourceWithStreamingResponse: + """ + 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 BusinessesResourceWithStreamingResponse(self) + + 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, + ) -> BusinessRetrieveResponse: + """ + Retrieves a single referred business and its referral terms. + + 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("/referrals/businesses/{id}", id=id), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=BusinessRetrieveResponse, + ) + + def list( + self, + *, + after: str | Omit = omit, + before: str | Omit = omit, + first: int | Omit = omit, + has_earnings: bool | Omit = omit, + last: int | Omit = omit, + status: Literal["active", "removed"] | 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[BusinessListResponse]: + """ + Lists the businesses the authenticated user referred onto Whop, most recent + first. + + Args: + after: Cursor to fetch the page after (from page_info.end_cursor). + + before: Cursor to fetch the page before (from page_info.start_cursor). + + first: Number of business referrals to return from the start of the window. + + has_earnings: When true, only businesses that have paid out at least one earning to the + caller. + + last: Number of business referrals to return from the end of the window. + + status: Filter by referral status. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return self._get_api_list( + "/referrals/businesses", + page=SyncCursorPage[BusinessListResponse], + 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, + "first": first, + "has_earnings": has_earnings, + "last": last, + "status": status, + }, + business_list_params.BusinessListParams, + ), + ), + model=BusinessListResponse, + ) + + def list_earnings( + self, + *, + after: str | Omit = omit, + before: str | Omit = omit, + first: int | Omit = omit, + include: Literal["receipt_fees"] | Omit = omit, + last: int | Omit = omit, + order: Literal["asc", "desc"] | Omit = omit, + sort: Literal["created_at", "amount", "payout_at"] | Omit = omit, + status: Literal["awaiting_settlement", "pending", "completed", "canceled", "reversed"] | 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[BusinessListEarningsResponse]: + """ + Lists every business referral earning the authenticated user has, most recent + first. + + Args: + include: Comma-separated extras to embed. Supported: receipt_fees (adds amount_after_fees + and the receipt_fees breakdown). + + order: Sort direction. + + sort: Field to sort earnings by. + + status: Filter by earning status. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return self._get_api_list( + "/referrals/businesses/earnings", + page=SyncCursorPage[BusinessListEarningsResponse], + 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, + "first": first, + "include": include, + "last": last, + "order": order, + "sort": sort, + "status": status, + }, + business_list_earnings_params.BusinessListEarningsParams, + ), + ), + model=BusinessListEarningsResponse, + ) + + +class AsyncBusinessesResource(AsyncAPIResource): + @cached_property + def earnings(self) -> AsyncEarningsResource: + return AsyncEarningsResource(self._client) + + @cached_property + def with_raw_response(self) -> AsyncBusinessesResourceWithRawResponse: + """ + 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 AsyncBusinessesResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncBusinessesResourceWithStreamingResponse: + """ + 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 AsyncBusinessesResourceWithStreamingResponse(self) + + 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, + ) -> BusinessRetrieveResponse: + """ + Retrieves a single referred business and its referral terms. + + 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("/referrals/businesses/{id}", id=id), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=BusinessRetrieveResponse, + ) + + def list( + self, + *, + after: str | Omit = omit, + before: str | Omit = omit, + first: int | Omit = omit, + has_earnings: bool | Omit = omit, + last: int | Omit = omit, + status: Literal["active", "removed"] | 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[BusinessListResponse, AsyncCursorPage[BusinessListResponse]]: + """ + Lists the businesses the authenticated user referred onto Whop, most recent + first. + + Args: + after: Cursor to fetch the page after (from page_info.end_cursor). + + before: Cursor to fetch the page before (from page_info.start_cursor). + + first: Number of business referrals to return from the start of the window. + + has_earnings: When true, only businesses that have paid out at least one earning to the + caller. + + last: Number of business referrals to return from the end of the window. + + status: Filter by referral status. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return self._get_api_list( + "/referrals/businesses", + page=AsyncCursorPage[BusinessListResponse], + 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, + "first": first, + "has_earnings": has_earnings, + "last": last, + "status": status, + }, + business_list_params.BusinessListParams, + ), + ), + model=BusinessListResponse, + ) + + def list_earnings( + self, + *, + after: str | Omit = omit, + before: str | Omit = omit, + first: int | Omit = omit, + include: Literal["receipt_fees"] | Omit = omit, + last: int | Omit = omit, + order: Literal["asc", "desc"] | Omit = omit, + sort: Literal["created_at", "amount", "payout_at"] | Omit = omit, + status: Literal["awaiting_settlement", "pending", "completed", "canceled", "reversed"] | 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[BusinessListEarningsResponse, AsyncCursorPage[BusinessListEarningsResponse]]: + """ + Lists every business referral earning the authenticated user has, most recent + first. + + Args: + include: Comma-separated extras to embed. Supported: receipt_fees (adds amount_after_fees + and the receipt_fees breakdown). + + order: Sort direction. + + sort: Field to sort earnings by. + + status: Filter by earning status. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return self._get_api_list( + "/referrals/businesses/earnings", + page=AsyncCursorPage[BusinessListEarningsResponse], + 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, + "first": first, + "include": include, + "last": last, + "order": order, + "sort": sort, + "status": status, + }, + business_list_earnings_params.BusinessListEarningsParams, + ), + ), + model=BusinessListEarningsResponse, + ) + + +class BusinessesResourceWithRawResponse: + def __init__(self, businesses: BusinessesResource) -> None: + self._businesses = businesses + + self.retrieve = to_raw_response_wrapper( + businesses.retrieve, + ) + self.list = to_raw_response_wrapper( + businesses.list, + ) + self.list_earnings = to_raw_response_wrapper( + businesses.list_earnings, + ) + + @cached_property + def earnings(self) -> EarningsResourceWithRawResponse: + return EarningsResourceWithRawResponse(self._businesses.earnings) + + +class AsyncBusinessesResourceWithRawResponse: + def __init__(self, businesses: AsyncBusinessesResource) -> None: + self._businesses = businesses + + self.retrieve = async_to_raw_response_wrapper( + businesses.retrieve, + ) + self.list = async_to_raw_response_wrapper( + businesses.list, + ) + self.list_earnings = async_to_raw_response_wrapper( + businesses.list_earnings, + ) + + @cached_property + def earnings(self) -> AsyncEarningsResourceWithRawResponse: + return AsyncEarningsResourceWithRawResponse(self._businesses.earnings) + + +class BusinessesResourceWithStreamingResponse: + def __init__(self, businesses: BusinessesResource) -> None: + self._businesses = businesses + + self.retrieve = to_streamed_response_wrapper( + businesses.retrieve, + ) + self.list = to_streamed_response_wrapper( + businesses.list, + ) + self.list_earnings = to_streamed_response_wrapper( + businesses.list_earnings, + ) + + @cached_property + def earnings(self) -> EarningsResourceWithStreamingResponse: + return EarningsResourceWithStreamingResponse(self._businesses.earnings) + + +class AsyncBusinessesResourceWithStreamingResponse: + def __init__(self, businesses: AsyncBusinessesResource) -> None: + self._businesses = businesses + + self.retrieve = async_to_streamed_response_wrapper( + businesses.retrieve, + ) + self.list = async_to_streamed_response_wrapper( + businesses.list, + ) + self.list_earnings = async_to_streamed_response_wrapper( + businesses.list_earnings, + ) + + @cached_property + def earnings(self) -> AsyncEarningsResourceWithStreamingResponse: + return AsyncEarningsResourceWithStreamingResponse(self._businesses.earnings) diff --git a/src/whop_sdk/resources/referrals/businesses/earnings.py b/src/whop_sdk/resources/referrals/businesses/earnings.py new file mode 100644 index 00000000..61c264bb --- /dev/null +++ b/src/whop_sdk/resources/referrals/businesses/earnings.py @@ -0,0 +1,238 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Literal + +import httpx + +from ...._types import Body, Omit, Query, Headers, NotGiven, omit, not_given +from ...._utils import path_template, 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.referrals.businesses import earning_list_params +from ....types.referrals.businesses.earning_list_response import EarningListResponse + +__all__ = ["EarningsResource", "AsyncEarningsResource"] + + +class EarningsResource(SyncAPIResource): + @cached_property + def with_raw_response(self) -> EarningsResourceWithRawResponse: + """ + 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 EarningsResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> EarningsResourceWithStreamingResponse: + """ + 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 EarningsResourceWithStreamingResponse(self) + + def list( + self, + id: str, + *, + after: str | Omit = omit, + before: str | Omit = omit, + first: int | Omit = omit, + include: Literal["receipt_fees"] | Omit = omit, + last: int | Omit = omit, + order: Literal["asc", "desc"] | Omit = omit, + sort: Literal["created_at", "amount", "payout_at"] | Omit = omit, + status: Literal["awaiting_settlement", "pending", "completed", "canceled", "reversed"] | 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[EarningListResponse]: + """ + Lists the earnings Whop pays out for one referred business's activity, most + recent first. + + Args: + include: Comma-separated extras to embed. Supported: receipt_fees (adds amount_after_fees + and the receipt_fees breakdown). + + order: Sort direction. + + sort: Field to sort earnings by. + + status: Filter by earning status. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not id: + raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") + return self._get_api_list( + path_template("/referrals/businesses/{id}/earnings", id=id), + page=SyncCursorPage[EarningListResponse], + 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, + "first": first, + "include": include, + "last": last, + "order": order, + "sort": sort, + "status": status, + }, + earning_list_params.EarningListParams, + ), + ), + model=EarningListResponse, + ) + + +class AsyncEarningsResource(AsyncAPIResource): + @cached_property + def with_raw_response(self) -> AsyncEarningsResourceWithRawResponse: + """ + 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 AsyncEarningsResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncEarningsResourceWithStreamingResponse: + """ + 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 AsyncEarningsResourceWithStreamingResponse(self) + + def list( + self, + id: str, + *, + after: str | Omit = omit, + before: str | Omit = omit, + first: int | Omit = omit, + include: Literal["receipt_fees"] | Omit = omit, + last: int | Omit = omit, + order: Literal["asc", "desc"] | Omit = omit, + sort: Literal["created_at", "amount", "payout_at"] | Omit = omit, + status: Literal["awaiting_settlement", "pending", "completed", "canceled", "reversed"] | 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[EarningListResponse, AsyncCursorPage[EarningListResponse]]: + """ + Lists the earnings Whop pays out for one referred business's activity, most + recent first. + + Args: + include: Comma-separated extras to embed. Supported: receipt_fees (adds amount_after_fees + and the receipt_fees breakdown). + + order: Sort direction. + + sort: Field to sort earnings by. + + status: Filter by earning status. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not id: + raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") + return self._get_api_list( + path_template("/referrals/businesses/{id}/earnings", id=id), + page=AsyncCursorPage[EarningListResponse], + 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, + "first": first, + "include": include, + "last": last, + "order": order, + "sort": sort, + "status": status, + }, + earning_list_params.EarningListParams, + ), + ), + model=EarningListResponse, + ) + + +class EarningsResourceWithRawResponse: + def __init__(self, earnings: EarningsResource) -> None: + self._earnings = earnings + + self.list = to_raw_response_wrapper( + earnings.list, + ) + + +class AsyncEarningsResourceWithRawResponse: + def __init__(self, earnings: AsyncEarningsResource) -> None: + self._earnings = earnings + + self.list = async_to_raw_response_wrapper( + earnings.list, + ) + + +class EarningsResourceWithStreamingResponse: + def __init__(self, earnings: EarningsResource) -> None: + self._earnings = earnings + + self.list = to_streamed_response_wrapper( + earnings.list, + ) + + +class AsyncEarningsResourceWithStreamingResponse: + def __init__(self, earnings: AsyncEarningsResource) -> None: + self._earnings = earnings + + self.list = async_to_streamed_response_wrapper( + earnings.list, + ) diff --git a/src/whop_sdk/resources/referrals/referrals.py b/src/whop_sdk/resources/referrals/referrals.py new file mode 100644 index 00000000..949735f3 --- /dev/null +++ b/src/whop_sdk/resources/referrals/referrals.py @@ -0,0 +1,102 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from ..._compat import cached_property +from ..._resource import SyncAPIResource, AsyncAPIResource +from .businesses.businesses import ( + BusinessesResource, + AsyncBusinessesResource, + BusinessesResourceWithRawResponse, + AsyncBusinessesResourceWithRawResponse, + BusinessesResourceWithStreamingResponse, + AsyncBusinessesResourceWithStreamingResponse, +) + +__all__ = ["ReferralsResource", "AsyncReferralsResource"] + + +class ReferralsResource(SyncAPIResource): + @cached_property + def businesses(self) -> BusinessesResource: + return BusinessesResource(self._client) + + @cached_property + def with_raw_response(self) -> ReferralsResourceWithRawResponse: + """ + 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 ReferralsResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> ReferralsResourceWithStreamingResponse: + """ + 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 ReferralsResourceWithStreamingResponse(self) + + +class AsyncReferralsResource(AsyncAPIResource): + @cached_property + def businesses(self) -> AsyncBusinessesResource: + return AsyncBusinessesResource(self._client) + + @cached_property + def with_raw_response(self) -> AsyncReferralsResourceWithRawResponse: + """ + 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 AsyncReferralsResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncReferralsResourceWithStreamingResponse: + """ + 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 AsyncReferralsResourceWithStreamingResponse(self) + + +class ReferralsResourceWithRawResponse: + def __init__(self, referrals: ReferralsResource) -> None: + self._referrals = referrals + + @cached_property + def businesses(self) -> BusinessesResourceWithRawResponse: + return BusinessesResourceWithRawResponse(self._referrals.businesses) + + +class AsyncReferralsResourceWithRawResponse: + def __init__(self, referrals: AsyncReferralsResource) -> None: + self._referrals = referrals + + @cached_property + def businesses(self) -> AsyncBusinessesResourceWithRawResponse: + return AsyncBusinessesResourceWithRawResponse(self._referrals.businesses) + + +class ReferralsResourceWithStreamingResponse: + def __init__(self, referrals: ReferralsResource) -> None: + self._referrals = referrals + + @cached_property + def businesses(self) -> BusinessesResourceWithStreamingResponse: + return BusinessesResourceWithStreamingResponse(self._referrals.businesses) + + +class AsyncReferralsResourceWithStreamingResponse: + def __init__(self, referrals: AsyncReferralsResource) -> None: + self._referrals = referrals + + @cached_property + def businesses(self) -> AsyncBusinessesResourceWithStreamingResponse: + return AsyncBusinessesResourceWithStreamingResponse(self._referrals.businesses) diff --git a/src/whop_sdk/types/referrals/__init__.py b/src/whop_sdk/types/referrals/__init__.py new file mode 100644 index 00000000..8900c156 --- /dev/null +++ b/src/whop_sdk/types/referrals/__init__.py @@ -0,0 +1,9 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from .business_list_params import BusinessListParams as BusinessListParams +from .business_list_response import BusinessListResponse as BusinessListResponse +from .business_retrieve_response import BusinessRetrieveResponse as BusinessRetrieveResponse +from .business_list_earnings_params import BusinessListEarningsParams as BusinessListEarningsParams +from .business_list_earnings_response import BusinessListEarningsResponse as BusinessListEarningsResponse diff --git a/src/whop_sdk/types/referrals/business_list_earnings_params.py b/src/whop_sdk/types/referrals/business_list_earnings_params.py new file mode 100644 index 00000000..5fc91692 --- /dev/null +++ b/src/whop_sdk/types/referrals/business_list_earnings_params.py @@ -0,0 +1,32 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Literal, TypedDict + +__all__ = ["BusinessListEarningsParams"] + + +class BusinessListEarningsParams(TypedDict, total=False): + after: str + + before: str + + first: int + + include: Literal["receipt_fees"] + """Comma-separated extras to embed. + + Supported: receipt_fees (adds amount_after_fees and the receipt_fees breakdown). + """ + + last: int + + order: Literal["asc", "desc"] + """Sort direction.""" + + sort: Literal["created_at", "amount", "payout_at"] + """Field to sort earnings by.""" + + status: Literal["awaiting_settlement", "pending", "completed", "canceled", "reversed"] + """Filter by earning status.""" diff --git a/src/whop_sdk/types/referrals/business_list_earnings_response.py b/src/whop_sdk/types/referrals/business_list_earnings_response.py new file mode 100644 index 00000000..e76b2bfb --- /dev/null +++ b/src/whop_sdk/types/referrals/business_list_earnings_response.py @@ -0,0 +1,112 @@ +# 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__ = [ + "BusinessListEarningsResponse", + "AccessPass", + "Account", + "Receipt", + "ReceiptAlternativePaymentMethod", + "ReceiptReceiptFee", +] + + +class AccessPass(BaseModel): + id: str + + route: str + + title: str + + +class Account(BaseModel): + id: str + """The referred business (a biz\\__ identifier).""" + + logo_url: Optional[str] = None + + route: str + + title: str + + +class ReceiptAlternativePaymentMethod(BaseModel): + image_url: Optional[str] = None + + name: str + + +class ReceiptReceiptFee(BaseModel): + currency: str + + description: Optional[str] = None + + label: str + + raw_amount: float + + specific_fee_origin: str + + type_of_fee: str + + value: str + + +class Receipt(BaseModel): + id: str + + alternative_payment_method: Optional[ReceiptAlternativePaymentMethod] = None + + brand: Optional[str] = None + + created_at: datetime + + currency: str + + last4: Optional[str] = None + + payment_method_type: Optional[str] = None + + processor: Optional[str] = None + + amount_after_fees: Optional[float] = None + """Only present when include=receipt_fees.""" + + receipt_fees: Optional[List[ReceiptReceiptFee]] = None + """Only present when include=receipt_fees.""" + + +class BusinessListEarningsResponse(BaseModel): + id: Optional[str] = None + + access_pass: Optional[AccessPass] = None + + account: Optional[Account] = None + + amount: Optional[float] = None + """What the referrer earns, in USD. Null until the earning settles.""" + + base_amount: float + """The seller payment the earning was calculated from, in USD.""" + + cancelation_reason: Optional[str] = None + """Why the earning was canceled or reversed, if applicable.""" + + created_at: datetime + + currency: str + + object: Literal["business_referral_earning"] + + payout_at: Optional[datetime] = None + + payout_percentage: Optional[float] = None + + receipt: Optional[Receipt] = None + + status: Literal["awaiting_settlement", "pending", "completed", "canceled", "reversed"] diff --git a/src/whop_sdk/types/referrals/business_list_params.py b/src/whop_sdk/types/referrals/business_list_params.py new file mode 100644 index 00000000..574004a1 --- /dev/null +++ b/src/whop_sdk/types/referrals/business_list_params.py @@ -0,0 +1,30 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Literal, TypedDict + +__all__ = ["BusinessListParams"] + + +class BusinessListParams(TypedDict, total=False): + after: str + """Cursor to fetch the page after (from page_info.end_cursor).""" + + before: str + """Cursor to fetch the page before (from page_info.start_cursor).""" + + first: int + """Number of business referrals to return from the start of the window.""" + + has_earnings: bool + """ + When true, only businesses that have paid out at least one earning to the + caller. + """ + + last: int + """Number of business referrals to return from the end of the window.""" + + status: Literal["active", "removed"] + """Filter by referral status.""" diff --git a/src/whop_sdk/types/referrals/business_list_response.py b/src/whop_sdk/types/referrals/business_list_response.py new file mode 100644 index 00000000..5eeec0b4 --- /dev/null +++ b/src/whop_sdk/types/referrals/business_list_response.py @@ -0,0 +1,55 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from datetime import datetime +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["BusinessListResponse", "Account"] + + +class Account(BaseModel): + id: str + """The referred business (a biz\\__ identifier).""" + + logo_url: Optional[str] = None + + route: str + + title: str + + +class BusinessListResponse(BaseModel): + id: str + + account: Optional[Account] = None + + completed_payout: float + """Earnings already paid out, in USD.""" + + created_at: datetime + + currency: str + + object: Literal["business_referral"] + + payout_percentage: float + + pending_payout: float + """Earnings awaiting payout, in USD.""" + + processing_volume: float + """All-time gross processing volume for the business, in USD.""" + + referral_expires_at: Optional[datetime] = None + + referral_started_at: Optional[datetime] = None + + referred_by_account_id: Optional[str] = None + """The company that made the referral, if a company referred.""" + + status: Literal["active", "removed"] + + total_earnings: float + """All-time affiliate earnings from this business (pending + completed), in USD.""" diff --git a/src/whop_sdk/types/referrals/business_retrieve_response.py b/src/whop_sdk/types/referrals/business_retrieve_response.py new file mode 100644 index 00000000..c6022d40 --- /dev/null +++ b/src/whop_sdk/types/referrals/business_retrieve_response.py @@ -0,0 +1,55 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from datetime import datetime +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["BusinessRetrieveResponse", "Account"] + + +class Account(BaseModel): + id: str + """The referred business (a biz\\__ identifier).""" + + logo_url: Optional[str] = None + + route: str + + title: str + + +class BusinessRetrieveResponse(BaseModel): + id: str + + account: Optional[Account] = None + + completed_payout: float + """Earnings already paid out, in USD.""" + + created_at: datetime + + currency: str + + object: Literal["business_referral"] + + payout_percentage: float + + pending_payout: float + """Earnings awaiting payout, in USD.""" + + processing_volume: float + """All-time gross processing volume for the business, in USD.""" + + referral_expires_at: Optional[datetime] = None + + referral_started_at: Optional[datetime] = None + + referred_by_account_id: Optional[str] = None + """The company that made the referral, if a company referred.""" + + status: Literal["active", "removed"] + + total_earnings: float + """All-time affiliate earnings from this business (pending + completed), in USD.""" diff --git a/src/whop_sdk/types/referrals/businesses/__init__.py b/src/whop_sdk/types/referrals/businesses/__init__.py new file mode 100644 index 00000000..dc2fac39 --- /dev/null +++ b/src/whop_sdk/types/referrals/businesses/__init__.py @@ -0,0 +1,6 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from .earning_list_params import EarningListParams as EarningListParams +from .earning_list_response import EarningListResponse as EarningListResponse diff --git a/src/whop_sdk/types/referrals/businesses/earning_list_params.py b/src/whop_sdk/types/referrals/businesses/earning_list_params.py new file mode 100644 index 00000000..161ef59f --- /dev/null +++ b/src/whop_sdk/types/referrals/businesses/earning_list_params.py @@ -0,0 +1,32 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Literal, TypedDict + +__all__ = ["EarningListParams"] + + +class EarningListParams(TypedDict, total=False): + after: str + + before: str + + first: int + + include: Literal["receipt_fees"] + """Comma-separated extras to embed. + + Supported: receipt_fees (adds amount_after_fees and the receipt_fees breakdown). + """ + + last: int + + order: Literal["asc", "desc"] + """Sort direction.""" + + sort: Literal["created_at", "amount", "payout_at"] + """Field to sort earnings by.""" + + status: Literal["awaiting_settlement", "pending", "completed", "canceled", "reversed"] + """Filter by earning status.""" diff --git a/src/whop_sdk/types/referrals/businesses/earning_list_response.py b/src/whop_sdk/types/referrals/businesses/earning_list_response.py new file mode 100644 index 00000000..ee6cd30b --- /dev/null +++ b/src/whop_sdk/types/referrals/businesses/earning_list_response.py @@ -0,0 +1,112 @@ +# 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__ = [ + "EarningListResponse", + "AccessPass", + "Account", + "Receipt", + "ReceiptAlternativePaymentMethod", + "ReceiptReceiptFee", +] + + +class AccessPass(BaseModel): + id: str + + route: str + + title: str + + +class Account(BaseModel): + id: str + """The referred business (a biz\\__ identifier).""" + + logo_url: Optional[str] = None + + route: str + + title: str + + +class ReceiptAlternativePaymentMethod(BaseModel): + image_url: Optional[str] = None + + name: str + + +class ReceiptReceiptFee(BaseModel): + currency: str + + description: Optional[str] = None + + label: str + + raw_amount: float + + specific_fee_origin: str + + type_of_fee: str + + value: str + + +class Receipt(BaseModel): + id: str + + alternative_payment_method: Optional[ReceiptAlternativePaymentMethod] = None + + brand: Optional[str] = None + + created_at: datetime + + currency: str + + last4: Optional[str] = None + + payment_method_type: Optional[str] = None + + processor: Optional[str] = None + + amount_after_fees: Optional[float] = None + """Only present when include=receipt_fees.""" + + receipt_fees: Optional[List[ReceiptReceiptFee]] = None + """Only present when include=receipt_fees.""" + + +class EarningListResponse(BaseModel): + id: Optional[str] = None + + access_pass: Optional[AccessPass] = None + + account: Optional[Account] = None + + amount: Optional[float] = None + """What the referrer earns, in USD. Null until the earning settles.""" + + base_amount: float + """The seller payment the earning was calculated from, in USD.""" + + cancelation_reason: Optional[str] = None + """Why the earning was canceled or reversed, if applicable.""" + + created_at: datetime + + currency: str + + object: Literal["business_referral_earning"] + + payout_at: Optional[datetime] = None + + payout_percentage: Optional[float] = None + + receipt: Optional[Receipt] = None + + status: Literal["awaiting_settlement", "pending", "completed", "canceled", "reversed"] diff --git a/tests/api_resources/referrals/__init__.py b/tests/api_resources/referrals/__init__.py new file mode 100644 index 00000000..fd8019a9 --- /dev/null +++ b/tests/api_resources/referrals/__init__.py @@ -0,0 +1 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. diff --git a/tests/api_resources/referrals/businesses/__init__.py b/tests/api_resources/referrals/businesses/__init__.py new file mode 100644 index 00000000..fd8019a9 --- /dev/null +++ b/tests/api_resources/referrals/businesses/__init__.py @@ -0,0 +1 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. diff --git a/tests/api_resources/referrals/businesses/test_earnings.py b/tests/api_resources/referrals/businesses/test_earnings.py new file mode 100644 index 00000000..5625938a --- /dev/null +++ b/tests/api_resources/referrals/businesses/test_earnings.py @@ -0,0 +1,141 @@ +# 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.pagination import SyncCursorPage, AsyncCursorPage +from whop_sdk.types.referrals.businesses import EarningListResponse + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestEarnings: + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_list(self, client: Whop) -> None: + earning = client.referrals.businesses.earnings.list( + id="id", + ) + assert_matches_type(SyncCursorPage[EarningListResponse], earning, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_list_with_all_params(self, client: Whop) -> None: + earning = client.referrals.businesses.earnings.list( + id="id", + after="after", + before="before", + first=100, + include="receipt_fees", + last=100, + order="asc", + sort="created_at", + status="awaiting_settlement", + ) + assert_matches_type(SyncCursorPage[EarningListResponse], earning, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_raw_response_list(self, client: Whop) -> None: + response = client.referrals.businesses.earnings.with_raw_response.list( + id="id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + earning = response.parse() + assert_matches_type(SyncCursorPage[EarningListResponse], earning, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_streaming_response_list(self, client: Whop) -> None: + with client.referrals.businesses.earnings.with_streaming_response.list( + id="id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + earning = response.parse() + assert_matches_type(SyncCursorPage[EarningListResponse], earning, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_path_params_list(self, client: Whop) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + client.referrals.businesses.earnings.with_raw_response.list( + id="", + ) + + +class TestAsyncEarnings: + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_list(self, async_client: AsyncWhop) -> None: + earning = await async_client.referrals.businesses.earnings.list( + id="id", + ) + assert_matches_type(AsyncCursorPage[EarningListResponse], earning, 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: + earning = await async_client.referrals.businesses.earnings.list( + id="id", + after="after", + before="before", + first=100, + include="receipt_fees", + last=100, + order="asc", + sort="created_at", + status="awaiting_settlement", + ) + assert_matches_type(AsyncCursorPage[EarningListResponse], earning, 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.referrals.businesses.earnings.with_raw_response.list( + id="id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + earning = await response.parse() + assert_matches_type(AsyncCursorPage[EarningListResponse], earning, 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.referrals.businesses.earnings.with_streaming_response.list( + id="id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + earning = await response.parse() + assert_matches_type(AsyncCursorPage[EarningListResponse], earning, 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_list(self, async_client: AsyncWhop) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + await async_client.referrals.businesses.earnings.with_raw_response.list( + id="", + ) diff --git a/tests/api_resources/referrals/test_businesses.py b/tests/api_resources/referrals/test_businesses.py new file mode 100644 index 00000000..51c774a5 --- /dev/null +++ b/tests/api_resources/referrals/test_businesses.py @@ -0,0 +1,281 @@ +# 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.pagination import SyncCursorPage, AsyncCursorPage +from whop_sdk.types.referrals import ( + BusinessListResponse, + BusinessRetrieveResponse, + BusinessListEarningsResponse, +) + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestBusinesses: + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_retrieve(self, client: Whop) -> None: + business = client.referrals.businesses.retrieve( + "id", + ) + assert_matches_type(BusinessRetrieveResponse, business, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_raw_response_retrieve(self, client: Whop) -> None: + response = client.referrals.businesses.with_raw_response.retrieve( + "id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + business = response.parse() + assert_matches_type(BusinessRetrieveResponse, business, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_streaming_response_retrieve(self, client: Whop) -> None: + with client.referrals.businesses.with_streaming_response.retrieve( + "id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + business = response.parse() + assert_matches_type(BusinessRetrieveResponse, business, 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.referrals.businesses.with_raw_response.retrieve( + "", + ) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_list(self, client: Whop) -> None: + business = client.referrals.businesses.list() + assert_matches_type(SyncCursorPage[BusinessListResponse], business, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_list_with_all_params(self, client: Whop) -> None: + business = client.referrals.businesses.list( + after="after", + before="before", + first=100, + has_earnings=True, + last=100, + status="active", + ) + assert_matches_type(SyncCursorPage[BusinessListResponse], business, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_raw_response_list(self, client: Whop) -> None: + response = client.referrals.businesses.with_raw_response.list() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + business = response.parse() + assert_matches_type(SyncCursorPage[BusinessListResponse], business, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_streaming_response_list(self, client: Whop) -> None: + with client.referrals.businesses.with_streaming_response.list() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + business = response.parse() + assert_matches_type(SyncCursorPage[BusinessListResponse], business, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_list_earnings(self, client: Whop) -> None: + business = client.referrals.businesses.list_earnings() + assert_matches_type(SyncCursorPage[BusinessListEarningsResponse], business, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_list_earnings_with_all_params(self, client: Whop) -> None: + business = client.referrals.businesses.list_earnings( + after="after", + before="before", + first=100, + include="receipt_fees", + last=100, + order="asc", + sort="created_at", + status="awaiting_settlement", + ) + assert_matches_type(SyncCursorPage[BusinessListEarningsResponse], business, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_raw_response_list_earnings(self, client: Whop) -> None: + response = client.referrals.businesses.with_raw_response.list_earnings() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + business = response.parse() + assert_matches_type(SyncCursorPage[BusinessListEarningsResponse], business, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_streaming_response_list_earnings(self, client: Whop) -> None: + with client.referrals.businesses.with_streaming_response.list_earnings() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + business = response.parse() + assert_matches_type(SyncCursorPage[BusinessListEarningsResponse], business, path=["response"]) + + assert cast(Any, response.is_closed) is True + + +class TestAsyncBusinesses: + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_retrieve(self, async_client: AsyncWhop) -> None: + business = await async_client.referrals.businesses.retrieve( + "id", + ) + assert_matches_type(BusinessRetrieveResponse, business, 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.referrals.businesses.with_raw_response.retrieve( + "id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + business = await response.parse() + assert_matches_type(BusinessRetrieveResponse, business, 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.referrals.businesses.with_streaming_response.retrieve( + "id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + business = await response.parse() + assert_matches_type(BusinessRetrieveResponse, business, 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.referrals.businesses.with_raw_response.retrieve( + "", + ) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_list(self, async_client: AsyncWhop) -> None: + business = await async_client.referrals.businesses.list() + assert_matches_type(AsyncCursorPage[BusinessListResponse], business, 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: + business = await async_client.referrals.businesses.list( + after="after", + before="before", + first=100, + has_earnings=True, + last=100, + status="active", + ) + assert_matches_type(AsyncCursorPage[BusinessListResponse], business, 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.referrals.businesses.with_raw_response.list() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + business = await response.parse() + assert_matches_type(AsyncCursorPage[BusinessListResponse], business, 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.referrals.businesses.with_streaming_response.list() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + business = await response.parse() + assert_matches_type(AsyncCursorPage[BusinessListResponse], business, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_list_earnings(self, async_client: AsyncWhop) -> None: + business = await async_client.referrals.businesses.list_earnings() + assert_matches_type(AsyncCursorPage[BusinessListEarningsResponse], business, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_list_earnings_with_all_params(self, async_client: AsyncWhop) -> None: + business = await async_client.referrals.businesses.list_earnings( + after="after", + before="before", + first=100, + include="receipt_fees", + last=100, + order="asc", + sort="created_at", + status="awaiting_settlement", + ) + assert_matches_type(AsyncCursorPage[BusinessListEarningsResponse], business, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_raw_response_list_earnings(self, async_client: AsyncWhop) -> None: + response = await async_client.referrals.businesses.with_raw_response.list_earnings() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + business = await response.parse() + assert_matches_type(AsyncCursorPage[BusinessListEarningsResponse], business, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_streaming_response_list_earnings(self, async_client: AsyncWhop) -> None: + async with async_client.referrals.businesses.with_streaming_response.list_earnings() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + business = await response.parse() + assert_matches_type(AsyncCursorPage[BusinessListEarningsResponse], business, path=["response"]) + + assert cast(Any, response.is_closed) is True From 8997802c23abef547bb3585e49bc9cbccbc3b11f Mon Sep 17 00:00:00 2001 From: stlc-bot Date: Wed, 17 Jun 2026 00:44:46 +0000 Subject: [PATCH 054/109] feat: 2fa enforcement on sensitive endpoints (backend only) Stainless-Generated-From: 1633e25bfa1844f36dd6783490814e1a671ed55b --- src/whop_sdk/resources/authorized_users.py | 8 ++++++ .../types/authorized_user_create_params.py | 27 ++++++++++++++++++- tests/api_resources/test_authorized_users.py | 16 +++++++++++ 3 files changed, 50 insertions(+), 1 deletion(-) diff --git a/src/whop_sdk/resources/authorized_users.py b/src/whop_sdk/resources/authorized_users.py index 0519e3e2..90c75893 100644 --- a/src/whop_sdk/resources/authorized_users.py +++ b/src/whop_sdk/resources/authorized_users.py @@ -56,6 +56,7 @@ def create( company_id: str, role: AuthorizedUserRoles, user_id: str, + elevation: Optional[authorized_user_create_params.Elevation] | Omit = omit, send_emails: Optional[bool] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. @@ -81,6 +82,8 @@ def create( user_id: The ID of the user to add as an authorized user. + elevation: Re-authentication proof required to perform this sensitive action. + send_emails: Whether to send notification emails to the user on creation. extra_headers: Send extra headers @@ -98,6 +101,7 @@ def create( "company_id": company_id, "role": role, "user_id": user_id, + "elevation": elevation, "send_emails": send_emails, }, authorized_user_create_params.AuthorizedUserCreateParams, @@ -304,6 +308,7 @@ async def create( company_id: str, role: AuthorizedUserRoles, user_id: str, + elevation: Optional[authorized_user_create_params.Elevation] | Omit = omit, send_emails: Optional[bool] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. @@ -329,6 +334,8 @@ async def create( user_id: The ID of the user to add as an authorized user. + elevation: Re-authentication proof required to perform this sensitive action. + send_emails: Whether to send notification emails to the user on creation. extra_headers: Send extra headers @@ -346,6 +353,7 @@ async def create( "company_id": company_id, "role": role, "user_id": user_id, + "elevation": elevation, "send_emails": send_emails, }, authorized_user_create_params.AuthorizedUserCreateParams, diff --git a/src/whop_sdk/types/authorized_user_create_params.py b/src/whop_sdk/types/authorized_user_create_params.py index b2b538a5..a412352b 100644 --- a/src/whop_sdk/types/authorized_user_create_params.py +++ b/src/whop_sdk/types/authorized_user_create_params.py @@ -7,7 +7,7 @@ from .shared.authorized_user_roles import AuthorizedUserRoles -__all__ = ["AuthorizedUserCreateParams"] +__all__ = ["AuthorizedUserCreateParams", "Elevation"] class AuthorizedUserCreateParams(TypedDict, total=False): @@ -23,5 +23,30 @@ class AuthorizedUserCreateParams(TypedDict, total=False): user_id: Required[str] """The ID of the user to add as an authorized user.""" + elevation: Optional[Elevation] + """Re-authentication proof required to perform this sensitive action.""" + send_emails: Optional[bool] """Whether to send notification emails to the user on creation.""" + + +class Elevation(TypedDict, total=False): + """Re-authentication proof required to perform this sensitive action.""" + + authenticator_data: Optional[str] + """The WebAuthn authenticator data (base64).""" + + client_data_json: Optional[str] + """The WebAuthn client data JSON (base64).""" + + credential_id: Optional[str] + """The WebAuthn credential ID (base64).""" + + signature: Optional[str] + """The WebAuthn signature (base64).""" + + totp_code: Optional[str] + """The 6-digit code from the authenticator app or SMS.""" + + use_finance_session: Optional[bool] + """Reuse an existing elevated session (for SMS/email 2FA users).""" diff --git a/tests/api_resources/test_authorized_users.py b/tests/api_resources/test_authorized_users.py index 007f3d47..95bb59b6 100644 --- a/tests/api_resources/test_authorized_users.py +++ b/tests/api_resources/test_authorized_users.py @@ -40,6 +40,14 @@ def test_method_create_with_all_params(self, client: Whop) -> None: company_id="biz_xxxxxxxxxxxxxx", role="owner", user_id="user_xxxxxxxxxxxxx", + elevation={ + "authenticator_data": "authenticator_data", + "client_data_json": "client_data_json", + "credential_id": "credential_id", + "signature": "signature", + "totp_code": "totp_code", + "use_finance_session": True, + }, send_emails=True, ) assert_matches_type(AuthorizedUser, authorized_user, path=["response"]) @@ -234,6 +242,14 @@ async def test_method_create_with_all_params(self, async_client: AsyncWhop) -> N company_id="biz_xxxxxxxxxxxxxx", role="owner", user_id="user_xxxxxxxxxxxxx", + elevation={ + "authenticator_data": "authenticator_data", + "client_data_json": "client_data_json", + "credential_id": "credential_id", + "signature": "signature", + "totp_code": "totp_code", + "use_finance_session": True, + }, send_emails=True, ) assert_matches_type(AuthorizedUser, authorized_user, path=["response"]) From 3da2cc9c04ca4c825fa528b47a744d17f96a5e38 Mon Sep 17 00:00:00 2001 From: stlc-bot Date: Wed, 17 Jun 2026 00:53:07 +0000 Subject: [PATCH 055/109] Add three_ds_verified to payment and setup intent APIs Stainless-Generated-From: 826d8fcb98ae75e3ecc1a938cdb91b57e2bbbd88 --- src/whop_sdk/types/setup_intent.py | 6 ++++++ src/whop_sdk/types/shared/payment.py | 3 +++ 2 files changed, 9 insertions(+) diff --git a/src/whop_sdk/types/setup_intent.py b/src/whop_sdk/types/setup_intent.py index a79a2b76..5f3f67b4 100644 --- a/src/whop_sdk/types/setup_intent.py +++ b/src/whop_sdk/types/setup_intent.py @@ -188,3 +188,9 @@ class SetupIntent(BaseModel): status: SetupIntentStatus """The current status of the setup intent.""" + + three_ds_verified: bool + """ + Whether 3D Secure authentication was completed when this payment method was set + up. + """ diff --git a/src/whop_sdk/types/shared/payment.py b/src/whop_sdk/types/shared/payment.py index 94effb39..551e9bf4 100644 --- a/src/whop_sdk/types/shared/payment.py +++ b/src/whop_sdk/types/shared/payment.py @@ -570,6 +570,9 @@ class Payment(BaseModel): tax_refunded_amount: Optional[float] = None """The amount of tax that has been refunded (if applicable).""" + three_ds_verified: bool + """Whether 3D Secure authentication was completed for this payment.""" + total: Optional[float] = None """The total to show to the creator (excluding buyer fees).""" From f51a243d07ed751970621309f465d29962a800f8 Mon Sep 17 00:00:00 2001 From: stlc-bot Date: Wed, 17 Jun 2026 01:00:59 +0000 Subject: [PATCH 056/109] Add POST /api/v1/cards endpoint for card issuance Stainless-Generated-From: c8d1918df30699b3c6d38f735ba7ee84e9f960a1 --- .stats.yml | 2 +- api.md | 3 +- src/whop_sdk/resources/cards.py | 141 ++++++++++++++++++- src/whop_sdk/types/__init__.py | 2 + src/whop_sdk/types/card_create_params.py | 27 ++++ src/whop_sdk/types/card_create_response.py | 96 +++++++++++++ src/whop_sdk/types/card_list_response.py | 4 +- src/whop_sdk/types/card_retrieve_response.py | 4 +- tests/api_resources/test_cards.py | 88 +++++++++++- 9 files changed, 359 insertions(+), 8 deletions(-) create mode 100644 src/whop_sdk/types/card_create_params.py create mode 100644 src/whop_sdk/types/card_create_response.py diff --git a/.stats.yml b/.stats.yml index 04d8692c..1b7187f8 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1 +1 @@ -configured_endpoints: 241 +configured_endpoints: 242 diff --git a/api.md b/api.md index 696963c6..1e2c7f70 100644 --- a/api.md +++ b/api.md @@ -803,11 +803,12 @@ Methods: Types: ```python -from whop_sdk.types import CardRetrieveResponse, CardListResponse +from whop_sdk.types import CardCreateResponse, CardRetrieveResponse, CardListResponse ``` Methods: +- client.cards.create(\*\*params) -> CardCreateResponse - client.cards.retrieve(card_id, \*\*params) -> CardRetrieveResponse - client.cards.list(\*\*params) -> CardListResponse diff --git a/src/whop_sdk/resources/cards.py b/src/whop_sdk/resources/cards.py index 87571eba..1bdb27f9 100644 --- a/src/whop_sdk/resources/cards.py +++ b/src/whop_sdk/resources/cards.py @@ -2,9 +2,11 @@ from __future__ import annotations +from typing_extensions import Literal + import httpx -from ..types import card_list_params, card_retrieve_params +from ..types import card_list_params, card_create_params, card_retrieve_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 @@ -17,6 +19,7 @@ ) from .._base_client import make_request_options from ..types.card_list_response import CardListResponse +from ..types.card_create_response import CardCreateResponse from ..types.card_retrieve_response import CardRetrieveResponse __all__ = ["CardsResource", "AsyncCardsResource"] @@ -42,6 +45,68 @@ def with_streaming_response(self) -> CardsResourceWithStreamingResponse: """ return CardsResourceWithStreamingResponse(self) + def create( + self, + *, + account_id: str | Omit = omit, + name: str | Omit = omit, + spend_limit: float | Omit = omit, + spend_limit_frequency: Literal["daily", "weekly", "monthly", "one_time"] | Omit = omit, + transaction_limit: float | Omit = omit, + user_id: 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, + ) -> CardCreateResponse: + """Issues a virtual card for an individual (consumer) card issuing account. + + The + ledger's owner is passed as exactly one of account*id (a biz* identifier) or + user*id (a user* identifier). Returns the newly created card resource. + + Args: + account_id: The owning account ID (a biz\\__ identifier). Provide this or user_id. + + name: A display name for the card. + + spend_limit: Spending limit amount, in dollars. + + spend_limit_frequency: The spending limit window. + + transaction_limit: Per-transaction limit amount, in dollars. + + user_id: The owning user ID (a user\\__ identifier). Provide this or account_id. + + 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( + "/cards", + body=maybe_transform( + { + "account_id": account_id, + "name": name, + "spend_limit": spend_limit, + "spend_limit_frequency": spend_limit_frequency, + "transaction_limit": transaction_limit, + "user_id": user_id, + }, + card_create_params.CardCreateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=CardCreateResponse, + ) + def retrieve( self, card_id: str, @@ -165,6 +230,68 @@ def with_streaming_response(self) -> AsyncCardsResourceWithStreamingResponse: """ return AsyncCardsResourceWithStreamingResponse(self) + async def create( + self, + *, + account_id: str | Omit = omit, + name: str | Omit = omit, + spend_limit: float | Omit = omit, + spend_limit_frequency: Literal["daily", "weekly", "monthly", "one_time"] | Omit = omit, + transaction_limit: float | Omit = omit, + user_id: 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, + ) -> CardCreateResponse: + """Issues a virtual card for an individual (consumer) card issuing account. + + The + ledger's owner is passed as exactly one of account*id (a biz* identifier) or + user*id (a user* identifier). Returns the newly created card resource. + + Args: + account_id: The owning account ID (a biz\\__ identifier). Provide this or user_id. + + name: A display name for the card. + + spend_limit: Spending limit amount, in dollars. + + spend_limit_frequency: The spending limit window. + + transaction_limit: Per-transaction limit amount, in dollars. + + user_id: The owning user ID (a user\\__ identifier). Provide this or account_id. + + 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( + "/cards", + body=await async_maybe_transform( + { + "account_id": account_id, + "name": name, + "spend_limit": spend_limit, + "spend_limit_frequency": spend_limit_frequency, + "transaction_limit": transaction_limit, + "user_id": user_id, + }, + card_create_params.CardCreateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=CardCreateResponse, + ) + async def retrieve( self, card_id: str, @@ -272,6 +399,9 @@ class CardsResourceWithRawResponse: def __init__(self, cards: CardsResource) -> None: self._cards = cards + self.create = to_raw_response_wrapper( + cards.create, + ) self.retrieve = to_raw_response_wrapper( cards.retrieve, ) @@ -284,6 +414,9 @@ class AsyncCardsResourceWithRawResponse: def __init__(self, cards: AsyncCardsResource) -> None: self._cards = cards + self.create = async_to_raw_response_wrapper( + cards.create, + ) self.retrieve = async_to_raw_response_wrapper( cards.retrieve, ) @@ -296,6 +429,9 @@ class CardsResourceWithStreamingResponse: def __init__(self, cards: CardsResource) -> None: self._cards = cards + self.create = to_streamed_response_wrapper( + cards.create, + ) self.retrieve = to_streamed_response_wrapper( cards.retrieve, ) @@ -308,6 +444,9 @@ class AsyncCardsResourceWithStreamingResponse: def __init__(self, cards: AsyncCardsResource) -> None: self._cards = cards + self.create = async_to_streamed_response_wrapper( + cards.create, + ) self.retrieve = async_to_streamed_response_wrapper( cards.retrieve, ) diff --git a/src/whop_sdk/types/__init__.py b/src/whop_sdk/types/__init__.py index 4b26d76e..f9ee586e 100644 --- a/src/whop_sdk/types/__init__.py +++ b/src/whop_sdk/types/__init__.py @@ -125,6 +125,7 @@ from .ad_campaign_status import AdCampaignStatus as AdCampaignStatus from .ad_retrieve_params import AdRetrieveParams as AdRetrieveParams from .bounty_list_params import BountyListParams as BountyListParams +from .card_create_params import CardCreateParams as CardCreateParams from .card_list_response import CardListResponse as CardListResponse from .course_list_params import CourseListParams as CourseListParams from .dispute_alert_type import DisputeAlertType as DisputeAlertType @@ -167,6 +168,7 @@ from .ad_group_list_params import AdGroupListParams as AdGroupListParams from .bounty_create_params import BountyCreateParams as BountyCreateParams from .bounty_list_response import BountyListResponse as BountyListResponse +from .card_create_response import CardCreateResponse as CardCreateResponse from .card_retrieve_params import CardRetrieveParams as CardRetrieveParams from .course_create_params import CourseCreateParams as CourseCreateParams from .course_list_response import CourseListResponse as CourseListResponse diff --git a/src/whop_sdk/types/card_create_params.py b/src/whop_sdk/types/card_create_params.py new file mode 100644 index 00000000..8f91ffac --- /dev/null +++ b/src/whop_sdk/types/card_create_params.py @@ -0,0 +1,27 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Literal, TypedDict + +__all__ = ["CardCreateParams"] + + +class CardCreateParams(TypedDict, total=False): + account_id: str + """The owning account ID (a biz\\__ identifier). Provide this or user_id.""" + + name: str + """A display name for the card.""" + + spend_limit: float + """Spending limit amount, in dollars.""" + + spend_limit_frequency: Literal["daily", "weekly", "monthly", "one_time"] + """The spending limit window.""" + + transaction_limit: float + """Per-transaction limit amount, in dollars.""" + + user_id: str + """The owning user ID (a user\\__ identifier). Provide this or account_id.""" diff --git a/src/whop_sdk/types/card_create_response.py b/src/whop_sdk/types/card_create_response.py new file mode 100644 index 00000000..e510651a --- /dev/null +++ b/src/whop_sdk/types/card_create_response.py @@ -0,0 +1,96 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from datetime import datetime +from typing_extensions import Literal + +from .._models import BaseModel + +__all__ = ["CardCreateResponse", "Billing", "Limit", "Secrets"] + + +class Billing(BaseModel): + """The billing address.""" + + city: Optional[str] = None + + country_code: Optional[str] = None + + line1: Optional[str] = None + + line2: Optional[str] = None + + postal_code: Optional[str] = None + + region: Optional[str] = None + + +class Limit(BaseModel): + """The spending limit configuration.""" + + amount: int + """The limit amount in cents.""" + + frequency: str + """The limit window, for example per24HourPeriod or perAuthorization.""" + + +class Secrets(BaseModel): + """The card's sensitive details. + + Only present on GET /cards/:card_id (retrieve); null for cards that are not active or whose details could not be retrieved. + """ + + card_number: str + """The full card number.""" + + cvc: str + """The card verification code.""" + + name_on_card: Optional[str] = None + """The cardholder name printed on the card.""" + + +class CardCreateResponse(BaseModel): + id: str + """The icrd\\__ identifier of the card.""" + + billing: Optional[Billing] = None + """The billing address.""" + + canceled_at: Optional[datetime] = None + + created_at: Optional[datetime] = None + + expiration_month: Optional[str] = None + + expiration_year: Optional[str] = None + + last4: Optional[str] = None + """The last 4 digits of the card number. Null for pending invitation cards.""" + + limit: Optional[Limit] = None + """The spending limit configuration.""" + + name: Optional[str] = None + + object: Literal["card"] + + spent_last_month: Optional[int] = None + """Total spend in the last 30 days, in cents.""" + + status: Optional[Literal["active", "frozen", "canceled", "invited"]] = None + """The card status.""" + + type: Optional[Literal["virtual", "physical"]] = None + """The card type.""" + + user_id: Optional[str] = None + """The user\\__ identifier of the cardholder, when assigned.""" + + secrets: Optional[Secrets] = None + """The card's sensitive details. + + Only present on GET /cards/:card_id (retrieve); null for cards that are not + active or whose details could not be retrieved. + """ diff --git a/src/whop_sdk/types/card_list_response.py b/src/whop_sdk/types/card_list_response.py index 2aa57689..cc0b1a77 100644 --- a/src/whop_sdk/types/card_list_response.py +++ b/src/whop_sdk/types/card_list_response.py @@ -79,8 +79,8 @@ class Data(BaseModel): spent_last_month: Optional[int] = None """Total spend in the last 30 days, in cents.""" - status: Optional[str] = None - """The card status, for example active, frozen, canceled, or invited.""" + status: Optional[Literal["active", "frozen", "canceled", "invited"]] = None + """The card status.""" type: Optional[Literal["virtual", "physical"]] = None """The card type.""" diff --git a/src/whop_sdk/types/card_retrieve_response.py b/src/whop_sdk/types/card_retrieve_response.py index 35d5afad..5da8c713 100644 --- a/src/whop_sdk/types/card_retrieve_response.py +++ b/src/whop_sdk/types/card_retrieve_response.py @@ -79,8 +79,8 @@ class CardRetrieveResponse(BaseModel): spent_last_month: Optional[int] = None """Total spend in the last 30 days, in cents.""" - status: Optional[str] = None - """The card status, for example active, frozen, canceled, or invited.""" + status: Optional[Literal["active", "frozen", "canceled", "invited"]] = None + """The card status.""" type: Optional[Literal["virtual", "physical"]] = None """The card type.""" diff --git a/tests/api_resources/test_cards.py b/tests/api_resources/test_cards.py index f40015c2..f56dc4c6 100644 --- a/tests/api_resources/test_cards.py +++ b/tests/api_resources/test_cards.py @@ -9,7 +9,11 @@ from whop_sdk import Whop, AsyncWhop from tests.utils import assert_matches_type -from whop_sdk.types import CardListResponse, CardRetrieveResponse +from whop_sdk.types import ( + CardListResponse, + CardCreateResponse, + CardRetrieveResponse, +) base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") @@ -17,6 +21,47 @@ class TestCards: 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: + card = client.cards.create() + assert_matches_type(CardCreateResponse, card, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_create_with_all_params(self, client: Whop) -> None: + card = client.cards.create( + account_id="account_id", + name="name", + spend_limit=0, + spend_limit_frequency="daily", + transaction_limit=0, + user_id="user_id", + ) + assert_matches_type(CardCreateResponse, card, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_raw_response_create(self, client: Whop) -> None: + response = client.cards.with_raw_response.create() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + card = response.parse() + assert_matches_type(CardCreateResponse, card, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_streaming_response_create(self, client: Whop) -> None: + with client.cards.with_streaming_response.create() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + card = response.parse() + assert_matches_type(CardCreateResponse, card, 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: @@ -112,6 +157,47 @@ class TestAsyncCards: "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: + card = await async_client.cards.create() + assert_matches_type(CardCreateResponse, card, 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: + card = await async_client.cards.create( + account_id="account_id", + name="name", + spend_limit=0, + spend_limit_frequency="daily", + transaction_limit=0, + user_id="user_id", + ) + assert_matches_type(CardCreateResponse, card, 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.cards.with_raw_response.create() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + card = await response.parse() + assert_matches_type(CardCreateResponse, card, 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.cards.with_streaming_response.create() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + card = await response.parse() + assert_matches_type(CardCreateResponse, card, 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: From 1fe66eabf622b848d7a6226ef7405dc9baf94aa4 Mon Sep 17 00:00:00 2001 From: stlc-bot Date: Wed, 17 Jun 2026 01:21:37 +0000 Subject: [PATCH 057/109] chore(backend): pagination consolidation Stainless-Generated-From: 7c1bb5dd23ad09829958c5189e9d1b48f6870c14 --- api.md | 4 +- src/whop_sdk/resources/accounts.py | 64 +++++++++++++-------- src/whop_sdk/types/__init__.py | 1 - src/whop_sdk/types/account_list_params.py | 15 +++-- src/whop_sdk/types/account_list_response.py | 31 ---------- tests/api_resources/test_accounts.py | 34 +++++------ 6 files changed, 69 insertions(+), 80 deletions(-) delete mode 100644 src/whop_sdk/types/account_list_response.py diff --git a/api.md b/api.md index 1e2c7f70..25afdf76 100644 --- a/api.md +++ b/api.md @@ -719,7 +719,7 @@ Methods: Types: ```python -from whop_sdk.types import Account, AccountSocialLink, AccountListResponse +from whop_sdk.types import Account, AccountSocialLink ``` Methods: @@ -727,7 +727,7 @@ Methods: - client.accounts.create(\*\*params) -> Account - client.accounts.retrieve(account_id) -> Account - client.accounts.update(account_id, \*\*params) -> Account -- client.accounts.list(\*\*params) -> AccountListResponse +- client.accounts.list(\*\*params) -> SyncCursorPage[Account] - client.accounts.me() -> Account # Wallets diff --git a/src/whop_sdk/resources/accounts.py b/src/whop_sdk/resources/accounts.py index 222afcb7..4f8189cb 100644 --- a/src/whop_sdk/resources/accounts.py +++ b/src/whop_sdk/resources/accounts.py @@ -17,9 +17,9 @@ async_to_raw_response_wrapper, async_to_streamed_response_wrapper, ) -from .._base_client import make_request_options +from ..pagination import SyncCursorPage, AsyncCursorPage +from .._base_client import AsyncPaginator, make_request_options from ..types.account import Account -from ..types.account_list_response import AccountListResponse __all__ = ["AccountsResource", "AsyncAccountsResource"] @@ -285,15 +285,17 @@ def update( def list( self, *, - page: int | Omit = omit, - per: int | Omit = omit, + after: str | Omit = omit, + before: str | Omit = omit, + first: int | Omit = omit, + last: int | 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, - ) -> AccountListResponse: + ) -> SyncCursorPage[Account]: """Lists accounts visible to the credential. User tokens return the user's business @@ -301,10 +303,13 @@ def list( its connected accounts. Args: - page: The page number to retrieve + after: A cursor; returns accounts after this position. - per: The number of resources to return per page. There is a limit of 50 results per - page. + before: A cursor; returns accounts before this position. + + first: The number of accounts to return (default 10, max 50). + + last: The number of accounts to return from the end of the range. extra_headers: Send extra headers @@ -314,8 +319,9 @@ def list( timeout: Override the client-level default timeout for this request, in seconds """ - return self._get( + return self._get_api_list( "/accounts", + page=SyncCursorPage[Account], options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, @@ -323,13 +329,15 @@ def list( timeout=timeout, query=maybe_transform( { - "page": page, - "per": per, + "after": after, + "before": before, + "first": first, + "last": last, }, account_list_params.AccountListParams, ), ), - cast_to=AccountListResponse, + model=Account, ) def me( @@ -613,18 +621,20 @@ async def update( cast_to=Account, ) - async def list( + def list( self, *, - page: int | Omit = omit, - per: int | Omit = omit, + after: str | Omit = omit, + before: str | Omit = omit, + first: int | Omit = omit, + last: int | 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, - ) -> AccountListResponse: + ) -> AsyncPaginator[Account, AsyncCursorPage[Account]]: """Lists accounts visible to the credential. User tokens return the user's business @@ -632,10 +642,13 @@ async def list( its connected accounts. Args: - page: The page number to retrieve + after: A cursor; returns accounts after this position. - per: The number of resources to return per page. There is a limit of 50 results per - page. + before: A cursor; returns accounts before this position. + + first: The number of accounts to return (default 10, max 50). + + last: The number of accounts to return from the end of the range. extra_headers: Send extra headers @@ -645,22 +658,25 @@ async def list( timeout: Override the client-level default timeout for this request, in seconds """ - return await self._get( + return self._get_api_list( "/accounts", + page=AsyncCursorPage[Account], options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout, - query=await async_maybe_transform( + query=maybe_transform( { - "page": page, - "per": per, + "after": after, + "before": before, + "first": first, + "last": last, }, account_list_params.AccountListParams, ), ), - cast_to=AccountListResponse, + model=Account, ) async def me( diff --git a/src/whop_sdk/types/__init__.py b/src/whop_sdk/types/__init__.py index f9ee586e..a5ce1fe1 100644 --- a/src/whop_sdk/types/__init__.py +++ b/src/whop_sdk/types/__init__.py @@ -191,7 +191,6 @@ from .wallet_list_response import WalletListResponse as WalletListResponse from .withdrawal_fee_types import WithdrawalFeeTypes as WithdrawalFeeTypes from .account_create_params import AccountCreateParams as AccountCreateParams -from .account_list_response import AccountListResponse as AccountListResponse from .account_update_params import AccountUpdateParams as AccountUpdateParams from .affiliate_list_params import AffiliateListParams as AffiliateListParams from .ai_chat_create_params import AIChatCreateParams as AIChatCreateParams diff --git a/src/whop_sdk/types/account_list_params.py b/src/whop_sdk/types/account_list_params.py index 3462edca..92d86bd4 100644 --- a/src/whop_sdk/types/account_list_params.py +++ b/src/whop_sdk/types/account_list_params.py @@ -8,11 +8,14 @@ class AccountListParams(TypedDict, total=False): - page: int - """The page number to retrieve""" + after: str + """A cursor; returns accounts after this position.""" - per: int - """The number of resources to return per page. + before: str + """A cursor; returns accounts before this position.""" - There is a limit of 50 results per page. - """ + first: int + """The number of accounts to return (default 10, max 50).""" + + last: int + """The number of accounts to return from the end of the range.""" diff --git a/src/whop_sdk/types/account_list_response.py b/src/whop_sdk/types/account_list_response.py deleted file mode 100644 index d4250efb..00000000 --- a/src/whop_sdk/types/account_list_response.py +++ /dev/null @@ -1,31 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from typing import List, Optional - -from .account import Account -from .._models import BaseModel - -__all__ = ["AccountListResponse", "Pagination"] - - -class Pagination(BaseModel): - current_page: float - """Current page number""" - - next_page: Optional[float] = None - """Next page number""" - - prev_page: Optional[float] = None - """Previous page number""" - - total_count: float - """Total number of records""" - - total_pages: float - """Total number of pages""" - - -class AccountListResponse(BaseModel): - accounts: List[Account] - - pagination: Pagination diff --git a/tests/api_resources/test_accounts.py b/tests/api_resources/test_accounts.py index 74855645..d57ce2c0 100644 --- a/tests/api_resources/test_accounts.py +++ b/tests/api_resources/test_accounts.py @@ -9,10 +9,8 @@ from whop_sdk import Whop, AsyncWhop from tests.utils import assert_matches_type -from whop_sdk.types import ( - Account, - AccountListResponse, -) +from whop_sdk.types import Account +from whop_sdk.pagination import SyncCursorPage, AsyncCursorPage base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") @@ -182,16 +180,18 @@ def test_path_params_update(self, client: Whop) -> None: @parametrize def test_method_list(self, client: Whop) -> None: account = client.accounts.list() - assert_matches_type(AccountListResponse, account, path=["response"]) + assert_matches_type(SyncCursorPage[Account], account, path=["response"]) @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_method_list_with_all_params(self, client: Whop) -> None: account = client.accounts.list( - page=0, - per=0, + after="after", + before="before", + first=0, + last=0, ) - assert_matches_type(AccountListResponse, account, path=["response"]) + assert_matches_type(SyncCursorPage[Account], account, path=["response"]) @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize @@ -201,7 +201,7 @@ def test_raw_response_list(self, client: Whop) -> None: assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" account = response.parse() - assert_matches_type(AccountListResponse, account, path=["response"]) + assert_matches_type(SyncCursorPage[Account], account, path=["response"]) @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize @@ -211,7 +211,7 @@ def test_streaming_response_list(self, client: Whop) -> None: assert response.http_request.headers.get("X-Stainless-Lang") == "python" account = response.parse() - assert_matches_type(AccountListResponse, account, path=["response"]) + assert_matches_type(SyncCursorPage[Account], account, path=["response"]) assert cast(Any, response.is_closed) is True @@ -411,16 +411,18 @@ async def test_path_params_update(self, async_client: AsyncWhop) -> None: @parametrize async def test_method_list(self, async_client: AsyncWhop) -> None: account = await async_client.accounts.list() - assert_matches_type(AccountListResponse, account, path=["response"]) + assert_matches_type(AsyncCursorPage[Account], account, 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: account = await async_client.accounts.list( - page=0, - per=0, + after="after", + before="before", + first=0, + last=0, ) - assert_matches_type(AccountListResponse, account, path=["response"]) + assert_matches_type(AsyncCursorPage[Account], account, path=["response"]) @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize @@ -430,7 +432,7 @@ async def test_raw_response_list(self, async_client: AsyncWhop) -> None: assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" account = await response.parse() - assert_matches_type(AccountListResponse, account, path=["response"]) + assert_matches_type(AsyncCursorPage[Account], account, path=["response"]) @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize @@ -440,7 +442,7 @@ async def test_streaming_response_list(self, async_client: AsyncWhop) -> None: assert response.http_request.headers.get("X-Stainless-Lang") == "python" account = await response.parse() - assert_matches_type(AccountListResponse, account, path=["response"]) + assert_matches_type(AsyncCursorPage[Account], account, path=["response"]) assert cast(Any, response.is_closed) is True From d50461055f58a66dcf6cb7a3802947642772276c Mon Sep 17 00:00:00 2001 From: stlc-bot Date: Wed, 17 Jun 2026 03:14:43 +0000 Subject: [PATCH 058/109] Remove the unused wallets REST API Stainless-Generated-From: 653b70180a99fe215d17f40585188cfed79230ef --- .stats.yml | 2 +- api.md | 12 -- src/whop_sdk/_client.py | 38 ------ src/whop_sdk/resources/__init__.py | 14 --- src/whop_sdk/resources/wallets.py | 135 --------------------- src/whop_sdk/types/__init__.py | 2 - src/whop_sdk/types/account.py | 19 ++- src/whop_sdk/types/account_wallet.py | 18 --- src/whop_sdk/types/wallet_list_response.py | 19 --- tests/api_resources/test_wallets.py | 80 ------------ 10 files changed, 17 insertions(+), 322 deletions(-) delete mode 100644 src/whop_sdk/resources/wallets.py delete mode 100644 src/whop_sdk/types/account_wallet.py delete mode 100644 src/whop_sdk/types/wallet_list_response.py delete mode 100644 tests/api_resources/test_wallets.py diff --git a/.stats.yml b/.stats.yml index 1b7187f8..04d8692c 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1 +1 @@ -configured_endpoints: 242 +configured_endpoints: 241 diff --git a/api.md b/api.md index 25afdf76..deb1372f 100644 --- a/api.md +++ b/api.md @@ -730,18 +730,6 @@ Methods: - client.accounts.list(\*\*params) -> SyncCursorPage[Account] - client.accounts.me() -> Account -# Wallets - -Types: - -```python -from whop_sdk.types import AccountWallet, WalletListResponse -``` - -Methods: - -- client.wallets.list() -> WalletListResponse - # FinancialActivity Types: diff --git a/src/whop_sdk/_client.py b/src/whop_sdk/_client.py index 14ab4263..074aecad 100644 --- a/src/whop_sdk/_client.py +++ b/src/whop_sdk/_client.py @@ -52,7 +52,6 @@ payouts, refunds, reviews, - wallets, accounts, ai_chats, bounties, @@ -120,7 +119,6 @@ from .resources.payouts import PayoutsResource, AsyncPayoutsResource from .resources.refunds import RefundsResource, AsyncRefundsResource from .resources.reviews import ReviewsResource, AsyncReviewsResource - from .resources.wallets import WalletsResource, AsyncWalletsResource from .resources.accounts import AccountsResource, AsyncAccountsResource from .resources.ai_chats import AIChatsResource, AsyncAIChatsResource from .resources.bounties import BountiesResource, AsyncBountiesResource @@ -529,12 +527,6 @@ def accounts(self) -> AccountsResource: return AccountsResource(self) - @cached_property - def wallets(self) -> WalletsResource: - from .resources.wallets import WalletsResource - - return WalletsResource(self) - @cached_property def financial_activity(self) -> FinancialActivityResource: from .resources.financial_activity import FinancialActivityResource @@ -1194,12 +1186,6 @@ def accounts(self) -> AsyncAccountsResource: return AsyncAccountsResource(self) - @cached_property - def wallets(self) -> AsyncWalletsResource: - from .resources.wallets import AsyncWalletsResource - - return AsyncWalletsResource(self) - @cached_property def financial_activity(self) -> AsyncFinancialActivityResource: from .resources.financial_activity import AsyncFinancialActivityResource @@ -1779,12 +1765,6 @@ def accounts(self) -> accounts.AccountsResourceWithRawResponse: return AccountsResourceWithRawResponse(self._client.accounts) - @cached_property - def wallets(self) -> wallets.WalletsResourceWithRawResponse: - from .resources.wallets import WalletsResourceWithRawResponse - - return WalletsResourceWithRawResponse(self._client.wallets) - @cached_property def financial_activity(self) -> financial_activity.FinancialActivityResourceWithRawResponse: from .resources.financial_activity import FinancialActivityResourceWithRawResponse @@ -2246,12 +2226,6 @@ def accounts(self) -> accounts.AsyncAccountsResourceWithRawResponse: return AsyncAccountsResourceWithRawResponse(self._client.accounts) - @cached_property - def wallets(self) -> wallets.AsyncWalletsResourceWithRawResponse: - from .resources.wallets import AsyncWalletsResourceWithRawResponse - - return AsyncWalletsResourceWithRawResponse(self._client.wallets) - @cached_property def financial_activity(self) -> financial_activity.AsyncFinancialActivityResourceWithRawResponse: from .resources.financial_activity import AsyncFinancialActivityResourceWithRawResponse @@ -2715,12 +2689,6 @@ def accounts(self) -> accounts.AccountsResourceWithStreamingResponse: return AccountsResourceWithStreamingResponse(self._client.accounts) - @cached_property - def wallets(self) -> wallets.WalletsResourceWithStreamingResponse: - from .resources.wallets import WalletsResourceWithStreamingResponse - - return WalletsResourceWithStreamingResponse(self._client.wallets) - @cached_property def financial_activity(self) -> financial_activity.FinancialActivityResourceWithStreamingResponse: from .resources.financial_activity import FinancialActivityResourceWithStreamingResponse @@ -3186,12 +3154,6 @@ def accounts(self) -> accounts.AsyncAccountsResourceWithStreamingResponse: return AsyncAccountsResourceWithStreamingResponse(self._client.accounts) - @cached_property - def wallets(self) -> wallets.AsyncWalletsResourceWithStreamingResponse: - from .resources.wallets import AsyncWalletsResourceWithStreamingResponse - - return AsyncWalletsResourceWithStreamingResponse(self._client.wallets) - @cached_property def financial_activity(self) -> financial_activity.AsyncFinancialActivityResourceWithStreamingResponse: from .resources.financial_activity import AsyncFinancialActivityResourceWithStreamingResponse diff --git a/src/whop_sdk/resources/__init__.py b/src/whop_sdk/resources/__init__.py index cbc4c462..492cd7c6 100644 --- a/src/whop_sdk/resources/__init__.py +++ b/src/whop_sdk/resources/__init__.py @@ -128,14 +128,6 @@ ReviewsResourceWithStreamingResponse, AsyncReviewsResourceWithStreamingResponse, ) -from .wallets import ( - WalletsResource, - AsyncWalletsResource, - WalletsResourceWithRawResponse, - AsyncWalletsResourceWithRawResponse, - WalletsResourceWithStreamingResponse, - AsyncWalletsResourceWithStreamingResponse, -) from .accounts import ( AccountsResource, AsyncAccountsResource, @@ -766,12 +758,6 @@ "AsyncAccountsResourceWithRawResponse", "AccountsResourceWithStreamingResponse", "AsyncAccountsResourceWithStreamingResponse", - "WalletsResource", - "AsyncWalletsResource", - "WalletsResourceWithRawResponse", - "AsyncWalletsResourceWithRawResponse", - "WalletsResourceWithStreamingResponse", - "AsyncWalletsResourceWithStreamingResponse", "FinancialActivityResource", "AsyncFinancialActivityResource", "FinancialActivityResourceWithRawResponse", diff --git a/src/whop_sdk/resources/wallets.py b/src/whop_sdk/resources/wallets.py deleted file mode 100644 index d5ef6406..00000000 --- a/src/whop_sdk/resources/wallets.py +++ /dev/null @@ -1,135 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from __future__ import annotations - -import httpx - -from .._types import Body, Query, Headers, NotGiven, not_given -from .._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.wallet_list_response import WalletListResponse - -__all__ = ["WalletsResource", "AsyncWalletsResource"] - - -class WalletsResource(SyncAPIResource): - @cached_property - def with_raw_response(self) -> WalletsResourceWithRawResponse: - """ - 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 WalletsResourceWithRawResponse(self) - - @cached_property - def with_streaming_response(self) -> WalletsResourceWithStreamingResponse: - """ - 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 WalletsResourceWithStreamingResponse(self) - - def list( - self, - *, - # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. - # The extra values given here take precedence over values defined on the client or passed to this method. - extra_headers: Headers | None = None, - extra_query: Query | None = None, - extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> WalletListResponse: - """Lists every crypto wallet linked to the authenticated resource.""" - return self._get( - "/wallets", - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout - ), - cast_to=WalletListResponse, - ) - - -class AsyncWalletsResource(AsyncAPIResource): - @cached_property - def with_raw_response(self) -> AsyncWalletsResourceWithRawResponse: - """ - 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 AsyncWalletsResourceWithRawResponse(self) - - @cached_property - def with_streaming_response(self) -> AsyncWalletsResourceWithStreamingResponse: - """ - 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 AsyncWalletsResourceWithStreamingResponse(self) - - async def list( - self, - *, - # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. - # The extra values given here take precedence over values defined on the client or passed to this method. - extra_headers: Headers | None = None, - extra_query: Query | None = None, - extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> WalletListResponse: - """Lists every crypto wallet linked to the authenticated resource.""" - return await self._get( - "/wallets", - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout - ), - cast_to=WalletListResponse, - ) - - -class WalletsResourceWithRawResponse: - def __init__(self, wallets: WalletsResource) -> None: - self._wallets = wallets - - self.list = to_raw_response_wrapper( - wallets.list, - ) - - -class AsyncWalletsResourceWithRawResponse: - def __init__(self, wallets: AsyncWalletsResource) -> None: - self._wallets = wallets - - self.list = async_to_raw_response_wrapper( - wallets.list, - ) - - -class WalletsResourceWithStreamingResponse: - def __init__(self, wallets: WalletsResource) -> None: - self._wallets = wallets - - self.list = to_streamed_response_wrapper( - wallets.list, - ) - - -class AsyncWalletsResourceWithStreamingResponse: - def __init__(self, wallets: AsyncWalletsResource) -> None: - self._wallets = wallets - - self.list = async_to_streamed_response_wrapper( - wallets.list, - ) diff --git a/src/whop_sdk/types/__init__.py b/src/whop_sdk/types/__init__.py index a5ce1fe1..ce21843b 100644 --- a/src/whop_sdk/types/__init__.py +++ b/src/whop_sdk/types/__init__.py @@ -91,7 +91,6 @@ from .review_status import ReviewStatus as ReviewStatus from .upload_status import UploadStatus as UploadStatus from .webhook_event import WebhookEvent as WebhookEvent -from .account_wallet import AccountWallet as AccountWallet from .ad_budget_type import AdBudgetType as AdBudgetType from .ad_list_params import AdListParams as AdListParams from .cancel_options import CancelOptions as CancelOptions @@ -188,7 +187,6 @@ from .transfer_list_params import TransferListParams as TransferListParams from .unwrap_webhook_event import UnwrapWebhookEvent as UnwrapWebhookEvent from .user_retrieve_params import UserRetrieveParams as UserRetrieveParams -from .wallet_list_response import WalletListResponse as WalletListResponse from .withdrawal_fee_types import WithdrawalFeeTypes as WithdrawalFeeTypes from .account_create_params import AccountCreateParams as AccountCreateParams from .account_update_params import AccountUpdateParams as AccountUpdateParams diff --git a/src/whop_sdk/types/account.py b/src/whop_sdk/types/account.py index e751eec5..8d502921 100644 --- a/src/whop_sdk/types/account.py +++ b/src/whop_sdk/types/account.py @@ -1,12 +1,12 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from typing import List, Optional +from typing_extensions import Literal from .._models import BaseModel -from .account_wallet import AccountWallet from .account_social_link import AccountSocialLink -__all__ = ["Account", "Balance"] +__all__ = ["Account", "Balance", "Wallet"] class Balance(BaseModel): @@ -41,6 +41,19 @@ class Balance(BaseModel): """The total USD value of the holding, or null when no exchange rate is available""" +class Wallet(BaseModel): + """The account's primary crypto wallet, or null if none has been provisioned""" + + id: str + """The ID of the wallet, which will look like wallet\\__******\\********""" + + address: str + """The on-chain address of the wallet""" + + network: Literal["solana", "ethereum", "bitcoin"] + """The blockchain network the wallet lives on""" + + class Account(BaseModel): id: str """The ID of the account, which will look like biz\\__******\\********""" @@ -151,5 +164,5 @@ class Account(BaseModel): approved, rejected """ - wallet: Optional[AccountWallet] = None + wallet: Optional[Wallet] = None """The account's primary crypto wallet, or null if none has been provisioned""" diff --git a/src/whop_sdk/types/account_wallet.py b/src/whop_sdk/types/account_wallet.py deleted file mode 100644 index 0f32e70d..00000000 --- a/src/whop_sdk/types/account_wallet.py +++ /dev/null @@ -1,18 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from typing_extensions import Literal - -from .._models import BaseModel - -__all__ = ["AccountWallet"] - - -class AccountWallet(BaseModel): - id: str - """The ID of the wallet, which will look like wallet\\__******\\********""" - - address: str - """The on-chain address of the wallet""" - - network: Literal["solana", "ethereum", "bitcoin"] - """The blockchain network the wallet lives on""" diff --git a/src/whop_sdk/types/wallet_list_response.py b/src/whop_sdk/types/wallet_list_response.py deleted file mode 100644 index 2527d869..00000000 --- a/src/whop_sdk/types/wallet_list_response.py +++ /dev/null @@ -1,19 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from typing import List - -from .._models import BaseModel - -__all__ = ["WalletListResponse", "Wallet"] - - -class Wallet(BaseModel): - address: str - - balance_usd: str - - network: str - - -class WalletListResponse(BaseModel): - wallets: List[Wallet] diff --git a/tests/api_resources/test_wallets.py b/tests/api_resources/test_wallets.py deleted file mode 100644 index 7643a6e4..00000000 --- a/tests/api_resources/test_wallets.py +++ /dev/null @@ -1,80 +0,0 @@ -# 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 WalletListResponse - -base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") - - -class TestWallets: - parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - def test_method_list(self, client: Whop) -> None: - wallet = client.wallets.list() - assert_matches_type(WalletListResponse, wallet, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - def test_raw_response_list(self, client: Whop) -> None: - response = client.wallets.with_raw_response.list() - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - wallet = response.parse() - assert_matches_type(WalletListResponse, wallet, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - def test_streaming_response_list(self, client: Whop) -> None: - with client.wallets.with_streaming_response.list() as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - wallet = response.parse() - assert_matches_type(WalletListResponse, wallet, path=["response"]) - - assert cast(Any, response.is_closed) is True - - -class TestAsyncWallets: - parametrize = pytest.mark.parametrize( - "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] - ) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - async def test_method_list(self, async_client: AsyncWhop) -> None: - wallet = await async_client.wallets.list() - assert_matches_type(WalletListResponse, wallet, 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.wallets.with_raw_response.list() - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - wallet = await response.parse() - assert_matches_type(WalletListResponse, wallet, 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.wallets.with_streaming_response.list() as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - wallet = await response.parse() - assert_matches_type(WalletListResponse, wallet, path=["response"]) - - assert cast(Any, response.is_closed) is True From 68c1d3e962d71f2ad354678b4621a353da8c20a8 Mon Sep 17 00:00:00 2001 From: stlc-bot Date: Wed, 17 Jun 2026 07:49:15 +0000 Subject: [PATCH 059/109] Fix cards REST API returning spend limit multiplied by 100 Stainless-Generated-From: 4713bca24c0c73ff84a564cfeed1ca83a517f4ee --- src/whop_sdk/types/card_create_response.py | 4 ++-- src/whop_sdk/types/card_list_response.py | 4 ++-- src/whop_sdk/types/card_retrieve_response.py | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/whop_sdk/types/card_create_response.py b/src/whop_sdk/types/card_create_response.py index e510651a..fe5e37dd 100644 --- a/src/whop_sdk/types/card_create_response.py +++ b/src/whop_sdk/types/card_create_response.py @@ -28,8 +28,8 @@ class Billing(BaseModel): class Limit(BaseModel): """The spending limit configuration.""" - amount: int - """The limit amount in cents.""" + amount: float + """The limit amount in dollars.""" frequency: str """The limit window, for example per24HourPeriod or perAuthorization.""" diff --git a/src/whop_sdk/types/card_list_response.py b/src/whop_sdk/types/card_list_response.py index cc0b1a77..7393af4e 100644 --- a/src/whop_sdk/types/card_list_response.py +++ b/src/whop_sdk/types/card_list_response.py @@ -28,8 +28,8 @@ class DataBilling(BaseModel): class DataLimit(BaseModel): """The spending limit configuration.""" - amount: int - """The limit amount in cents.""" + amount: float + """The limit amount in dollars.""" frequency: str """The limit window, for example per24HourPeriod or perAuthorization.""" diff --git a/src/whop_sdk/types/card_retrieve_response.py b/src/whop_sdk/types/card_retrieve_response.py index 5da8c713..937e2aa0 100644 --- a/src/whop_sdk/types/card_retrieve_response.py +++ b/src/whop_sdk/types/card_retrieve_response.py @@ -28,8 +28,8 @@ class Billing(BaseModel): class Limit(BaseModel): """The spending limit configuration.""" - amount: int - """The limit amount in cents.""" + amount: float + """The limit amount in dollars.""" frequency: str """The limit window, for example per24HourPeriod or perAuthorization.""" From e25bbfea3e688b277fdbb5fb7a89a40c87e0b583 Mon Sep 17 00:00:00 2001 From: stlc-bot Date: Wed, 17 Jun 2026 09:04:09 +0000 Subject: [PATCH 060/109] Add recommended actions to account API Stainless-Generated-From: 18ed353e4b37e2e29797a12c1a88a15f01f5d914 --- src/whop_sdk/types/account.py | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/whop_sdk/types/account.py b/src/whop_sdk/types/account.py index 8d502921..7dfc7c6d 100644 --- a/src/whop_sdk/types/account.py +++ b/src/whop_sdk/types/account.py @@ -6,7 +6,7 @@ from .._models import BaseModel from .account_social_link import AccountSocialLink -__all__ = ["Account", "Balance", "Wallet"] +__all__ = ["Account", "Balance", "RecommendedAction", "Wallet"] class Balance(BaseModel): @@ -41,6 +41,19 @@ class Balance(BaseModel): """The total USD value of the holding, or null when no exchange rate is available""" +class RecommendedAction(BaseModel): + """Recommended actions to drive volume on the account""" + + cta: str + """The URL the call-to-action links to""" + + cta_label: str + """The label for the action's call-to-action button""" + + title: str + """The headline describing the recommended action""" + + class Wallet(BaseModel): """The account's primary crypto wallet, or null if none has been provisioned""" @@ -113,6 +126,8 @@ class Account(BaseModel): parent_account_id: Optional[str] = None """The parent account ID for connected accounts""" + recommended_actions: List[RecommendedAction] + require_2fa: bool """ Whether the account requires authorized users to have two-factor authentication From cf8ef2e4bc6cd2f5187bc8c2e820d7a496aa6b0a Mon Sep 17 00:00:00 2001 From: stlc-bot Date: Wed, 17 Jun 2026 09:12:44 +0000 Subject: [PATCH 061/109] feat(payouts): allow platforms to cover withdrawal fees for connected accounts in UI Stainless-Generated-From: 61ac39b7b140176b6a443aa29a24d9604c121d72 --- src/whop_sdk/resources/withdrawals.py | 4 ++-- src/whop_sdk/types/withdrawal_create_params.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/whop_sdk/resources/withdrawals.py b/src/whop_sdk/resources/withdrawals.py index 0eb7bde9..78a2336c 100644 --- a/src/whop_sdk/resources/withdrawals.py +++ b/src/whop_sdk/resources/withdrawals.py @@ -83,7 +83,7 @@ def create( payout_method_id: The ID of the payout method to use for the withdrawal. - platform_covers_fees: Whether the platform covers the payout fees instead of the connected account. + platform_covers_fees: Whether the platform covers the payout fees. statement_descriptor: Custom statement descriptor for the withdrawal. Must be between 5 and 22 characters and contain only alphanumeric characters. @@ -285,7 +285,7 @@ async def create( payout_method_id: The ID of the payout method to use for the withdrawal. - platform_covers_fees: Whether the platform covers the payout fees instead of the connected account. + platform_covers_fees: Whether the platform covers the payout fees. statement_descriptor: Custom statement descriptor for the withdrawal. Must be between 5 and 22 characters and contain only alphanumeric characters. diff --git a/src/whop_sdk/types/withdrawal_create_params.py b/src/whop_sdk/types/withdrawal_create_params.py index 46d134f2..3345abab 100644 --- a/src/whop_sdk/types/withdrawal_create_params.py +++ b/src/whop_sdk/types/withdrawal_create_params.py @@ -24,7 +24,7 @@ class WithdrawalCreateParams(TypedDict, total=False): """The ID of the payout method to use for the withdrawal.""" platform_covers_fees: Optional[bool] - """Whether the platform covers the payout fees instead of the connected account.""" + """Whether the platform covers the payout fees.""" statement_descriptor: Optional[str] """Custom statement descriptor for the withdrawal. From 96baf1b1200c4a00379f5c6f22710f0fd19e3620 Mon Sep 17 00:00:00 2001 From: stlc-bot Date: Wed, 17 Jun 2026 17:23:59 +0000 Subject: [PATCH 062/109] chore(backend): remove referred_by_bot_id code references Stainless-Generated-From: eddf110145f092850194fbd6b66ca33a29bf8a79 --- src/whop_sdk/types/referrals/business_list_response.py | 3 --- src/whop_sdk/types/referrals/business_retrieve_response.py | 3 --- 2 files changed, 6 deletions(-) diff --git a/src/whop_sdk/types/referrals/business_list_response.py b/src/whop_sdk/types/referrals/business_list_response.py index 5eeec0b4..cba6a34b 100644 --- a/src/whop_sdk/types/referrals/business_list_response.py +++ b/src/whop_sdk/types/referrals/business_list_response.py @@ -46,9 +46,6 @@ class BusinessListResponse(BaseModel): referral_started_at: Optional[datetime] = None - referred_by_account_id: Optional[str] = None - """The company that made the referral, if a company referred.""" - status: Literal["active", "removed"] total_earnings: float diff --git a/src/whop_sdk/types/referrals/business_retrieve_response.py b/src/whop_sdk/types/referrals/business_retrieve_response.py index c6022d40..08a4566c 100644 --- a/src/whop_sdk/types/referrals/business_retrieve_response.py +++ b/src/whop_sdk/types/referrals/business_retrieve_response.py @@ -46,9 +46,6 @@ class BusinessRetrieveResponse(BaseModel): referral_started_at: Optional[datetime] = None - referred_by_account_id: Optional[str] = None - """The company that made the referral, if a company referred.""" - status: Literal["active", "removed"] total_earnings: float From 1f188d3b39a7936ac5ea97fdfcf011a4383a82c6 Mon Sep 17 00:00:00 2001 From: stlc-bot Date: Wed, 17 Jun 2026 20:36:14 +0000 Subject: [PATCH 063/109] =?UTF-8?q?Scheduled=20bounties:=20auto-publish=20?= =?UTF-8?q?a=20bounty=20on=20a=20schedule=20(one-time=20or=20recurring)=20?= =?UTF-8?q?=E2=80=94=20backend?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Stainless-Generated-From: 6a156caaed8a9fc15869f33de2ae1d0ecaa78806 --- src/whop_sdk/resources/bounties.py | 35 +++++++++++++++++-- src/whop_sdk/types/bounty_create_params.py | 22 ++++++++++-- src/whop_sdk/types/bounty_create_response.py | 2 +- src/whop_sdk/types/bounty_list_params.py | 2 +- src/whop_sdk/types/bounty_list_response.py | 2 +- .../types/bounty_retrieve_response.py | 2 +- tests/api_resources/test_bounties.py | 7 ++++ 7 files changed, 63 insertions(+), 9 deletions(-) diff --git a/src/whop_sdk/resources/bounties.py b/src/whop_sdk/resources/bounties.py index 640a450c..7f2a1f46 100644 --- a/src/whop_sdk/resources/bounties.py +++ b/src/whop_sdk/resources/bounties.py @@ -2,7 +2,8 @@ from __future__ import annotations -from typing import Optional +from typing import Union, Optional +from datetime import datetime from typing_extensions import Literal import httpx @@ -68,6 +69,9 @@ def create( origin_account_id: Optional[str] | Omit = omit, post_markdown_content: Optional[str] | Omit = omit, post_title: Optional[str] | Omit = omit, + scheduled_frequency: Optional[Literal["once", "hourly", "daily", "weekly", "monthly"]] | Omit = omit, + scheduled_publish_at: Union[str, datetime, None] | Omit = omit, + scheduled_timezone: 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, @@ -113,6 +117,14 @@ def create( post_title: Optional title for the anchor forum post. Falls back to the bounty title when omitted. + scheduled_frequency: How often a scheduled bounty republishes a new bounty. + + scheduled_publish_at: When to publish the bounty. When provided, the bounty is created as a hidden + draft and published at this time instead of immediately. Must be in the future. + + scheduled_timezone: The IANA timezone used for recurring occurrences. Required when + scheduled_publish_at is provided. + extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -136,6 +148,9 @@ def create( "origin_account_id": origin_account_id, "post_markdown_content": post_markdown_content, "post_title": post_title, + "scheduled_frequency": scheduled_frequency, + "scheduled_publish_at": scheduled_publish_at, + "scheduled_timezone": scheduled_timezone, }, bounty_create_params.BountyCreateParams, ), @@ -187,7 +202,7 @@ def list( experience_id: Optional[str] | Omit = omit, first: Optional[int] | Omit = omit, last: Optional[int] | Omit = omit, - status: Optional[Literal["published", "archived"]] | Omit = omit, + status: Optional[Literal["published", "archived", "scheduled"]] | 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, @@ -289,6 +304,9 @@ async def create( origin_account_id: Optional[str] | Omit = omit, post_markdown_content: Optional[str] | Omit = omit, post_title: Optional[str] | Omit = omit, + scheduled_frequency: Optional[Literal["once", "hourly", "daily", "weekly", "monthly"]] | Omit = omit, + scheduled_publish_at: Union[str, datetime, None] | Omit = omit, + scheduled_timezone: 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, @@ -334,6 +352,14 @@ async def create( post_title: Optional title for the anchor forum post. Falls back to the bounty title when omitted. + scheduled_frequency: How often a scheduled bounty republishes a new bounty. + + scheduled_publish_at: When to publish the bounty. When provided, the bounty is created as a hidden + draft and published at this time instead of immediately. Must be in the future. + + scheduled_timezone: The IANA timezone used for recurring occurrences. Required when + scheduled_publish_at is provided. + extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -357,6 +383,9 @@ async def create( "origin_account_id": origin_account_id, "post_markdown_content": post_markdown_content, "post_title": post_title, + "scheduled_frequency": scheduled_frequency, + "scheduled_publish_at": scheduled_publish_at, + "scheduled_timezone": scheduled_timezone, }, bounty_create_params.BountyCreateParams, ), @@ -408,7 +437,7 @@ def list( experience_id: Optional[str] | Omit = omit, first: Optional[int] | Omit = omit, last: Optional[int] | Omit = omit, - status: Optional[Literal["published", "archived"]] | Omit = omit, + status: Optional[Literal["published", "archived", "scheduled"]] | 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, diff --git a/src/whop_sdk/types/bounty_create_params.py b/src/whop_sdk/types/bounty_create_params.py index 583854de..277d4cc1 100644 --- a/src/whop_sdk/types/bounty_create_params.py +++ b/src/whop_sdk/types/bounty_create_params.py @@ -2,10 +2,12 @@ from __future__ import annotations -from typing import Optional -from typing_extensions import Literal, Required, TypedDict +from typing import Union, Optional +from datetime import datetime +from typing_extensions import Literal, Required, Annotated, TypedDict from .._types import SequenceNotStr +from .._utils import PropertyInfo from .shared.currency import Currency __all__ = ["BountyCreateParams"] @@ -69,3 +71,19 @@ class BountyCreateParams(TypedDict, total=False): Falls back to the bounty title when omitted. """ + + scheduled_frequency: Optional[Literal["once", "hourly", "daily", "weekly", "monthly"]] + """How often a scheduled bounty republishes a new bounty.""" + + scheduled_publish_at: Annotated[Union[str, datetime, None], PropertyInfo(format="iso8601")] + """When to publish the bounty. + + When provided, the bounty is created as a hidden draft and published at this + time instead of immediately. Must be in the future. + """ + + scheduled_timezone: Optional[str] + """The IANA timezone used for recurring occurrences. + + Required when scheduled_publish_at is provided. + """ diff --git a/src/whop_sdk/types/bounty_create_response.py b/src/whop_sdk/types/bounty_create_response.py index 94c4f5e4..45e80b8a 100644 --- a/src/whop_sdk/types/bounty_create_response.py +++ b/src/whop_sdk/types/bounty_create_response.py @@ -27,7 +27,7 @@ class BountyCreateResponse(BaseModel): description: str """The description of the bounty.""" - status: Literal["published", "archived"] + status: Literal["published", "archived", "scheduled"] """The current lifecycle status of the bounty.""" title: str diff --git a/src/whop_sdk/types/bounty_list_params.py b/src/whop_sdk/types/bounty_list_params.py index cb80eaba..08aa2311 100644 --- a/src/whop_sdk/types/bounty_list_params.py +++ b/src/whop_sdk/types/bounty_list_params.py @@ -32,5 +32,5 @@ class BountyListParams(TypedDict, total=False): last: Optional[int] """Returns the last _n_ elements from the list.""" - status: Optional[Literal["published", "archived"]] + status: Optional[Literal["published", "archived", "scheduled"]] """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 index 0117a1ba..fcb7c54a 100644 --- a/src/whop_sdk/types/bounty_list_response.py +++ b/src/whop_sdk/types/bounty_list_response.py @@ -27,7 +27,7 @@ class BountyListResponse(BaseModel): description: str """The description of the bounty.""" - status: Literal["published", "archived"] + status: Literal["published", "archived", "scheduled"] """The current lifecycle status of the bounty.""" title: str diff --git a/src/whop_sdk/types/bounty_retrieve_response.py b/src/whop_sdk/types/bounty_retrieve_response.py index d9df060a..56ffc09d 100644 --- a/src/whop_sdk/types/bounty_retrieve_response.py +++ b/src/whop_sdk/types/bounty_retrieve_response.py @@ -27,7 +27,7 @@ class BountyRetrieveResponse(BaseModel): description: str """The description of the bounty.""" - status: Literal["published", "archived"] + status: Literal["published", "archived", "scheduled"] """The current lifecycle status of the bounty.""" title: str diff --git a/tests/api_resources/test_bounties.py b/tests/api_resources/test_bounties.py index d81b572b..99ac9142 100644 --- a/tests/api_resources/test_bounties.py +++ b/tests/api_resources/test_bounties.py @@ -14,6 +14,7 @@ BountyCreateResponse, BountyRetrieveResponse, ) +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") @@ -48,6 +49,9 @@ def test_method_create_with_all_params(self, client: Whop) -> None: origin_account_id="origin_account_id", post_markdown_content="post_markdown_content", post_title="post_title", + scheduled_frequency="once", + scheduled_publish_at=parse_datetime("2023-12-01T05:00:00.401Z"), + scheduled_timezone="scheduled_timezone", ) assert_matches_type(BountyCreateResponse, bounty, path=["response"]) @@ -199,6 +203,9 @@ async def test_method_create_with_all_params(self, async_client: AsyncWhop) -> N origin_account_id="origin_account_id", post_markdown_content="post_markdown_content", post_title="post_title", + scheduled_frequency="once", + scheduled_publish_at=parse_datetime("2023-12-01T05:00:00.401Z"), + scheduled_timezone="scheduled_timezone", ) assert_matches_type(BountyCreateResponse, bounty, path=["response"]) From 0d68cf11ec8bd184b674a8af78e8e0a65bcc93fe Mon Sep 17 00:00:00 2001 From: stlc-bot Date: Thu, 18 Jun 2026 00:43:44 +0000 Subject: [PATCH 064/109] Add a realistic sample response to the Ledger Activity API docs Stainless-Generated-From: e68e1a4c11323fe3f814eb963feae166ad5582e4 --- .../types/financial_activity_list_response.py | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/src/whop_sdk/types/financial_activity_list_response.py b/src/whop_sdk/types/financial_activity_list_response.py index 9ed35a95..66a85874 100644 --- a/src/whop_sdk/types/financial_activity_list_response.py +++ b/src/whop_sdk/types/financial_activity_list_response.py @@ -223,12 +223,21 @@ class DataSource(BaseModel): object: str + amount_float: Optional[float] = None + """ + Withdrawal amount as a decimal number in the destination currency (withdrawal + sources only; requires payout:withdrawal:read). + """ + chain: Optional[str] = None """ Chain the deposit landed on, for example plasma (onchain_transaction sources only). """ + claim_url: Optional[str] = None + """Public claim URL for the airdrop link (airdrop_link sources only).""" + created_at: Optional[datetime] = None """ Withdrawal creation time as an ISO 8601 timestamp (withdrawal sources only; @@ -266,9 +275,11 @@ class DataSource(BaseModel): """ status: Optional[str] = None - """ - Withdrawal lifecycle status (withdrawal sources only; requires - payout:withdrawal:read). + """Lifecycle status. + + On withdrawal sources this is the withdrawal status (requires + payout:withdrawal:read); on airdrop_link sources it is the claim-link status + (ungated). """ to_amount: Optional[str] = None From ebb720f197cba876eafdc93b77c24142f00ffaf8 Mon Sep 17 00:00:00 2001 From: stlc-bot Date: Thu, 18 Jun 2026 01:07:03 +0000 Subject: [PATCH 065/109] feat(identity): return alpha-2 country code from IdentityProfile GraphQL Stainless-Generated-From: 3b42b4672974eb2d5052312733b3df451e4e1249 --- .../types/identity_profile_approved_webhook_event.py | 4 ++-- .../types/identity_profile_needs_action_webhook_event.py | 4 ++-- .../types/identity_profile_rejected_webhook_event.py | 4 ++-- .../types/identity_profile_updated_webhook_event.py | 4 ++-- src/whop_sdk/types/verification_create_response.py | 6 ++++++ src/whop_sdk/types/verification_list_response.py | 6 ++++++ src/whop_sdk/types/verification_retrieve_response.py | 6 ++++++ src/whop_sdk/types/verification_update_response.py | 6 ++++++ 8 files changed, 32 insertions(+), 8 deletions(-) diff --git a/src/whop_sdk/types/identity_profile_approved_webhook_event.py b/src/whop_sdk/types/identity_profile_approved_webhook_event.py index f498efd8..7a737ae0 100644 --- a/src/whop_sdk/types/identity_profile_approved_webhook_event.py +++ b/src/whop_sdk/types/identity_profile_approved_webhook_event.py @@ -135,9 +135,9 @@ class Data(BaseModel): """ country: Optional[str] = None - """ISO 3166-1 alpha-3 country code (e.g. + """ISO 3166-1 alpha-2 country code (e.g. - `USA`, `GBR`). For individuals this is the country of citizenship or residence + `US`, `GB`). For individuals this is the country of citizenship or residence reported by the identity provider; for businesses this is the country of incorporation. """ diff --git a/src/whop_sdk/types/identity_profile_needs_action_webhook_event.py b/src/whop_sdk/types/identity_profile_needs_action_webhook_event.py index 83b88252..7f67727b 100644 --- a/src/whop_sdk/types/identity_profile_needs_action_webhook_event.py +++ b/src/whop_sdk/types/identity_profile_needs_action_webhook_event.py @@ -135,9 +135,9 @@ class Data(BaseModel): """ country: Optional[str] = None - """ISO 3166-1 alpha-3 country code (e.g. + """ISO 3166-1 alpha-2 country code (e.g. - `USA`, `GBR`). For individuals this is the country of citizenship or residence + `US`, `GB`). For individuals this is the country of citizenship or residence reported by the identity provider; for businesses this is the country of incorporation. """ diff --git a/src/whop_sdk/types/identity_profile_rejected_webhook_event.py b/src/whop_sdk/types/identity_profile_rejected_webhook_event.py index d6188520..9de64813 100644 --- a/src/whop_sdk/types/identity_profile_rejected_webhook_event.py +++ b/src/whop_sdk/types/identity_profile_rejected_webhook_event.py @@ -135,9 +135,9 @@ class Data(BaseModel): """ country: Optional[str] = None - """ISO 3166-1 alpha-3 country code (e.g. + """ISO 3166-1 alpha-2 country code (e.g. - `USA`, `GBR`). For individuals this is the country of citizenship or residence + `US`, `GB`). For individuals this is the country of citizenship or residence reported by the identity provider; for businesses this is the country of incorporation. """ diff --git a/src/whop_sdk/types/identity_profile_updated_webhook_event.py b/src/whop_sdk/types/identity_profile_updated_webhook_event.py index 23b9a859..cea15a83 100644 --- a/src/whop_sdk/types/identity_profile_updated_webhook_event.py +++ b/src/whop_sdk/types/identity_profile_updated_webhook_event.py @@ -135,9 +135,9 @@ class Data(BaseModel): """ country: Optional[str] = None - """ISO 3166-1 alpha-3 country code (e.g. + """ISO 3166-1 alpha-2 country code (e.g. - `USA`, `GBR`). For individuals this is the country of citizenship or residence + `US`, `GB`). For individuals this is the country of citizenship or residence reported by the identity provider; for businesses this is the country of incorporation. """ diff --git a/src/whop_sdk/types/verification_create_response.py b/src/whop_sdk/types/verification_create_response.py index 895ffc00..ac7a1faa 100644 --- a/src/whop_sdk/types/verification_create_response.py +++ b/src/whop_sdk/types/verification_create_response.py @@ -47,6 +47,12 @@ class VerificationCreateResponse(BaseModel): business_structure: Optional[str] = None country: Optional[str] = None + """ISO 3166-1 alpha-2 country code (e.g. + + `US`, `GB`). For individuals this is the country of citizenship or residence + reported by the identity provider; for businesses this is the country of + incorporation. + """ created_at: Optional[str] = None diff --git a/src/whop_sdk/types/verification_list_response.py b/src/whop_sdk/types/verification_list_response.py index 5a98c45d..0bd21076 100644 --- a/src/whop_sdk/types/verification_list_response.py +++ b/src/whop_sdk/types/verification_list_response.py @@ -47,6 +47,12 @@ class Data(BaseModel): business_structure: Optional[str] = None country: Optional[str] = None + """ISO 3166-1 alpha-2 country code (e.g. + + `US`, `GB`). For individuals this is the country of citizenship or residence + reported by the identity provider; for businesses this is the country of + incorporation. + """ created_at: Optional[str] = None diff --git a/src/whop_sdk/types/verification_retrieve_response.py b/src/whop_sdk/types/verification_retrieve_response.py index 9018a263..2b37ff1e 100644 --- a/src/whop_sdk/types/verification_retrieve_response.py +++ b/src/whop_sdk/types/verification_retrieve_response.py @@ -47,6 +47,12 @@ class VerificationRetrieveResponse(BaseModel): business_structure: Optional[str] = None country: Optional[str] = None + """ISO 3166-1 alpha-2 country code (e.g. + + `US`, `GB`). For individuals this is the country of citizenship or residence + reported by the identity provider; for businesses this is the country of + incorporation. + """ created_at: Optional[str] = None diff --git a/src/whop_sdk/types/verification_update_response.py b/src/whop_sdk/types/verification_update_response.py index b10f4f03..a75097ee 100644 --- a/src/whop_sdk/types/verification_update_response.py +++ b/src/whop_sdk/types/verification_update_response.py @@ -47,6 +47,12 @@ class VerificationUpdateResponse(BaseModel): business_structure: Optional[str] = None country: Optional[str] = None + """ISO 3166-1 alpha-2 country code (e.g. + + `US`, `GB`). For individuals this is the country of citizenship or residence + reported by the identity provider; for businesses this is the country of + incorporation. + """ created_at: Optional[str] = None From f227fc9014c5618555b0306b46c34b779a398bee Mon Sep 17 00:00:00 2001 From: stlc-bot Date: Thu, 18 Jun 2026 05:06:07 +0000 Subject: [PATCH 066/109] Create IdentityProfile then Provision Payout Account Stainless-Generated-From: c8fa75801f15f1f37cced480881e2f8e35791a63 --- src/whop_sdk/resources/verifications.py | 72 +++++++++---------- .../types/verification_create_params.py | 41 ++++++----- 2 files changed, 54 insertions(+), 59 deletions(-) diff --git a/src/whop_sdk/resources/verifications.py b/src/whop_sdk/resources/verifications.py index c7b88492..452b5690 100644 --- a/src/whop_sdk/resources/verifications.py +++ b/src/whop_sdk/resources/verifications.py @@ -79,38 +79,36 @@ def create( Args: account_id: The account ID to verify (biz\\__ tag). - address: Address (line1, city, state, postal_code). line1, city and postal_code are - required for individuals when this request sets up the payout account; not - required for businesses. + address: Optional pre-fill claim. Address (line1, city, state, postal_code). - business_name: Business name. Required for businesses. + business_name: Optional pre-fill claim for businesses. - business_structure: Business structure (e.g. llc, corporation). + business_structure: Optional. Business structure (e.g. llc, corporation). - country: Country code. Required. For businesses this is the country of incorporation. + country: Optional pre-fill claim. Country code; for businesses, the country of + incorporation. - date_of_birth: Date of birth. Required for individuals when this request sets up the payout - account. + date_of_birth: Optional pre-fill claim. Seeds the Sumsub session; attested values come from + Sumsub on approval. - first_name: First name. Required for individuals when this request sets up the payout - account. + first_name: Optional pre-fill claim. Seeds the Sumsub session; attested values come from + Sumsub on approval. kind: The verification type. Defaults to individual. - last_name: Last name. Required for individuals when this request sets up the payout - account. + last_name: Optional pre-fill claim. Seeds the Sumsub session; attested values come from + Sumsub on approval. - phone: Pre-fill the phone number. + phone: Optional pre-fill claim — phone number. - place_of_incorporation: Place of incorporation (state/region). Required for businesses; maps to the - address state. + place_of_incorporation: Optional. Place of incorporation (state/region); maps to the business address + state. restart: Whether to restart an in-flight verification. - tax_identification_number: Tax identification number — SSN for individuals, EIN for businesses. Required - for business; recommended for individuals. Tokenized in transit, never stored - raw, and pre-fills the payout account's tax-id requirement so no RFI is raised - for it. + tax_identification_number: Optional. Tax identification number — SSN for individuals, EIN for businesses. + Tokenized in transit, never stored raw; stored on the profile so the payout + account, provisioned on approval, doesn't raise a tax-id RFI. extra_headers: Send extra headers @@ -380,38 +378,36 @@ async def create( Args: account_id: The account ID to verify (biz\\__ tag). - address: Address (line1, city, state, postal_code). line1, city and postal_code are - required for individuals when this request sets up the payout account; not - required for businesses. + address: Optional pre-fill claim. Address (line1, city, state, postal_code). - business_name: Business name. Required for businesses. + business_name: Optional pre-fill claim for businesses. - business_structure: Business structure (e.g. llc, corporation). + business_structure: Optional. Business structure (e.g. llc, corporation). - country: Country code. Required. For businesses this is the country of incorporation. + country: Optional pre-fill claim. Country code; for businesses, the country of + incorporation. - date_of_birth: Date of birth. Required for individuals when this request sets up the payout - account. + date_of_birth: Optional pre-fill claim. Seeds the Sumsub session; attested values come from + Sumsub on approval. - first_name: First name. Required for individuals when this request sets up the payout - account. + first_name: Optional pre-fill claim. Seeds the Sumsub session; attested values come from + Sumsub on approval. kind: The verification type. Defaults to individual. - last_name: Last name. Required for individuals when this request sets up the payout - account. + last_name: Optional pre-fill claim. Seeds the Sumsub session; attested values come from + Sumsub on approval. - phone: Pre-fill the phone number. + phone: Optional pre-fill claim — phone number. - place_of_incorporation: Place of incorporation (state/region). Required for businesses; maps to the - address state. + place_of_incorporation: Optional. Place of incorporation (state/region); maps to the business address + state. restart: Whether to restart an in-flight verification. - tax_identification_number: Tax identification number — SSN for individuals, EIN for businesses. Required - for business; recommended for individuals. Tokenized in transit, never stored - raw, and pre-fills the payout account's tax-id requirement so no RFI is raised - for it. + tax_identification_number: Optional. Tax identification number — SSN for individuals, EIN for businesses. + Tokenized in transit, never stored raw; stored on the profile so the payout + account, provisioned on approval, doesn't raise a tax-id RFI. extra_headers: Send extra headers diff --git a/src/whop_sdk/types/verification_create_params.py b/src/whop_sdk/types/verification_create_params.py index 4e128ffd..b1a13652 100644 --- a/src/whop_sdk/types/verification_create_params.py +++ b/src/whop_sdk/types/verification_create_params.py @@ -13,58 +13,57 @@ class VerificationCreateParams(TypedDict, total=False): """The account ID to verify (biz\\__ tag).""" address: Dict[str, object] - """Address (line1, city, state, postal_code). - - line1, city and postal_code are required for individuals when this request sets - up the payout account; not required for businesses. - """ + """Optional pre-fill claim. Address (line1, city, state, postal_code).""" business_name: str - """Business name. Required for businesses.""" + """Optional pre-fill claim for businesses.""" business_structure: str - """Business structure (e.g. llc, corporation).""" + """Optional. Business structure (e.g. llc, corporation).""" country: str - """Country code. Required. For businesses this is the country of incorporation.""" + """Optional pre-fill claim. + + Country code; for businesses, the country of incorporation. + """ date_of_birth: str - """Date of birth. + """Optional pre-fill claim. - Required for individuals when this request sets up the payout account. + Seeds the Sumsub session; attested values come from Sumsub on approval. """ first_name: str - """First name. + """Optional pre-fill claim. - Required for individuals when this request sets up the payout account. + Seeds the Sumsub session; attested values come from Sumsub on approval. """ kind: Literal["individual", "business"] """The verification type. Defaults to individual.""" last_name: str - """Last name. + """Optional pre-fill claim. - Required for individuals when this request sets up the payout account. + Seeds the Sumsub session; attested values come from Sumsub on approval. """ phone: str - """Pre-fill the phone number.""" + """Optional pre-fill claim — phone number.""" place_of_incorporation: str - """Place of incorporation (state/region). + """Optional. - Required for businesses; maps to the address state. + Place of incorporation (state/region); maps to the business address state. """ restart: bool """Whether to restart an in-flight verification.""" tax_identification_number: str - """Tax identification number — SSN for individuals, EIN for businesses. + """Optional. - Required for business; recommended for individuals. Tokenized in transit, never - stored raw, and pre-fills the payout account's tax-id requirement so no RFI is - raised for it. + Tax identification number — SSN for individuals, EIN for businesses. Tokenized + in transit, never stored raw; stored on the profile so the payout account, + provisioned on approval, doesn't raise a tax-id RFI. """ From 45a2bebba9c44e0f85898a47f89840e35f4f4751 Mon Sep 17 00:00:00 2001 From: stlc-bot Date: Thu, 18 Jun 2026 05:32:44 +0000 Subject: [PATCH 067/109] Publish brand illustrations publicly + add recommended-action icons to the accounts API Stainless-Generated-From: 8a647ac049f85a482ab682512d77cd0ed9665508 --- src/whop_sdk/types/account.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/whop_sdk/types/account.py b/src/whop_sdk/types/account.py index 7dfc7c6d..18257794 100644 --- a/src/whop_sdk/types/account.py +++ b/src/whop_sdk/types/account.py @@ -50,6 +50,9 @@ class RecommendedAction(BaseModel): cta_label: str """The label for the action's call-to-action button""" + icon_url: Optional[str] = None + """The URL of the action's illustration icon, or null if it has none""" + title: str """The headline describing the recommended action""" From bfa38a0a34cfb808838e1de1e9c0f62b94fd589d Mon Sep 17 00:00:00 2001 From: stlc-bot Date: Thu, 18 Jun 2026 07:56:20 +0000 Subject: [PATCH 068/109] feat(verifications): persist business_website for individual KYC Stainless-Generated-From: d2fa22f7f8afe623a71737290b049f7f98a753fb --- src/whop_sdk/resources/verifications.py | 12 ++++++++++++ src/whop_sdk/types/verification_create_params.py | 8 ++++++++ tests/api_resources/test_verifications.py | 2 ++ 3 files changed, 22 insertions(+) diff --git a/src/whop_sdk/resources/verifications.py b/src/whop_sdk/resources/verifications.py index 452b5690..3403ff34 100644 --- a/src/whop_sdk/resources/verifications.py +++ b/src/whop_sdk/resources/verifications.py @@ -57,6 +57,7 @@ def create( address: Dict[str, object] | Omit = omit, business_name: str | Omit = omit, business_structure: str | Omit = omit, + business_website: str | Omit = omit, country: str | Omit = omit, date_of_birth: str | Omit = omit, first_name: str | Omit = omit, @@ -85,6 +86,10 @@ def create( business_structure: Optional. Business structure (e.g. llc, corporation). + business_website: Optional. Business website URL. Accepted for both individual and business + verifications on company accounts; persisted to the account's metadata and used + to provision the payout account on approval. Whop store pages are rejected. + country: Optional pre-fill claim. Country code; for businesses, the country of incorporation. @@ -125,6 +130,7 @@ def create( "address": address, "business_name": business_name, "business_structure": business_structure, + "business_website": business_website, "country": country, "date_of_birth": date_of_birth, "first_name": first_name, @@ -356,6 +362,7 @@ async def create( address: Dict[str, object] | Omit = omit, business_name: str | Omit = omit, business_structure: str | Omit = omit, + business_website: str | Omit = omit, country: str | Omit = omit, date_of_birth: str | Omit = omit, first_name: str | Omit = omit, @@ -384,6 +391,10 @@ async def create( business_structure: Optional. Business structure (e.g. llc, corporation). + business_website: Optional. Business website URL. Accepted for both individual and business + verifications on company accounts; persisted to the account's metadata and used + to provision the payout account on approval. Whop store pages are rejected. + country: Optional pre-fill claim. Country code; for businesses, the country of incorporation. @@ -424,6 +435,7 @@ async def create( "address": address, "business_name": business_name, "business_structure": business_structure, + "business_website": business_website, "country": country, "date_of_birth": date_of_birth, "first_name": first_name, diff --git a/src/whop_sdk/types/verification_create_params.py b/src/whop_sdk/types/verification_create_params.py index b1a13652..792d5488 100644 --- a/src/whop_sdk/types/verification_create_params.py +++ b/src/whop_sdk/types/verification_create_params.py @@ -21,6 +21,14 @@ class VerificationCreateParams(TypedDict, total=False): business_structure: str """Optional. Business structure (e.g. llc, corporation).""" + business_website: str + """Optional. + + Business website URL. Accepted for both individual and business verifications on + company accounts; persisted to the account's metadata and used to provision the + payout account on approval. Whop store pages are rejected. + """ + country: str """Optional pre-fill claim. diff --git a/tests/api_resources/test_verifications.py b/tests/api_resources/test_verifications.py index 5c6fa1f0..1cb41c09 100644 --- a/tests/api_resources/test_verifications.py +++ b/tests/api_resources/test_verifications.py @@ -39,6 +39,7 @@ def test_method_create_with_all_params(self, client: Whop) -> None: address={"foo": "bar"}, business_name="business_name", business_structure="business_structure", + business_website="business_website", country="country", date_of_birth="date_of_birth", first_name="first_name", @@ -284,6 +285,7 @@ async def test_method_create_with_all_params(self, async_client: AsyncWhop) -> N address={"foo": "bar"}, business_name="business_name", business_structure="business_structure", + business_website="business_website", country="country", date_of_birth="date_of_birth", first_name="first_name", From df3ed6acb1c5c26ab59cfdc097d1d40695208232 Mon Sep 17 00:00:00 2001 From: stlc-bot Date: Thu, 18 Jun 2026 16:08:42 +0000 Subject: [PATCH 069/109] Enrich top-up activity rows with payment status and method Stainless-Generated-From: 9013c6785c6261018881031756f6e060f0d1ab51 --- src/whop_sdk/types/financial_activity_list_response.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/whop_sdk/types/financial_activity_list_response.py b/src/whop_sdk/types/financial_activity_list_response.py index 66a85874..7b21b302 100644 --- a/src/whop_sdk/types/financial_activity_list_response.py +++ b/src/whop_sdk/types/financial_activity_list_response.py @@ -279,7 +279,8 @@ class DataSource(BaseModel): On withdrawal sources this is the withdrawal status (requires payout:withdrawal:read); on airdrop_link sources it is the claim-link status - (ungated). + (ungated); on payment and top-up sources it is the friendly payment status such + as succeeded/pending/failed (ungated). """ to_amount: Optional[str] = None From 747a61a26a142f703f0b7082fc546871fd9aedd3 Mon Sep 17 00:00:00 2001 From: stlc-bot Date: Thu, 18 Jun 2026 19:59:33 +0000 Subject: [PATCH 070/109] fix(payments): align tax ID types with Numeral's supported list Stainless-Generated-From: d74cd97f1892236e62c0451197b2d3a45ad85347 --- src/whop_sdk/types/tax_identifier_type.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/whop_sdk/types/tax_identifier_type.py b/src/whop_sdk/types/tax_identifier_type.py index 6b186a3c..92c4dbb9 100644 --- a/src/whop_sdk/types/tax_identifier_type.py +++ b/src/whop_sdk/types/tax_identifier_type.py @@ -8,6 +8,7 @@ "ad_nrt", "ao_tin", "ar_cuit", + "al_tin", "am_tin", "aw_tin", "au_abn", @@ -48,6 +49,7 @@ "et_tin", "eu_oss_vat", "ge_vat", + "gh_tin", "de_stn", "gb_vat", "gn_nif", From 936783ebf48e5f285ba7028f23bd929294d64274 Mon Sep 17 00:00:00 2001 From: stlc-bot Date: Thu, 18 Jun 2026 22:43:36 +0000 Subject: [PATCH 071/109] Checkout sessions v1 port Stainless-Generated-From: 00891d743e62c431b5cb9096c88817b42accf697 --- .stats.yml | 2 +- api.md | 12 +- src/whop_sdk/_client.py | 6 - .../resources/checkout_configurations.py | 692 +++++------------- src/whop_sdk/types/__init__.py | 6 + .../checkout_configuration_create_params.py | 545 ++------------ .../checkout_configuration_create_response.py | 52 ++ .../checkout_configuration_list_params.py | 42 +- .../checkout_configuration_list_response.py | 167 +---- ...heckout_configuration_retrieve_response.py | 52 ++ .../types/shared/checkout_configuration.py | 6 - src/whop_sdk/types/shared_params/__init__.py | 1 - src/whop_sdk/types/shared_params/tax_type.py | 9 - .../test_checkout_configurations.py | 624 +++++----------- 14 files changed, 579 insertions(+), 1637 deletions(-) create mode 100644 src/whop_sdk/types/checkout_configuration_create_response.py create mode 100644 src/whop_sdk/types/checkout_configuration_retrieve_response.py delete mode 100644 src/whop_sdk/types/shared_params/tax_type.py diff --git a/.stats.yml b/.stats.yml index 04d8692c..1b7187f8 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1 +1 @@ -configured_endpoints: 241 +configured_endpoints: 242 diff --git a/api.md b/api.md index deb1372f..08fab0da 100644 --- a/api.md +++ b/api.md @@ -353,14 +353,20 @@ Methods: Types: ```python -from whop_sdk.types import CheckoutModes, CheckoutConfigurationListResponse +from whop_sdk.types import ( + CheckoutModes, + CheckoutConfigurationCreateResponse, + CheckoutConfigurationRetrieveResponse, + CheckoutConfigurationListResponse, +) ``` Methods: -- client.checkout_configurations.create(\*\*params) -> CheckoutConfiguration -- client.checkout_configurations.retrieve(id) -> CheckoutConfiguration +- client.checkout_configurations.create(\*\*params) -> CheckoutConfigurationCreateResponse +- client.checkout_configurations.retrieve(id) -> CheckoutConfigurationRetrieveResponse - client.checkout_configurations.list(\*\*params) -> SyncCursorPage[CheckoutConfigurationListResponse] +- client.checkout_configurations.delete(id) -> None # Messages diff --git a/src/whop_sdk/_client.py b/src/whop_sdk/_client.py index 074aecad..f7b08806 100644 --- a/src/whop_sdk/_client.py +++ b/src/whop_sdk/_client.py @@ -370,7 +370,6 @@ def shipments(self) -> ShipmentsResource: @cached_property def checkout_configurations(self) -> CheckoutConfigurationsResource: - """Checkout configurations""" from .resources.checkout_configurations import CheckoutConfigurationsResource return CheckoutConfigurationsResource(self) @@ -1029,7 +1028,6 @@ def shipments(self) -> AsyncShipmentsResource: @cached_property def checkout_configurations(self) -> AsyncCheckoutConfigurationsResource: - """Checkout configurations""" from .resources.checkout_configurations import AsyncCheckoutConfigurationsResource return AsyncCheckoutConfigurationsResource(self) @@ -1608,7 +1606,6 @@ def shipments(self) -> shipments.ShipmentsResourceWithRawResponse: @cached_property def checkout_configurations(self) -> checkout_configurations.CheckoutConfigurationsResourceWithRawResponse: - """Checkout configurations""" from .resources.checkout_configurations import CheckoutConfigurationsResourceWithRawResponse return CheckoutConfigurationsResourceWithRawResponse(self._client.checkout_configurations) @@ -2069,7 +2066,6 @@ def shipments(self) -> shipments.AsyncShipmentsResourceWithRawResponse: @cached_property def checkout_configurations(self) -> checkout_configurations.AsyncCheckoutConfigurationsResourceWithRawResponse: - """Checkout configurations""" from .resources.checkout_configurations import AsyncCheckoutConfigurationsResourceWithRawResponse return AsyncCheckoutConfigurationsResourceWithRawResponse(self._client.checkout_configurations) @@ -2532,7 +2528,6 @@ def shipments(self) -> shipments.ShipmentsResourceWithStreamingResponse: @cached_property def checkout_configurations(self) -> checkout_configurations.CheckoutConfigurationsResourceWithStreamingResponse: - """Checkout configurations""" from .resources.checkout_configurations import CheckoutConfigurationsResourceWithStreamingResponse return CheckoutConfigurationsResourceWithStreamingResponse(self._client.checkout_configurations) @@ -2997,7 +2992,6 @@ def shipments(self) -> shipments.AsyncShipmentsResourceWithStreamingResponse: def checkout_configurations( self, ) -> checkout_configurations.AsyncCheckoutConfigurationsResourceWithStreamingResponse: - """Checkout configurations""" from .resources.checkout_configurations import AsyncCheckoutConfigurationsResourceWithStreamingResponse return AsyncCheckoutConfigurationsResourceWithStreamingResponse(self._client.checkout_configurations) diff --git a/src/whop_sdk/resources/checkout_configurations.py b/src/whop_sdk/resources/checkout_configurations.py index b2e70eeb..7a15580f 100644 --- a/src/whop_sdk/resources/checkout_configurations.py +++ b/src/whop_sdk/resources/checkout_configurations.py @@ -2,15 +2,14 @@ from __future__ import annotations -from typing import Dict, Union, Optional -from datetime import datetime -from typing_extensions import Literal, overload +from typing import Optional +from typing_extensions import Literal import httpx from ..types import checkout_configuration_list_params, checkout_configuration_create_params -from .._types import Body, Omit, Query, Headers, NotGiven, omit, not_given -from .._utils import path_template, required_args, maybe_transform, async_maybe_transform +from .._types import Body, Omit, Query, Headers, NoneType, 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 ( @@ -21,17 +20,14 @@ ) 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.shared.checkout_configuration import CheckoutConfiguration from ..types.checkout_configuration_list_response import CheckoutConfigurationListResponse +from ..types.checkout_configuration_create_response import CheckoutConfigurationCreateResponse +from ..types.checkout_configuration_retrieve_response import CheckoutConfigurationRetrieveResponse __all__ = ["CheckoutConfigurationsResource", "AsyncCheckoutConfigurationsResource"] class CheckoutConfigurationsResource(SyncAPIResource): - """Checkout configurations""" - @cached_property def with_raw_response(self) -> CheckoutConfigurationsResourceWithRawResponse: """ @@ -51,207 +47,50 @@ def with_streaming_response(self) -> CheckoutConfigurationsResourceWithStreaming """ return CheckoutConfigurationsResourceWithStreamingResponse(self) - @overload def create( self, *, - plan: checkout_configuration_create_params.CreateCheckoutSessionInputModePaymentWithPlanPlan, affiliate_code: Optional[str] | Omit = omit, - allow_promo_codes: Optional[bool] | Omit = omit, - checkout_styling: Optional[ - checkout_configuration_create_params.CreateCheckoutSessionInputModePaymentWithPlanCheckoutStyling - ] - | Omit = omit, - currency: Optional[Currency] | Omit = omit, - metadata: Optional[Dict[str, object]] | Omit = omit, - mode: Literal["payment"] | Omit = omit, - payment_method_configuration: Optional[ - checkout_configuration_create_params.CreateCheckoutSessionInputModePaymentWithPlanPaymentMethodConfiguration - ] - | Omit = omit, - redirect_url: Optional[str] | Omit = omit, - source_url: 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, - ) -> CheckoutConfiguration: - """ - Creates a new checkout configuration - - Required permissions: - - - `checkout_configuration:create` - - `plan:create` - - `access_pass:create` - - `access_pass:update` - - `checkout_configuration:basic:read` - - Args: - plan: The plan attributes to create a new plan inline for this checkout configuration. - - affiliate_code: An affiliate tracking code to attribute the checkout to a specific affiliate. - - allow_promo_codes: Whether the checkout should show the promo code input field and accept promo - codes. Defaults to true. - - checkout_styling: Checkout styling overrides for this session. Overrides plan and company - defaults. - - currency: The available currencies on the platform - - metadata: Custom key-value metadata to attach to the checkout configuration. - - payment_method_configuration: The explicit payment method configuration for the checkout session. Only applies - to setup mode. If not provided, the platform or company defaults will apply. - - redirect_url: The URL to redirect the user to after checkout is completed. - - source_url: The URL of the page where the checkout is being initiated 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 - """ - ... - - @overload - def create( - self, - *, - plan_id: str, - affiliate_code: Optional[str] | Omit = omit, - allow_promo_codes: Optional[bool] | Omit = omit, - checkout_styling: Optional[ - checkout_configuration_create_params.CreateCheckoutSessionInputModePaymentWithPlanIDCheckoutStyling - ] - | Omit = omit, - currency: Optional[Currency] | Omit = omit, - metadata: Optional[Dict[str, object]] | Omit = omit, - mode: Literal["payment"] | Omit = omit, - payment_method_configuration: Optional[ - checkout_configuration_create_params.CreateCheckoutSessionInputModePaymentWithPlanIDPaymentMethodConfiguration - ] - | Omit = omit, - redirect_url: Optional[str] | Omit = omit, - source_url: 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, - ) -> CheckoutConfiguration: - """ - Creates a new checkout configuration - - Required permissions: - - - `checkout_configuration:create` - - `plan:create` - - `access_pass:create` - - `access_pass:update` - - `checkout_configuration:basic:read` - - Args: - plan_id: The unique identifier of an existing plan to use for this checkout - configuration. - - affiliate_code: An affiliate tracking code to attribute the checkout to a specific affiliate. - - allow_promo_codes: Whether the checkout should show the promo code input field and accept promo - codes. Defaults to true. - - checkout_styling: Checkout styling overrides for this session. Overrides plan and company - defaults. - - currency: The available currencies on the platform - - metadata: Custom key-value metadata to attach to the checkout configuration. - - payment_method_configuration: The explicit payment method configuration for the checkout session. Only applies - to setup mode. If not provided, the platform or company defaults will apply. - - redirect_url: The URL to redirect the user to after checkout is completed. - - source_url: The URL of the page where the checkout is being initiated 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 - """ - ... - - @overload - def create( - self, - *, - company_id: str, - mode: Literal["setup"], - allow_promo_codes: Optional[bool] | Omit = omit, - checkout_styling: Optional[ - checkout_configuration_create_params.CreateCheckoutSessionInputModeSetupCheckoutStyling - ] - | Omit = omit, - currency: Optional[Currency] | Omit = omit, - metadata: Optional[Dict[str, object]] | Omit = omit, - payment_method_configuration: Optional[ - checkout_configuration_create_params.CreateCheckoutSessionInputModeSetupPaymentMethodConfiguration - ] + company_id: str | Omit = omit, + currency: Optional[str] | Omit = omit, + metadata: Optional[object] | Omit = omit, + mode: Literal["payment", "setup"] | Omit = omit, + payment_method_configuration: Optional[checkout_configuration_create_params.PaymentMethodConfiguration] | Omit = omit, + plan: Optional[checkout_configuration_create_params.Plan] | Omit = omit, + plan_id: Optional[str] | Omit = omit, redirect_url: Optional[str] | Omit = omit, - source_url: Optional[str] | Omit = omit, - three_ds_level: Optional[Literal["mandate_challenge", "frictionless"]] | Omit = omit, + three_ds_level: 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, - ) -> CheckoutConfiguration: - """ - Creates a new checkout configuration - - Required permissions: + ) -> CheckoutConfigurationCreateResponse: + """Creates a checkout configuration for a plan. - - `checkout_configuration:create` - - `plan:create` - - `access_pass:create` - - `access_pass:update` - - `checkout_configuration:basic:read` + Mode defaults to 'payment'. Args: - company_id: The unique identifier of the company to create the checkout configuration for. - Only required in setup mode. + affiliate_code: An affiliate code to apply. - allow_promo_codes: Whether the checkout should show the promo code input field and accept promo - codes. Defaults to true. + company_id: The ID of the company. - checkout_styling: Checkout styling overrides for this session. Overrides plan and company - defaults. + currency: The currency code. - currency: The available currencies on the platform + metadata: Arbitrary key-value metadata. - metadata: Custom key-value metadata to attach to the checkout configuration. + mode: Checkout mode. Defaults to 'payment'. - payment_method_configuration: The explicit payment method configuration for the checkout session. Only applies - to setup mode. If not provided, the platform or company defaults will apply. + plan: Plan attributes to create a new plan inline for this checkout configuration. + Mutually exclusive with plan_id. - redirect_url: The URL to redirect the user to after checkout is completed. + plan_id: The ID of an existing plan to attach. - source_url: The URL of the page where the checkout is being initiated from. + redirect_url: URL to redirect after checkout. - three_ds_level: The 3D Secure behavior for a plan. + three_ds_level: 3D Secure enforcement level. extra_headers: Send extra headers @@ -261,60 +100,19 @@ def create( timeout: Override the client-level default timeout for this request, in seconds """ - ... - - @required_args(["plan"], ["plan_id"], ["company_id", "mode"]) - def create( - self, - *, - plan: checkout_configuration_create_params.CreateCheckoutSessionInputModePaymentWithPlanPlan | Omit = omit, - affiliate_code: Optional[str] | Omit = omit, - allow_promo_codes: Optional[bool] | Omit = omit, - checkout_styling: Optional[ - checkout_configuration_create_params.CreateCheckoutSessionInputModePaymentWithPlanCheckoutStyling - ] - | Optional[checkout_configuration_create_params.CreateCheckoutSessionInputModePaymentWithPlanIDCheckoutStyling] - | Optional[checkout_configuration_create_params.CreateCheckoutSessionInputModeSetupCheckoutStyling] - | Omit = omit, - currency: Optional[Currency] | Omit = omit, - metadata: Optional[Dict[str, object]] | Omit = omit, - mode: Literal["payment"] | Literal["setup"] | Omit = omit, - payment_method_configuration: Optional[ - checkout_configuration_create_params.CreateCheckoutSessionInputModePaymentWithPlanPaymentMethodConfiguration - ] - | Optional[ - checkout_configuration_create_params.CreateCheckoutSessionInputModePaymentWithPlanIDPaymentMethodConfiguration - ] - | Optional[checkout_configuration_create_params.CreateCheckoutSessionInputModeSetupPaymentMethodConfiguration] - | Omit = omit, - redirect_url: Optional[str] | Omit = omit, - source_url: Optional[str] | Omit = omit, - plan_id: str | Omit = omit, - company_id: str | Omit = omit, - three_ds_level: Optional[Literal["mandate_challenge", "frictionless"]] | 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, - ) -> CheckoutConfiguration: return self._post( "/checkout_configurations", body=maybe_transform( { - "plan": plan, "affiliate_code": affiliate_code, - "allow_promo_codes": allow_promo_codes, - "checkout_styling": checkout_styling, + "company_id": company_id, "currency": currency, "metadata": metadata, "mode": mode, "payment_method_configuration": payment_method_configuration, - "redirect_url": redirect_url, - "source_url": source_url, + "plan": plan, "plan_id": plan_id, - "company_id": company_id, + "redirect_url": redirect_url, "three_ds_level": three_ds_level, }, checkout_configuration_create_params.CheckoutConfigurationCreateParams, @@ -322,7 +120,7 @@ def create( options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), - cast_to=CheckoutConfiguration, + cast_to=CheckoutConfigurationCreateResponse, ) def retrieve( @@ -335,13 +133,10 @@ def retrieve( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> CheckoutConfiguration: - """ - Retrieves the details of an existing checkout configuration. + ) -> CheckoutConfigurationRetrieveResponse: + """Retrieves a checkout configuration by ID. - Required permissions: - - - `checkout_configuration:basic:read` + No authentication required. Args: extra_headers: Send extra headers @@ -359,21 +154,19 @@ def retrieve( options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), - cast_to=CheckoutConfiguration, + cast_to=CheckoutConfigurationRetrieveResponse, ) 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, - direction: Optional[Direction] | Omit = omit, - first: Optional[int] | Omit = omit, - last: Optional[int] | Omit = omit, - plan_id: Optional[str] | Omit = omit, + after: str | Omit = omit, + created_after: int | Omit = omit, + created_before: int | Omit = omit, + direction: str | Omit = omit, + first: int | Omit = omit, + plan_id: 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, @@ -382,32 +175,22 @@ def list( timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> SyncCursorPage[CheckoutConfigurationListResponse]: """ - Returns a paginated list of checkout configurations for a company, with optional - filtering by plan and creation date. - - Required permissions: - - - `checkout_configuration:basic:read` + Returns a paginated list of checkout configurations for a company. Args: - company_id: The unique identifier of the company to list checkout configurations for. + company_id: The ID of the company to list checkout configurations for. - after: Returns the elements in the list that come after the specified cursor. + after: Cursor for forward pagination. - before: Returns the elements in the list that come before the specified cursor. + created_after: Filter to configurations created after this Unix timestamp. - created_after: Only return checkout configurations created after this timestamp. + created_before: Filter to configurations created before this Unix timestamp. - created_before: Only return checkout configurations created before this timestamp. + direction: Sort direction: asc or desc. Defaults to desc. - direction: The direction of the sort. + first: Number of results to return (forward pagination). - first: Returns the first _n_ elements from the list. - - last: Returns the last _n_ elements from the list. - - plan_id: Filter checkout configurations to only those associated with this plan - identifier. + plan_id: Filter by plan ID. extra_headers: Send extra headers @@ -429,12 +212,10 @@ def list( { "company_id": company_id, "after": after, - "before": before, "created_after": created_after, "created_before": created_before, "direction": direction, "first": first, - "last": last, "plan_id": plan_id, }, checkout_configuration_list_params.CheckoutConfigurationListParams, @@ -443,89 +224,21 @@ def list( model=CheckoutConfigurationListResponse, ) - -class AsyncCheckoutConfigurationsResource(AsyncAPIResource): - """Checkout configurations""" - - @cached_property - def with_raw_response(self) -> AsyncCheckoutConfigurationsResourceWithRawResponse: - """ - 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 AsyncCheckoutConfigurationsResourceWithRawResponse(self) - - @cached_property - def with_streaming_response(self) -> AsyncCheckoutConfigurationsResourceWithStreamingResponse: - """ - 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 AsyncCheckoutConfigurationsResourceWithStreamingResponse(self) - - @overload - async def create( + def delete( self, + id: str, *, - plan: checkout_configuration_create_params.CreateCheckoutSessionInputModePaymentWithPlanPlan, - affiliate_code: Optional[str] | Omit = omit, - allow_promo_codes: Optional[bool] | Omit = omit, - checkout_styling: Optional[ - checkout_configuration_create_params.CreateCheckoutSessionInputModePaymentWithPlanCheckoutStyling - ] - | Omit = omit, - currency: Optional[Currency] | Omit = omit, - metadata: Optional[Dict[str, object]] | Omit = omit, - mode: Literal["payment"] | Omit = omit, - payment_method_configuration: Optional[ - checkout_configuration_create_params.CreateCheckoutSessionInputModePaymentWithPlanPaymentMethodConfiguration - ] - | Omit = omit, - redirect_url: Optional[str] | Omit = omit, - source_url: 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, - ) -> CheckoutConfiguration: + ) -> None: """ - Creates a new checkout configuration - - Required permissions: - - - `checkout_configuration:create` - - `plan:create` - - `access_pass:create` - - `access_pass:update` - - `checkout_configuration:basic:read` + Deletes a checkout configuration. Args: - plan: The plan attributes to create a new plan inline for this checkout configuration. - - affiliate_code: An affiliate tracking code to attribute the checkout to a specific affiliate. - - allow_promo_codes: Whether the checkout should show the promo code input field and accept promo - codes. Defaults to true. - - checkout_styling: Checkout styling overrides for this session. Overrides plan and company - defaults. - - currency: The available currencies on the platform - - metadata: Custom key-value metadata to attach to the checkout configuration. - - payment_method_configuration: The explicit payment method configuration for the checkout session. Only applies - to setup mode. If not provided, the platform or company defaults will apply. - - redirect_url: The URL to redirect the user to after checkout is completed. - - source_url: The URL of the page where the checkout is being initiated from. - extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -534,139 +247,82 @@ async def create( timeout: Override the client-level default timeout for this request, in seconds """ - ... - - @overload - async def create( - self, - *, - plan_id: str, - affiliate_code: Optional[str] | Omit = omit, - allow_promo_codes: Optional[bool] | Omit = omit, - checkout_styling: Optional[ - checkout_configuration_create_params.CreateCheckoutSessionInputModePaymentWithPlanIDCheckoutStyling - ] - | Omit = omit, - currency: Optional[Currency] | Omit = omit, - metadata: Optional[Dict[str, object]] | Omit = omit, - mode: Literal["payment"] | Omit = omit, - payment_method_configuration: Optional[ - checkout_configuration_create_params.CreateCheckoutSessionInputModePaymentWithPlanIDPaymentMethodConfiguration - ] - | Omit = omit, - redirect_url: Optional[str] | Omit = omit, - source_url: 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, - ) -> CheckoutConfiguration: - """ - Creates a new checkout configuration - - Required permissions: - - - `checkout_configuration:create` - - `plan:create` - - `access_pass:create` - - `access_pass:update` - - `checkout_configuration:basic:read` - - Args: - plan_id: The unique identifier of an existing plan to use for this checkout - configuration. - - affiliate_code: An affiliate tracking code to attribute the checkout to a specific affiliate. - - allow_promo_codes: Whether the checkout should show the promo code input field and accept promo - codes. Defaults to true. - - checkout_styling: Checkout styling overrides for this session. Overrides plan and company - defaults. - - currency: The available currencies on the platform - - metadata: Custom key-value metadata to attach to the checkout configuration. - - payment_method_configuration: The explicit payment method configuration for the checkout session. Only applies - to setup mode. If not provided, the platform or company defaults will apply. - - redirect_url: The URL to redirect the user to after checkout is completed. + if not id: + raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") + extra_headers = {"Accept": "*/*", **(extra_headers or {})} + return self._delete( + path_template("/checkout_configurations/{id}", id=id), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=NoneType, + ) - source_url: The URL of the page where the checkout is being initiated from. - extra_headers: Send extra headers +class AsyncCheckoutConfigurationsResource(AsyncAPIResource): + @cached_property + def with_raw_response(self) -> AsyncCheckoutConfigurationsResourceWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. - extra_query: Add additional query parameters to the request + For more information, see https://www.github.com/whopio/whopsdk-python#accessing-raw-response-data-eg-headers + """ + return AsyncCheckoutConfigurationsResourceWithRawResponse(self) - extra_body: Add additional JSON properties to the request + @cached_property + def with_streaming_response(self) -> AsyncCheckoutConfigurationsResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. - timeout: Override the client-level default timeout for this request, in seconds + For more information, see https://www.github.com/whopio/whopsdk-python#with_streaming_response """ - ... + return AsyncCheckoutConfigurationsResourceWithStreamingResponse(self) - @overload async def create( self, *, - company_id: str, - mode: Literal["setup"], - allow_promo_codes: Optional[bool] | Omit = omit, - checkout_styling: Optional[ - checkout_configuration_create_params.CreateCheckoutSessionInputModeSetupCheckoutStyling - ] - | Omit = omit, - currency: Optional[Currency] | Omit = omit, - metadata: Optional[Dict[str, object]] | Omit = omit, - payment_method_configuration: Optional[ - checkout_configuration_create_params.CreateCheckoutSessionInputModeSetupPaymentMethodConfiguration - ] + affiliate_code: Optional[str] | Omit = omit, + company_id: str | Omit = omit, + currency: Optional[str] | Omit = omit, + metadata: Optional[object] | Omit = omit, + mode: Literal["payment", "setup"] | Omit = omit, + payment_method_configuration: Optional[checkout_configuration_create_params.PaymentMethodConfiguration] | Omit = omit, + plan: Optional[checkout_configuration_create_params.Plan] | Omit = omit, + plan_id: Optional[str] | Omit = omit, redirect_url: Optional[str] | Omit = omit, - source_url: Optional[str] | Omit = omit, - three_ds_level: Optional[Literal["mandate_challenge", "frictionless"]] | Omit = omit, + three_ds_level: 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, - ) -> CheckoutConfiguration: - """ - Creates a new checkout configuration - - Required permissions: + ) -> CheckoutConfigurationCreateResponse: + """Creates a checkout configuration for a plan. - - `checkout_configuration:create` - - `plan:create` - - `access_pass:create` - - `access_pass:update` - - `checkout_configuration:basic:read` + Mode defaults to 'payment'. Args: - company_id: The unique identifier of the company to create the checkout configuration for. - Only required in setup mode. + affiliate_code: An affiliate code to apply. - allow_promo_codes: Whether the checkout should show the promo code input field and accept promo - codes. Defaults to true. + company_id: The ID of the company. - checkout_styling: Checkout styling overrides for this session. Overrides plan and company - defaults. + currency: The currency code. - currency: The available currencies on the platform + metadata: Arbitrary key-value metadata. - metadata: Custom key-value metadata to attach to the checkout configuration. + mode: Checkout mode. Defaults to 'payment'. - payment_method_configuration: The explicit payment method configuration for the checkout session. Only applies - to setup mode. If not provided, the platform or company defaults will apply. + plan: Plan attributes to create a new plan inline for this checkout configuration. + Mutually exclusive with plan_id. - redirect_url: The URL to redirect the user to after checkout is completed. + plan_id: The ID of an existing plan to attach. - source_url: The URL of the page where the checkout is being initiated from. + redirect_url: URL to redirect after checkout. - three_ds_level: The 3D Secure behavior for a plan. + three_ds_level: 3D Secure enforcement level. extra_headers: Send extra headers @@ -676,60 +332,19 @@ async def create( timeout: Override the client-level default timeout for this request, in seconds """ - ... - - @required_args(["plan"], ["plan_id"], ["company_id", "mode"]) - async def create( - self, - *, - plan: checkout_configuration_create_params.CreateCheckoutSessionInputModePaymentWithPlanPlan | Omit = omit, - affiliate_code: Optional[str] | Omit = omit, - allow_promo_codes: Optional[bool] | Omit = omit, - checkout_styling: Optional[ - checkout_configuration_create_params.CreateCheckoutSessionInputModePaymentWithPlanCheckoutStyling - ] - | Optional[checkout_configuration_create_params.CreateCheckoutSessionInputModePaymentWithPlanIDCheckoutStyling] - | Optional[checkout_configuration_create_params.CreateCheckoutSessionInputModeSetupCheckoutStyling] - | Omit = omit, - currency: Optional[Currency] | Omit = omit, - metadata: Optional[Dict[str, object]] | Omit = omit, - mode: Literal["payment"] | Literal["setup"] | Omit = omit, - payment_method_configuration: Optional[ - checkout_configuration_create_params.CreateCheckoutSessionInputModePaymentWithPlanPaymentMethodConfiguration - ] - | Optional[ - checkout_configuration_create_params.CreateCheckoutSessionInputModePaymentWithPlanIDPaymentMethodConfiguration - ] - | Optional[checkout_configuration_create_params.CreateCheckoutSessionInputModeSetupPaymentMethodConfiguration] - | Omit = omit, - redirect_url: Optional[str] | Omit = omit, - source_url: Optional[str] | Omit = omit, - plan_id: str | Omit = omit, - company_id: str | Omit = omit, - three_ds_level: Optional[Literal["mandate_challenge", "frictionless"]] | 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, - ) -> CheckoutConfiguration: return await self._post( "/checkout_configurations", body=await async_maybe_transform( { - "plan": plan, "affiliate_code": affiliate_code, - "allow_promo_codes": allow_promo_codes, - "checkout_styling": checkout_styling, + "company_id": company_id, "currency": currency, "metadata": metadata, "mode": mode, "payment_method_configuration": payment_method_configuration, - "redirect_url": redirect_url, - "source_url": source_url, + "plan": plan, "plan_id": plan_id, - "company_id": company_id, + "redirect_url": redirect_url, "three_ds_level": three_ds_level, }, checkout_configuration_create_params.CheckoutConfigurationCreateParams, @@ -737,7 +352,7 @@ async def create( options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), - cast_to=CheckoutConfiguration, + cast_to=CheckoutConfigurationCreateResponse, ) async def retrieve( @@ -750,13 +365,10 @@ async def retrieve( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> CheckoutConfiguration: - """ - Retrieves the details of an existing checkout configuration. + ) -> CheckoutConfigurationRetrieveResponse: + """Retrieves a checkout configuration by ID. - Required permissions: - - - `checkout_configuration:basic:read` + No authentication required. Args: extra_headers: Send extra headers @@ -774,21 +386,19 @@ async def retrieve( options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), - cast_to=CheckoutConfiguration, + cast_to=CheckoutConfigurationRetrieveResponse, ) 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, - direction: Optional[Direction] | Omit = omit, - first: Optional[int] | Omit = omit, - last: Optional[int] | Omit = omit, - plan_id: Optional[str] | Omit = omit, + after: str | Omit = omit, + created_after: int | Omit = omit, + created_before: int | Omit = omit, + direction: str | Omit = omit, + first: int | Omit = omit, + plan_id: 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, @@ -797,32 +407,22 @@ def list( timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> AsyncPaginator[CheckoutConfigurationListResponse, AsyncCursorPage[CheckoutConfigurationListResponse]]: """ - Returns a paginated list of checkout configurations for a company, with optional - filtering by plan and creation date. - - Required permissions: - - - `checkout_configuration:basic:read` + Returns a paginated list of checkout configurations for a company. Args: - company_id: The unique identifier of the company to list checkout configurations for. - - after: Returns the elements in the list that come after the specified cursor. + company_id: The ID of the company to list checkout configurations for. - before: Returns the elements in the list that come before the specified cursor. + after: Cursor for forward pagination. - created_after: Only return checkout configurations created after this timestamp. + created_after: Filter to configurations created after this Unix timestamp. - created_before: Only return checkout configurations created before this timestamp. + created_before: Filter to configurations created before this Unix timestamp. - direction: The direction of the sort. + direction: Sort direction: asc or desc. Defaults to desc. - first: Returns the first _n_ elements from the list. + first: Number of results to return (forward pagination). - last: Returns the last _n_ elements from the list. - - plan_id: Filter checkout configurations to only those associated with this plan - identifier. + plan_id: Filter by plan ID. extra_headers: Send extra headers @@ -844,12 +444,10 @@ def list( { "company_id": company_id, "after": after, - "before": before, "created_after": created_after, "created_before": created_before, "direction": direction, "first": first, - "last": last, "plan_id": plan_id, }, checkout_configuration_list_params.CheckoutConfigurationListParams, @@ -858,6 +456,40 @@ def list( model=CheckoutConfigurationListResponse, ) + 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, + ) -> None: + """ + Deletes a checkout configuration. + + 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}") + extra_headers = {"Accept": "*/*", **(extra_headers or {})} + return await self._delete( + path_template("/checkout_configurations/{id}", id=id), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=NoneType, + ) + class CheckoutConfigurationsResourceWithRawResponse: def __init__(self, checkout_configurations: CheckoutConfigurationsResource) -> None: @@ -872,6 +504,9 @@ def __init__(self, checkout_configurations: CheckoutConfigurationsResource) -> N self.list = to_raw_response_wrapper( checkout_configurations.list, ) + self.delete = to_raw_response_wrapper( + checkout_configurations.delete, + ) class AsyncCheckoutConfigurationsResourceWithRawResponse: @@ -887,6 +522,9 @@ def __init__(self, checkout_configurations: AsyncCheckoutConfigurationsResource) self.list = async_to_raw_response_wrapper( checkout_configurations.list, ) + self.delete = async_to_raw_response_wrapper( + checkout_configurations.delete, + ) class CheckoutConfigurationsResourceWithStreamingResponse: @@ -902,6 +540,9 @@ def __init__(self, checkout_configurations: CheckoutConfigurationsResource) -> N self.list = to_streamed_response_wrapper( checkout_configurations.list, ) + self.delete = to_streamed_response_wrapper( + checkout_configurations.delete, + ) class AsyncCheckoutConfigurationsResourceWithStreamingResponse: @@ -917,3 +558,6 @@ def __init__(self, checkout_configurations: AsyncCheckoutConfigurationsResource) self.list = async_to_streamed_response_wrapper( checkout_configurations.list, ) + self.delete = async_to_streamed_response_wrapper( + checkout_configurations.delete, + ) diff --git a/src/whop_sdk/types/__init__.py b/src/whop_sdk/types/__init__.py index ce21843b..90baf49b 100644 --- a/src/whop_sdk/types/__init__.py +++ b/src/whop_sdk/types/__init__.py @@ -420,6 +420,9 @@ from .course_lesson_interaction_list_params import ( CourseLessonInteractionListParams as CourseLessonInteractionListParams, ) +from .checkout_configuration_create_response import ( + CheckoutConfigurationCreateResponse as CheckoutConfigurationCreateResponse, +) from .course_lesson_submit_assessment_params import ( CourseLessonSubmitAssessmentParams as CourseLessonSubmitAssessmentParams, ) @@ -441,6 +444,9 @@ from .identity_profile_rejected_webhook_event import ( IdentityProfileRejectedWebhookEvent as IdentityProfileRejectedWebhookEvent, ) +from .checkout_configuration_retrieve_response import ( + CheckoutConfigurationRetrieveResponse as CheckoutConfigurationRetrieveResponse, +) from .course_lesson_mark_as_completed_response import ( CourseLessonMarkAsCompletedResponse as CourseLessonMarkAsCompletedResponse, ) diff --git a/src/whop_sdk/types/checkout_configuration_create_params.py b/src/whop_sdk/types/checkout_configuration_create_params.py index de81ec08..42954cbc 100644 --- a/src/whop_sdk/types/checkout_configuration_create_params.py +++ b/src/whop_sdk/types/checkout_configuration_create_params.py @@ -2,539 +2,128 @@ from __future__ import annotations -from typing import Dict, List, Union, Iterable, Optional -from typing_extensions import Literal, Required, TypeAlias, TypedDict - -from .checkout_font import CheckoutFont -from .checkout_shape import CheckoutShape -from .shared.currency import Currency -from .shared.tax_type import TaxType -from .shared.plan_type import PlanType -from .shared.visibility import Visibility -from .payment_method_types import PaymentMethodTypes -from .shared.release_method import ReleaseMethod -from .shared.global_affiliate_status import GlobalAffiliateStatus - -__all__ = [ - "CheckoutConfigurationCreateParams", - "CreateCheckoutSessionInputModePaymentWithPlan", - "CreateCheckoutSessionInputModePaymentWithPlanPlan", - "CreateCheckoutSessionInputModePaymentWithPlanPlanCustomField", - "CreateCheckoutSessionInputModePaymentWithPlanPlanImage", - "CreateCheckoutSessionInputModePaymentWithPlanPlanPaymentMethodConfiguration", - "CreateCheckoutSessionInputModePaymentWithPlanPlanProduct", - "CreateCheckoutSessionInputModePaymentWithPlanCheckoutStyling", - "CreateCheckoutSessionInputModePaymentWithPlanPaymentMethodConfiguration", - "CreateCheckoutSessionInputModePaymentWithPlanID", - "CreateCheckoutSessionInputModePaymentWithPlanIDCheckoutStyling", - "CreateCheckoutSessionInputModePaymentWithPlanIDPaymentMethodConfiguration", - "CreateCheckoutSessionInputModeSetup", - "CreateCheckoutSessionInputModeSetupCheckoutStyling", - "CreateCheckoutSessionInputModeSetupPaymentMethodConfiguration", -] - - -class CreateCheckoutSessionInputModePaymentWithPlan(TypedDict, total=False): - plan: Required[CreateCheckoutSessionInputModePaymentWithPlanPlan] - """ - The plan attributes to create a new plan inline for this checkout configuration. - """ - - affiliate_code: Optional[str] - """An affiliate tracking code to attribute the checkout to a specific affiliate.""" - - allow_promo_codes: Optional[bool] - """ - Whether the checkout should show the promo code input field and accept promo - codes. Defaults to true. - """ - - checkout_styling: Optional[CreateCheckoutSessionInputModePaymentWithPlanCheckoutStyling] - """Checkout styling overrides for this session. - - Overrides plan and company defaults. - """ - - currency: Optional[Currency] - """The available currencies on the platform""" - - metadata: Optional[Dict[str, object]] - """Custom key-value metadata to attach to the checkout configuration.""" - - mode: Literal["payment"] - - payment_method_configuration: Optional[CreateCheckoutSessionInputModePaymentWithPlanPaymentMethodConfiguration] - """The explicit payment method configuration for the checkout session. - - Only applies to setup mode. If not provided, the platform or company defaults - will apply. - """ - - redirect_url: Optional[str] - """The URL to redirect the user to after checkout is completed.""" - - source_url: Optional[str] - """The URL of the page where the checkout is being initiated from.""" - - -class CreateCheckoutSessionInputModePaymentWithPlanPlanCustomField(TypedDict, total=False): - field_type: Required[Literal["text"]] - """The type of the custom field.""" - - name: Required[str] - """The name of the custom field.""" - - id: Optional[str] - """The ID of the custom field (if being updated)""" +from typing import Optional +from typing_extensions import Literal, TypedDict - order: Optional[int] - """The order of the field.""" +from .._types import SequenceNotStr - placeholder: Optional[str] - """The placeholder value of the field.""" +__all__ = ["CheckoutConfigurationCreateParams", "PaymentMethodConfiguration", "Plan", "PlanPaymentMethodConfiguration"] - required: Optional[bool] - """Whether or not the field is required.""" +class CheckoutConfigurationCreateParams(TypedDict, total=False): + affiliate_code: Optional[str] + """An affiliate code to apply.""" -class CreateCheckoutSessionInputModePaymentWithPlanPlanImage(TypedDict, total=False): - """An image for the plan. This will be visible on the product page to customers.""" - - id: Required[str] - """The ID of an existing file object.""" - - -class CreateCheckoutSessionInputModePaymentWithPlanPlanPaymentMethodConfiguration(TypedDict, total=False): - """The explicit payment method configuration for the plan. - - If not provided, the platform or company's defaults will apply. - """ - - disabled: Required[List[PaymentMethodTypes]] - """An array of payment method identifiers that are explicitly disabled. - - Only applies if the include_platform_defaults is true. - """ - - enabled: Required[List[PaymentMethodTypes]] - """An array of payment method identifiers that are explicitly enabled. - - This means these payment methods will be shown on checkout. Example use case is - to only enable a specific payment method like cashapp, or extending the platform - defaults with additional methods. - """ - - 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 - the documentation at docs.whop.com/payments. - """ - - -class CreateCheckoutSessionInputModePaymentWithPlanPlanProduct(TypedDict, total=False): - """Pass this object to create a new product for this plan. - - We will use the product external identifier to find or create an existing product. - """ + company_id: str + """The ID of the company.""" - external_identifier: Required[str] - """A unique ID used to find or create a product. + currency: Optional[str] + """The currency code.""" - When provided during creation, we will look for an existing product with this - external identifier — if found, it will be updated; otherwise, a new product - will be created. - """ + metadata: Optional[object] + """Arbitrary key-value metadata.""" - title: Required[str] - """The title of the product.""" + mode: Literal["payment", "setup"] + """Checkout mode. Defaults to 'payment'.""" - collect_shipping_address: Optional[bool] - """Whether or not to collect shipping information at checkout from the customer.""" + payment_method_configuration: Optional[PaymentMethodConfiguration] - custom_statement_descriptor: Optional[str] - """The custom statement descriptor for the product i.e. + plan: Optional[Plan] + """Plan attributes to create a new plan inline for this checkout configuration. - WHOP\\**SPORTS, must be between 5 and 22 characters, contain at least one letter, - and not contain any of the following characters: <, >, \\,, ', " + Mutually exclusive with plan_id. """ - description: Optional[str] - """A written description of the product.""" - - global_affiliate_percentage: Optional[float] - """The percentage of the revenue that goes to the global affiliate program.""" + plan_id: Optional[str] + """The ID of an existing plan to attach.""" - global_affiliate_status: Optional[GlobalAffiliateStatus] - """The different statuses of the global affiliate program for a product.""" + redirect_url: Optional[str] + """URL to redirect after checkout.""" - headline: Optional[str] - """The headline of the product.""" + three_ds_level: Optional[str] + """3D Secure enforcement level.""" - product_tax_code_id: Optional[str] - """The ID of the product tax code to apply to this product.""" - redirect_purchase_url: Optional[str] - """The URL to redirect the customer to after a purchase.""" +class PaymentMethodConfiguration(TypedDict, total=False): + disabled: SequenceNotStr[str] - route: Optional[str] - """The route of the product.""" + enabled: SequenceNotStr[str] - visibility: Optional[Visibility] - """Visibility of a resource""" + include_platform_defaults: bool -class CreateCheckoutSessionInputModePaymentWithPlanPlan(TypedDict, total=False): - """ - The plan attributes to create a new plan inline for this checkout configuration. - """ +class PlanPaymentMethodConfiguration(TypedDict, total=False): + disabled: SequenceNotStr[str] - company_id: Required[str] - """The company the plan should be created for.""" + enabled: SequenceNotStr[str] - currency: Required[Currency] - """The respective currency identifier for the plan.""" + include_platform_defaults: bool - 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. +class Plan(TypedDict, total=False): + """Plan attributes to create a new plan inline for this checkout configuration. - Provided as a number in dollars (e.g., 5.00 for $5.00). Must be less than the - total payment amount. Only valid for connected accounts with a parent company. + Mutually exclusive with plan_id. """ billing_period: Optional[int] - """The interval in days at which the plan charges (renewal plans). + """The number of days between recurring charges.""" - For example, 30 for monthly billing. + company_id: Optional[str] + """The company the plan should be created for. + + Defaults to the company resolved from the request. """ - custom_fields: Optional[Iterable[CreateCheckoutSessionInputModePaymentWithPlanPlanCustomField]] - """An array of custom field objects.""" + currency: Optional[str] + """The three-letter ISO currency code for the plan's pricing.""" description: Optional[str] - """The description of the plan.""" + """A text description of the plan displayed to customers.""" expiration_days: Optional[int] - """The number of days until the membership expires (for expiration-based plans). - - For example, 365 for a one-year access pass. - """ + """The number of days until the membership expires.""" force_create_new_plan: Optional[bool] - """ - Whether to force the creation of a new plan even if one with the same attributes - already exists. - """ - - image: Optional[CreateCheckoutSessionInputModePaymentWithPlanPlanImage] - """An image for the plan. This will be visible on the product page to customers.""" + """Force creating a new plan even if one with the same attributes already exists.""" initial_price: Optional[float] - """An additional amount charged upon first purchase. + """The amount charged on the first purchase, in the plan's currency.""" - Provided as a number in dollars (e.g., 10.00 for $10.00). - """ + metadata: Optional[object] + """Custom key-value metadata to store on the plan.""" - internal_notes: Optional[str] - """A personal description or notes section for the business.""" + override_tax_type: Optional[str] + """Override the default tax classification for this plan.""" - override_tax_type: Optional[TaxType] - """ - Whether or not the tax is included in a plan's price (or if it hasn't been set - up) - """ + payment_method_configuration: Optional[PlanPaymentMethodConfiguration] - payment_method_configuration: Optional[CreateCheckoutSessionInputModePaymentWithPlanPlanPaymentMethodConfiguration] - """The explicit payment method configuration for the plan. - - If not provided, the platform or company's defaults will apply. - """ - - plan_type: Optional[PlanType] - """The type of plan that can be attached to a product""" - - product: Optional[CreateCheckoutSessionInputModePaymentWithPlanPlanProduct] - """Pass this object to create a new product for this plan. - - We will use the product external identifier to find or create an existing - product. - """ + plan_type: Optional[str] + """The billing model for the plan, e.g. 'one_time' or 'renewal'.""" product_id: Optional[str] - """The product the plan is related to. Either this or product is required.""" + """The ID of an existing product (access pass) to attach the plan to.""" - release_method: Optional[ReleaseMethod] - """The methods of how a plan can be released.""" + release_method: Optional[str] + """How the plan is sold, e.g. 'buy_now'.""" renewal_price: Optional[float] - """The amount the customer is charged every billing period. - - Provided as a number in dollars (e.g., 9.99 for $9.99/period). """ - - split_pay_required_payments: Optional[int] - """The number of payments required before pausing the subscription.""" + The amount charged each billing period for recurring plans, in the plan's + currency. + """ stock: Optional[int] - """The number of units available for purchase. - - If not provided, stock is unlimited. - """ + """The maximum number of units available for purchase.""" title: Optional[str] - """The title of the plan. This will be visible on the product page to customers.""" + """The display name of the plan shown to customers.""" trial_period_days: Optional[int] - """The number of free trial days added before a renewal plan.""" - - visibility: Optional[Visibility] - """Visibility of a resource""" - - -class CreateCheckoutSessionInputModePaymentWithPlanCheckoutStyling(TypedDict, total=False): - """Checkout styling overrides for this session. - - 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.""" - - button_color: Optional[str] - """A hex color code for the button color (e.g. #FF5733).""" - - font_family: Optional[CheckoutFont] - """The different font families available for checkout pages.""" - - -class CreateCheckoutSessionInputModePaymentWithPlanPaymentMethodConfiguration(TypedDict, total=False): - """The explicit payment method configuration for the checkout session. - - Only applies to setup mode. If not provided, the platform or company defaults will apply. - """ - - disabled: Required[List[PaymentMethodTypes]] - """An array of payment method identifiers that are explicitly disabled. - - Only applies if the include_platform_defaults is true. - """ - - enabled: Required[List[PaymentMethodTypes]] - """An array of payment method identifiers that are explicitly enabled. - - This means these payment methods will be shown on checkout. Example use case is - to only enable a specific payment method like cashapp, or extending the platform - defaults with additional methods. - """ - - 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 - the documentation at docs.whop.com/payments. - """ - - -class CreateCheckoutSessionInputModePaymentWithPlanID(TypedDict, total=False): - plan_id: Required[str] - """ - The unique identifier of an existing plan to use for this checkout - configuration. - """ - - affiliate_code: Optional[str] - """An affiliate tracking code to attribute the checkout to a specific affiliate.""" - - allow_promo_codes: Optional[bool] - """ - Whether the checkout should show the promo code input field and accept promo - codes. Defaults to true. - """ - - checkout_styling: Optional[CreateCheckoutSessionInputModePaymentWithPlanIDCheckoutStyling] - """Checkout styling overrides for this session. - - Overrides plan and company defaults. - """ - - currency: Optional[Currency] - """The available currencies on the platform""" - - metadata: Optional[Dict[str, object]] - """Custom key-value metadata to attach to the checkout configuration.""" - - mode: Literal["payment"] - - payment_method_configuration: Optional[CreateCheckoutSessionInputModePaymentWithPlanIDPaymentMethodConfiguration] - """The explicit payment method configuration for the checkout session. - - Only applies to setup mode. If not provided, the platform or company defaults - will apply. - """ - - redirect_url: Optional[str] - """The URL to redirect the user to after checkout is completed.""" - - source_url: Optional[str] - """The URL of the page where the checkout is being initiated from.""" - - -class CreateCheckoutSessionInputModePaymentWithPlanIDCheckoutStyling(TypedDict, total=False): - """Checkout styling overrides for this session. - - 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.""" - - button_color: Optional[str] - """A hex color code for the button color (e.g. #FF5733).""" - - font_family: Optional[CheckoutFont] - """The different font families available for checkout pages.""" - - -class CreateCheckoutSessionInputModePaymentWithPlanIDPaymentMethodConfiguration(TypedDict, total=False): - """The explicit payment method configuration for the checkout session. - - Only applies to setup mode. If not provided, the platform or company defaults will apply. - """ - - disabled: Required[List[PaymentMethodTypes]] - """An array of payment method identifiers that are explicitly disabled. - - Only applies if the include_platform_defaults is true. - """ - - enabled: Required[List[PaymentMethodTypes]] - """An array of payment method identifiers that are explicitly enabled. - - This means these payment methods will be shown on checkout. Example use case is - to only enable a specific payment method like cashapp, or extending the platform - defaults with additional methods. - """ - - 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 - the documentation at docs.whop.com/payments. - """ - - -class CreateCheckoutSessionInputModeSetup(TypedDict, total=False): - company_id: Required[str] - """The unique identifier of the company to create the checkout configuration for. - - Only required in setup mode. - """ - - mode: Required[Literal["setup"]] - - allow_promo_codes: Optional[bool] - """ - Whether the checkout should show the promo code input field and accept promo - codes. Defaults to true. - """ - - checkout_styling: Optional[CreateCheckoutSessionInputModeSetupCheckoutStyling] - """Checkout styling overrides for this session. - - Overrides plan and company defaults. - """ - - currency: Optional[Currency] - """The available currencies on the platform""" - - metadata: Optional[Dict[str, object]] - """Custom key-value metadata to attach to the checkout configuration.""" - - payment_method_configuration: Optional[CreateCheckoutSessionInputModeSetupPaymentMethodConfiguration] - """The explicit payment method configuration for the checkout session. - - Only applies to setup mode. If not provided, the platform or company defaults - will apply. - """ - - redirect_url: Optional[str] - """The URL to redirect the user to after checkout is completed.""" - - source_url: Optional[str] - """The URL of the page where the checkout is being initiated from.""" - - three_ds_level: Optional[Literal["mandate_challenge", "frictionless"]] - """The 3D Secure behavior for a plan.""" - - -class CreateCheckoutSessionInputModeSetupCheckoutStyling(TypedDict, total=False): - """Checkout styling overrides for this session. - - 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.""" - - button_color: Optional[str] - """A hex color code for the button color (e.g. #FF5733).""" - - font_family: Optional[CheckoutFont] - """The different font families available for checkout pages.""" - - -class CreateCheckoutSessionInputModeSetupPaymentMethodConfiguration(TypedDict, total=False): - """The explicit payment method configuration for the checkout session. - - Only applies to setup mode. If not provided, the platform or company defaults will apply. - """ - - disabled: Required[List[PaymentMethodTypes]] - """An array of payment method identifiers that are explicitly disabled. - - Only applies if the include_platform_defaults is true. - """ - - enabled: Required[List[PaymentMethodTypes]] - """An array of payment method identifiers that are explicitly enabled. - - This means these payment methods will be shown on checkout. Example use case is - to only enable a specific payment method like cashapp, or extending the platform - defaults with additional methods. - """ - - 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 - the documentation at docs.whop.com/payments. - """ + """The number of free trial days before the first charge.""" + unlimited_stock: Optional[bool] + """Whether the plan has unlimited stock.""" -CheckoutConfigurationCreateParams: TypeAlias = Union[ - CreateCheckoutSessionInputModePaymentWithPlan, - CreateCheckoutSessionInputModePaymentWithPlanID, - CreateCheckoutSessionInputModeSetup, -] + visibility: Optional[str] + """Whether the plan is visible to customers or hidden.""" diff --git a/src/whop_sdk/types/checkout_configuration_create_response.py b/src/whop_sdk/types/checkout_configuration_create_response.py new file mode 100644 index 00000000..0e2c7e79 --- /dev/null +++ b/src/whop_sdk/types/checkout_configuration_create_response.py @@ -0,0 +1,52 @@ +# 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 + +__all__ = ["CheckoutConfigurationCreateResponse"] + + +class CheckoutConfigurationCreateResponse(BaseModel): + id: str + """The unique identifier of the checkout configuration.""" + + company_id: str + """The ID of the company that owns this checkout configuration.""" + + created_at: int + """Unix timestamp when the checkout configuration was created.""" + + mode: Literal["payment", "setup"] + """The checkout mode.""" + + updated_at: int + """Unix timestamp when the checkout configuration was last updated.""" + + affiliate_code: Optional[str] = None + """The affiliate code applied at checkout.""" + + currency: Optional[str] = None + """The currency for this checkout configuration.""" + + metadata: Optional[object] = None + """Arbitrary key-value metadata. + + Only returned when caller has checkout_configuration:basic:read scope. + """ + + payment_method_configuration: Optional[object] = None + """Payment method configuration.""" + + plan: Optional[object] = None + """The plan associated with this checkout configuration.""" + + purchase_url: Optional[str] = None + """The URL for the checkout page.""" + + redirect_url: Optional[str] = None + """The URL to redirect after checkout.""" + + three_ds_level: Optional[str] = None + """The 3D Secure enforcement level.""" diff --git a/src/whop_sdk/types/checkout_configuration_list_params.py b/src/whop_sdk/types/checkout_configuration_list_params.py index b2770334..bd593714 100644 --- a/src/whop_sdk/types/checkout_configuration_list_params.py +++ b/src/whop_sdk/types/checkout_configuration_list_params.py @@ -2,43 +2,29 @@ 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 +from typing_extensions import Required, TypedDict __all__ = ["CheckoutConfigurationListParams"] class CheckoutConfigurationListParams(TypedDict, total=False): company_id: Required[str] - """The unique identifier of the company to list checkout configurations 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.""" + """The ID of the company to list checkout configurations for.""" - created_after: Annotated[Union[str, datetime, None], PropertyInfo(format="iso8601")] - """Only return checkout configurations created after this timestamp.""" + after: str + """Cursor for forward pagination.""" - created_before: Annotated[Union[str, datetime, None], PropertyInfo(format="iso8601")] - """Only return checkout configurations created before this timestamp.""" + created_after: int + """Filter to configurations created after this Unix timestamp.""" - direction: Optional[Direction] - """The direction of the sort.""" + created_before: int + """Filter to configurations created before this Unix timestamp.""" - first: Optional[int] - """Returns the first _n_ elements from the list.""" + direction: str + """Sort direction: asc or desc. Defaults to desc.""" - last: Optional[int] - """Returns the last _n_ elements from the list.""" + first: int + """Number of results to return (forward pagination).""" - plan_id: Optional[str] - """ - Filter checkout configurations to only those associated with this plan - identifier. - """ + plan_id: str + """Filter by plan ID.""" diff --git a/src/whop_sdk/types/checkout_configuration_list_response.py b/src/whop_sdk/types/checkout_configuration_list_response.py index be80192b..5c1348c2 100644 --- a/src/whop_sdk/types/checkout_configuration_list_response.py +++ b/src/whop_sdk/types/checkout_configuration_list_response.py @@ -1,163 +1,52 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -from typing import Dict, List, Optional +from typing import Optional from typing_extensions import Literal from .._models import BaseModel -from .checkout_modes import CheckoutModes -from .shared.currency import Currency -from .shared.plan_type import PlanType -from .shared.visibility import Visibility -from .payment_method_types import PaymentMethodTypes -from .shared.release_method import ReleaseMethod -__all__ = ["CheckoutConfigurationListResponse", "PaymentMethodConfiguration", "Plan"] - - -class PaymentMethodConfiguration(BaseModel): - """The explicit payment method configuration for the session, if any. - - This currently only works in 'setup' mode. Use the plan's payment_method_configuration for payment method. - """ - - disabled: List[PaymentMethodTypes] - """An array of payment method identifiers that are explicitly disabled. - - Only applies if the include_platform_defaults is true. - """ - - enabled: List[PaymentMethodTypes] - """An array of payment method identifiers that are explicitly enabled. - - This means these payment methods will be shown on checkout. Example use case is - to only enable a specific payment method like cashapp, or extending the platform - defaults with additional methods. - """ - - include_platform_defaults: 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 - the documentation at docs.whop.com/payments. - """ - - -class Plan(BaseModel): - """The plan to use for the checkout configuration""" - - 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. - - Null for one-time plans. For example, 30 for monthly or 365 for annual billing. - """ - - currency: Currency - """The currency used for all prices on this plan (e.g., 'usd', 'eur'). - - All monetary amounts on the plan are denominated in this currency. - """ - - expiration_days: Optional[int] = None - """The number of days until the membership expires (for expiration-based plans). - - For example, 365 for a one-year access pass. - """ - - initial_price: float - """The initial purchase price in the plan's base_currency (e.g., 49.99 for $49.99). - - For one-time plans, this is the full price. For renewal plans, this is charged - on top of the first renewal_price. - """ - - plan_type: PlanType - """ - The billing model for this plan: 'renewal' for recurring subscriptions or - 'one_time' for single payments. - """ - - release_method: ReleaseMethod - """ - The method used to sell this plan: 'buy_now' for immediate purchase or - 'waitlist' for waitlist-based access. - """ - - renewal_price: float - """ - The recurring price charged every billing_period in the plan's base_currency - (e.g., 9.99 for $9.99/period). Zero for one-time plans. - """ - - three_ds_level: Optional[Literal["mandate_challenge", "frictionless"]] = None - """The 3D Secure behavior for a plan.""" - - trial_period_days: Optional[int] = None - """The number of free trial days before the first charge on a renewal plan. - - Null if no trial is configured or the current user has already used a trial for - this plan. - """ - - visibility: Visibility - """Controls whether the plan is visible to customers. - - When set to 'hidden', the plan is only accessible via direct link. - """ +__all__ = ["CheckoutConfigurationListResponse"] class CheckoutConfigurationListResponse(BaseModel): - """ - A checkout configuration is a reusable configuration for a checkout, including the plan, affiliate, and custom metadata. Payments and memberships created from a checkout session inherit its metadata. - """ - id: str - """The unique identifier for the checkout session.""" - - affiliate_code: Optional[str] = None - """The affiliate code to use for the checkout configuration""" + """The unique identifier of the checkout configuration.""" - allow_promo_codes: bool - """Whether the checkout configuration allows promo codes. + company_id: str + """The ID of the company that owns this checkout configuration.""" - When false, the promo code input is hidden and promo codes are rejected. - """ + created_at: int + """Unix timestamp when the checkout configuration was created.""" - company_id: str - """The ID of the company to use for the checkout configuration""" + mode: Literal["payment", "setup"] + """The checkout mode.""" - currency: Optional[Currency] = None - """The available currencies on the platform""" + updated_at: int + """Unix timestamp when the checkout configuration was last updated.""" - metadata: Optional[Dict[str, object]] = None - """The metadata to use for the checkout configuration""" + affiliate_code: Optional[str] = None + """The affiliate code applied at checkout.""" - mode: CheckoutModes - """The mode of the checkout session.""" + currency: Optional[str] = None + """The currency for this checkout configuration.""" - payment_method_configuration: Optional[PaymentMethodConfiguration] = None - """The explicit payment method configuration for the session, if any. + metadata: Optional[object] = None + """Arbitrary key-value metadata. - This currently only works in 'setup' mode. Use the plan's - payment_method_configuration for payment method. + Only returned when caller has checkout_configuration:basic:read scope. """ - plan: Optional[Plan] = None - """The plan to use for the checkout configuration""" + payment_method_configuration: Optional[object] = None + """Payment method configuration.""" - purchase_url: str - """A URL you can send to customers to complete a checkout. + plan: Optional[object] = None + """The plan associated with this checkout configuration.""" - It looks like `/checkout/plan_xxxx?session={id}` - """ + purchase_url: Optional[str] = None + """The URL for the checkout page.""" redirect_url: Optional[str] = None - """The URL to redirect the user to after the checkout configuration is created""" + """The URL to redirect after checkout.""" + + three_ds_level: Optional[str] = None + """The 3D Secure enforcement level.""" diff --git a/src/whop_sdk/types/checkout_configuration_retrieve_response.py b/src/whop_sdk/types/checkout_configuration_retrieve_response.py new file mode 100644 index 00000000..cbaf3704 --- /dev/null +++ b/src/whop_sdk/types/checkout_configuration_retrieve_response.py @@ -0,0 +1,52 @@ +# 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 + +__all__ = ["CheckoutConfigurationRetrieveResponse"] + + +class CheckoutConfigurationRetrieveResponse(BaseModel): + id: str + """The unique identifier of the checkout configuration.""" + + company_id: str + """The ID of the company that owns this checkout configuration.""" + + created_at: int + """Unix timestamp when the checkout configuration was created.""" + + mode: Literal["payment", "setup"] + """The checkout mode.""" + + updated_at: int + """Unix timestamp when the checkout configuration was last updated.""" + + affiliate_code: Optional[str] = None + """The affiliate code applied at checkout.""" + + currency: Optional[str] = None + """The currency for this checkout configuration.""" + + metadata: Optional[object] = None + """Arbitrary key-value metadata. + + Only returned when caller has checkout_configuration:basic:read scope. + """ + + payment_method_configuration: Optional[object] = None + """Payment method configuration.""" + + plan: Optional[object] = None + """The plan associated with this checkout configuration.""" + + purchase_url: Optional[str] = None + """The URL for the checkout page.""" + + redirect_url: Optional[str] = None + """The URL to redirect after checkout.""" + + three_ds_level: Optional[str] = None + """The 3D Secure enforcement level.""" diff --git a/src/whop_sdk/types/shared/checkout_configuration.py b/src/whop_sdk/types/shared/checkout_configuration.py index d21bcd8f..8784af7e 100644 --- a/src/whop_sdk/types/shared/checkout_configuration.py +++ b/src/whop_sdk/types/shared/checkout_configuration.py @@ -125,12 +125,6 @@ class CheckoutConfiguration(BaseModel): affiliate_code: Optional[str] = None """The affiliate code to use for the checkout configuration""" - allow_promo_codes: bool - """Whether the checkout configuration allows promo codes. - - When false, the promo code input is hidden and promo codes are rejected. - """ - company_id: str """The ID of the company to use for the checkout configuration""" diff --git a/src/whop_sdk/types/shared_params/__init__.py b/src/whop_sdk/types/shared_params/__init__.py index 4ae01cc7..e2000124 100644 --- a/src/whop_sdk/types/shared_params/__init__.py +++ b/src/whop_sdk/types/shared_params/__init__.py @@ -1,7 +1,6 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from .currency import Currency as Currency -from .tax_type import TaxType as TaxType from .direction import Direction as Direction from .plan_type import PlanType as PlanType from .promo_type import PromoType as PromoType diff --git a/src/whop_sdk/types/shared_params/tax_type.py b/src/whop_sdk/types/shared_params/tax_type.py deleted file mode 100644 index bae01a1a..00000000 --- a/src/whop_sdk/types/shared_params/tax_type.py +++ /dev/null @@ -1,9 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from __future__ import annotations - -from typing_extensions import Literal, TypeAlias - -__all__ = ["TaxType"] - -TaxType: TypeAlias = Literal["inclusive", "exclusive", "unspecified"] diff --git a/tests/api_resources/test_checkout_configurations.py b/tests/api_resources/test_checkout_configurations.py index 76ccdb32..acdcd76b 100644 --- a/tests/api_resources/test_checkout_configurations.py +++ b/tests/api_resources/test_checkout_configurations.py @@ -11,10 +11,10 @@ from tests.utils import assert_matches_type from whop_sdk.types import ( CheckoutConfigurationListResponse, + CheckoutConfigurationCreateResponse, + CheckoutConfigurationRetrieveResponse, ) -from whop_sdk._utils import parse_datetime from whop_sdk.pagination import SyncCursorPage, AsyncCursorPage -from whop_sdk.types.shared import CheckoutConfiguration base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") @@ -24,244 +24,74 @@ class TestCheckoutConfigurations: @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize - def test_method_create_overload_1(self, client: Whop) -> None: - checkout_configuration = client.checkout_configurations.create( - plan={ - "company_id": "biz_xxxxxxxxxxxxxx", - "currency": "usd", - }, - ) - assert_matches_type(CheckoutConfiguration, checkout_configuration, path=["response"]) + def test_method_create(self, client: Whop) -> None: + checkout_configuration = client.checkout_configurations.create() + assert_matches_type(CheckoutConfigurationCreateResponse, checkout_configuration, path=["response"]) @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize - def test_method_create_with_all_params_overload_1(self, client: Whop) -> None: + def test_method_create_with_all_params(self, client: Whop) -> None: checkout_configuration = client.checkout_configurations.create( + affiliate_code="affiliate_code", + company_id="company_id", + currency="currency", + metadata={}, + mode="payment", + payment_method_configuration={ + "disabled": ["string"], + "enabled": ["string"], + "include_platform_defaults": True, + }, plan={ - "company_id": "biz_xxxxxxxxxxxxxx", - "currency": "usd", - "adaptive_pricing_enabled": True, - "application_fee_amount": 6.9, - "billing_period": 42, - "custom_fields": [ - { - "field_type": "text", - "name": "name", - "id": "id", - "order": 42, - "placeholder": "placeholder", - "required": True, - } - ], + "billing_period": 0, + "company_id": "company_id", + "currency": "currency", "description": "description", - "expiration_days": 42, + "expiration_days": 0, "force_create_new_plan": True, - "image": {"id": "id"}, - "initial_price": 6.9, - "internal_notes": "internal_notes", - "override_tax_type": "inclusive", + "initial_price": 0, + "metadata": {}, + "override_tax_type": "override_tax_type", "payment_method_configuration": { - "disabled": ["acss_debit"], - "enabled": ["acss_debit"], + "disabled": ["string"], + "enabled": ["string"], "include_platform_defaults": True, }, - "plan_type": "renewal", - "product": { - "external_identifier": "external_identifier", - "title": "title", - "collect_shipping_address": True, - "custom_statement_descriptor": "custom_statement_descriptor", - "description": "description", - "global_affiliate_percentage": 6.9, - "global_affiliate_status": "enabled", - "headline": "headline", - "product_tax_code_id": "ptc_xxxxxxxxxxxxxx", - "redirect_purchase_url": "redirect_purchase_url", - "route": "route", - "visibility": "visible", - }, - "product_id": "prod_xxxxxxxxxxxxx", - "release_method": "buy_now", - "renewal_price": 6.9, - "split_pay_required_payments": 42, - "stock": 42, + "plan_type": "plan_type", + "product_id": "product_id", + "release_method": "release_method", + "renewal_price": 0, + "stock": 0, "title": "title", - "trial_period_days": 42, - "visibility": "visible", - }, - affiliate_code="affiliate_code", - allow_promo_codes=True, - checkout_styling={ - "background_color": "background_color", - "border_style": "rounded", - "button_color": "button_color", - "font_family": "system", - }, - currency="usd", - metadata={"foo": "bar"}, - mode="payment", - payment_method_configuration={ - "disabled": ["acss_debit"], - "enabled": ["acss_debit"], - "include_platform_defaults": True, - }, - redirect_url="redirect_url", - source_url="source_url", - ) - assert_matches_type(CheckoutConfiguration, checkout_configuration, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - def test_raw_response_create_overload_1(self, client: Whop) -> None: - response = client.checkout_configurations.with_raw_response.create( - plan={ - "company_id": "biz_xxxxxxxxxxxxxx", - "currency": "usd", - }, - ) - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - checkout_configuration = response.parse() - assert_matches_type(CheckoutConfiguration, checkout_configuration, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - def test_streaming_response_create_overload_1(self, client: Whop) -> None: - with client.checkout_configurations.with_streaming_response.create( - plan={ - "company_id": "biz_xxxxxxxxxxxxxx", - "currency": "usd", - }, - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - checkout_configuration = response.parse() - assert_matches_type(CheckoutConfiguration, checkout_configuration, path=["response"]) - - assert cast(Any, response.is_closed) is True - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - def test_method_create_overload_2(self, client: Whop) -> None: - checkout_configuration = client.checkout_configurations.create( - plan_id="plan_xxxxxxxxxxxxx", - ) - assert_matches_type(CheckoutConfiguration, checkout_configuration, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - def test_method_create_with_all_params_overload_2(self, client: Whop) -> None: - checkout_configuration = client.checkout_configurations.create( - plan_id="plan_xxxxxxxxxxxxx", - affiliate_code="affiliate_code", - allow_promo_codes=True, - checkout_styling={ - "background_color": "background_color", - "border_style": "rounded", - "button_color": "button_color", - "font_family": "system", - }, - currency="usd", - metadata={"foo": "bar"}, - mode="payment", - payment_method_configuration={ - "disabled": ["acss_debit"], - "enabled": ["acss_debit"], - "include_platform_defaults": True, - }, - redirect_url="redirect_url", - source_url="source_url", - ) - assert_matches_type(CheckoutConfiguration, checkout_configuration, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - def test_raw_response_create_overload_2(self, client: Whop) -> None: - response = client.checkout_configurations.with_raw_response.create( - plan_id="plan_xxxxxxxxxxxxx", - ) - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - checkout_configuration = response.parse() - assert_matches_type(CheckoutConfiguration, checkout_configuration, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - def test_streaming_response_create_overload_2(self, client: Whop) -> None: - with client.checkout_configurations.with_streaming_response.create( - plan_id="plan_xxxxxxxxxxxxx", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - checkout_configuration = response.parse() - assert_matches_type(CheckoutConfiguration, checkout_configuration, path=["response"]) - - assert cast(Any, response.is_closed) is True - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - def test_method_create_overload_3(self, client: Whop) -> None: - checkout_configuration = client.checkout_configurations.create( - company_id="biz_xxxxxxxxxxxxxx", - mode="setup", - ) - assert_matches_type(CheckoutConfiguration, checkout_configuration, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - def test_method_create_with_all_params_overload_3(self, client: Whop) -> None: - checkout_configuration = client.checkout_configurations.create( - company_id="biz_xxxxxxxxxxxxxx", - mode="setup", - allow_promo_codes=True, - checkout_styling={ - "background_color": "background_color", - "border_style": "rounded", - "button_color": "button_color", - "font_family": "system", - }, - currency="usd", - metadata={"foo": "bar"}, - payment_method_configuration={ - "disabled": ["acss_debit"], - "enabled": ["acss_debit"], - "include_platform_defaults": True, + "trial_period_days": 0, + "unlimited_stock": True, + "visibility": "visibility", }, + plan_id="plan_id", redirect_url="redirect_url", - source_url="source_url", - three_ds_level="mandate_challenge", + three_ds_level="three_ds_level", ) - assert_matches_type(CheckoutConfiguration, checkout_configuration, path=["response"]) + assert_matches_type(CheckoutConfigurationCreateResponse, checkout_configuration, path=["response"]) @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize - def test_raw_response_create_overload_3(self, client: Whop) -> None: - response = client.checkout_configurations.with_raw_response.create( - company_id="biz_xxxxxxxxxxxxxx", - mode="setup", - ) + def test_raw_response_create(self, client: Whop) -> None: + response = client.checkout_configurations.with_raw_response.create() assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" checkout_configuration = response.parse() - assert_matches_type(CheckoutConfiguration, checkout_configuration, path=["response"]) + assert_matches_type(CheckoutConfigurationCreateResponse, checkout_configuration, path=["response"]) @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize - def test_streaming_response_create_overload_3(self, client: Whop) -> None: - with client.checkout_configurations.with_streaming_response.create( - company_id="biz_xxxxxxxxxxxxxx", - mode="setup", - ) as response: + def test_streaming_response_create(self, client: Whop) -> None: + with client.checkout_configurations.with_streaming_response.create() as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" checkout_configuration = response.parse() - assert_matches_type(CheckoutConfiguration, checkout_configuration, path=["response"]) + assert_matches_type(CheckoutConfigurationCreateResponse, checkout_configuration, path=["response"]) assert cast(Any, response.is_closed) is True @@ -269,33 +99,33 @@ def test_streaming_response_create_overload_3(self, client: Whop) -> None: @parametrize def test_method_retrieve(self, client: Whop) -> None: checkout_configuration = client.checkout_configurations.retrieve( - "ch_xxxxxxxxxxxxxxx", + "id", ) - assert_matches_type(CheckoutConfiguration, checkout_configuration, path=["response"]) + assert_matches_type(CheckoutConfigurationRetrieveResponse, checkout_configuration, path=["response"]) @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_raw_response_retrieve(self, client: Whop) -> None: response = client.checkout_configurations.with_raw_response.retrieve( - "ch_xxxxxxxxxxxxxxx", + "id", ) assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" checkout_configuration = response.parse() - assert_matches_type(CheckoutConfiguration, checkout_configuration, path=["response"]) + assert_matches_type(CheckoutConfigurationRetrieveResponse, checkout_configuration, path=["response"]) @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_streaming_response_retrieve(self, client: Whop) -> None: with client.checkout_configurations.with_streaming_response.retrieve( - "ch_xxxxxxxxxxxxxxx", + "id", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" checkout_configuration = response.parse() - assert_matches_type(CheckoutConfiguration, checkout_configuration, path=["response"]) + assert_matches_type(CheckoutConfigurationRetrieveResponse, checkout_configuration, path=["response"]) assert cast(Any, response.is_closed) is True @@ -311,7 +141,7 @@ def test_path_params_retrieve(self, client: Whop) -> None: @parametrize def test_method_list(self, client: Whop) -> None: checkout_configuration = client.checkout_configurations.list( - company_id="biz_xxxxxxxxxxxxxx", + company_id="company_id", ) assert_matches_type( SyncCursorPage[CheckoutConfigurationListResponse], checkout_configuration, path=["response"] @@ -321,15 +151,13 @@ def test_method_list(self, client: Whop) -> None: @parametrize def test_method_list_with_all_params(self, client: Whop) -> None: checkout_configuration = client.checkout_configurations.list( - company_id="biz_xxxxxxxxxxxxxx", + company_id="company_id", after="after", - before="before", - created_after=parse_datetime("2023-12-01T05:00:00.401Z"), - created_before=parse_datetime("2023-12-01T05:00:00.401Z"), - direction="asc", - first=42, - last=42, - plan_id="plan_xxxxxxxxxxxxx", + created_after=0, + created_before=0, + direction="direction", + first=0, + plan_id="plan_id", ) assert_matches_type( SyncCursorPage[CheckoutConfigurationListResponse], checkout_configuration, path=["response"] @@ -339,7 +167,7 @@ def test_method_list_with_all_params(self, client: Whop) -> None: @parametrize def test_raw_response_list(self, client: Whop) -> None: response = client.checkout_configurations.with_raw_response.list( - company_id="biz_xxxxxxxxxxxxxx", + company_id="company_id", ) assert response.is_closed is True @@ -353,7 +181,7 @@ def test_raw_response_list(self, client: Whop) -> None: @parametrize def test_streaming_response_list(self, client: Whop) -> None: with client.checkout_configurations.with_streaming_response.list( - company_id="biz_xxxxxxxxxxxxxx", + company_id="company_id", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -365,252 +193,124 @@ def test_streaming_response_list(self, client: Whop) -> None: assert cast(Any, response.is_closed) is True - -class TestAsyncCheckoutConfigurations: - 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_overload_1(self, async_client: AsyncWhop) -> None: - checkout_configuration = await async_client.checkout_configurations.create( - plan={ - "company_id": "biz_xxxxxxxxxxxxxx", - "currency": "usd", - }, - ) - assert_matches_type(CheckoutConfiguration, checkout_configuration, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - async def test_method_create_with_all_params_overload_1(self, async_client: AsyncWhop) -> None: - checkout_configuration = await async_client.checkout_configurations.create( - plan={ - "company_id": "biz_xxxxxxxxxxxxxx", - "currency": "usd", - "adaptive_pricing_enabled": True, - "application_fee_amount": 6.9, - "billing_period": 42, - "custom_fields": [ - { - "field_type": "text", - "name": "name", - "id": "id", - "order": 42, - "placeholder": "placeholder", - "required": True, - } - ], - "description": "description", - "expiration_days": 42, - "force_create_new_plan": True, - "image": {"id": "id"}, - "initial_price": 6.9, - "internal_notes": "internal_notes", - "override_tax_type": "inclusive", - "payment_method_configuration": { - "disabled": ["acss_debit"], - "enabled": ["acss_debit"], - "include_platform_defaults": True, - }, - "plan_type": "renewal", - "product": { - "external_identifier": "external_identifier", - "title": "title", - "collect_shipping_address": True, - "custom_statement_descriptor": "custom_statement_descriptor", - "description": "description", - "global_affiliate_percentage": 6.9, - "global_affiliate_status": "enabled", - "headline": "headline", - "product_tax_code_id": "ptc_xxxxxxxxxxxxxx", - "redirect_purchase_url": "redirect_purchase_url", - "route": "route", - "visibility": "visible", - }, - "product_id": "prod_xxxxxxxxxxxxx", - "release_method": "buy_now", - "renewal_price": 6.9, - "split_pay_required_payments": 42, - "stock": 42, - "title": "title", - "trial_period_days": 42, - "visibility": "visible", - }, - affiliate_code="affiliate_code", - allow_promo_codes=True, - checkout_styling={ - "background_color": "background_color", - "border_style": "rounded", - "button_color": "button_color", - "font_family": "system", - }, - currency="usd", - metadata={"foo": "bar"}, - mode="payment", - payment_method_configuration={ - "disabled": ["acss_debit"], - "enabled": ["acss_debit"], - "include_platform_defaults": True, - }, - redirect_url="redirect_url", - source_url="source_url", + def test_method_delete(self, client: Whop) -> None: + checkout_configuration = client.checkout_configurations.delete( + "id", ) - assert_matches_type(CheckoutConfiguration, checkout_configuration, path=["response"]) + assert checkout_configuration is None @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize - async def test_raw_response_create_overload_1(self, async_client: AsyncWhop) -> None: - response = await async_client.checkout_configurations.with_raw_response.create( - plan={ - "company_id": "biz_xxxxxxxxxxxxxx", - "currency": "usd", - }, + def test_raw_response_delete(self, client: Whop) -> None: + response = client.checkout_configurations.with_raw_response.delete( + "id", ) assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" - checkout_configuration = await response.parse() - assert_matches_type(CheckoutConfiguration, checkout_configuration, path=["response"]) + checkout_configuration = response.parse() + assert checkout_configuration is None @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize - async def test_streaming_response_create_overload_1(self, async_client: AsyncWhop) -> None: - async with async_client.checkout_configurations.with_streaming_response.create( - plan={ - "company_id": "biz_xxxxxxxxxxxxxx", - "currency": "usd", - }, + def test_streaming_response_delete(self, client: Whop) -> None: + with client.checkout_configurations.with_streaming_response.delete( + "id", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" - checkout_configuration = await response.parse() - assert_matches_type(CheckoutConfiguration, checkout_configuration, path=["response"]) + checkout_configuration = response.parse() + assert checkout_configuration is None assert cast(Any, response.is_closed) is True @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize - async def test_method_create_overload_2(self, async_client: AsyncWhop) -> None: - checkout_configuration = await async_client.checkout_configurations.create( - plan_id="plan_xxxxxxxxxxxxx", - ) - assert_matches_type(CheckoutConfiguration, checkout_configuration, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - async def test_method_create_with_all_params_overload_2(self, async_client: AsyncWhop) -> None: - checkout_configuration = await async_client.checkout_configurations.create( - plan_id="plan_xxxxxxxxxxxxx", - affiliate_code="affiliate_code", - allow_promo_codes=True, - checkout_styling={ - "background_color": "background_color", - "border_style": "rounded", - "button_color": "button_color", - "font_family": "system", - }, - currency="usd", - metadata={"foo": "bar"}, - mode="payment", - payment_method_configuration={ - "disabled": ["acss_debit"], - "enabled": ["acss_debit"], - "include_platform_defaults": True, - }, - redirect_url="redirect_url", - source_url="source_url", - ) - assert_matches_type(CheckoutConfiguration, checkout_configuration, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - async def test_raw_response_create_overload_2(self, async_client: AsyncWhop) -> None: - response = await async_client.checkout_configurations.with_raw_response.create( - plan_id="plan_xxxxxxxxxxxxx", - ) - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - checkout_configuration = await response.parse() - assert_matches_type(CheckoutConfiguration, checkout_configuration, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - async def test_streaming_response_create_overload_2(self, async_client: AsyncWhop) -> None: - async with async_client.checkout_configurations.with_streaming_response.create( - plan_id="plan_xxxxxxxxxxxxx", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" + 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.checkout_configurations.with_raw_response.delete( + "", + ) - checkout_configuration = await response.parse() - assert_matches_type(CheckoutConfiguration, checkout_configuration, path=["response"]) - assert cast(Any, response.is_closed) is True +class TestAsyncCheckoutConfigurations: + 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_overload_3(self, async_client: AsyncWhop) -> None: - checkout_configuration = await async_client.checkout_configurations.create( - company_id="biz_xxxxxxxxxxxxxx", - mode="setup", - ) - assert_matches_type(CheckoutConfiguration, checkout_configuration, path=["response"]) + async def test_method_create(self, async_client: AsyncWhop) -> None: + checkout_configuration = await async_client.checkout_configurations.create() + assert_matches_type(CheckoutConfigurationCreateResponse, checkout_configuration, path=["response"]) @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize - async def test_method_create_with_all_params_overload_3(self, async_client: AsyncWhop) -> None: + async def test_method_create_with_all_params(self, async_client: AsyncWhop) -> None: checkout_configuration = await async_client.checkout_configurations.create( - company_id="biz_xxxxxxxxxxxxxx", - mode="setup", - allow_promo_codes=True, - checkout_styling={ - "background_color": "background_color", - "border_style": "rounded", - "button_color": "button_color", - "font_family": "system", - }, - currency="usd", - metadata={"foo": "bar"}, + affiliate_code="affiliate_code", + company_id="company_id", + currency="currency", + metadata={}, + mode="payment", payment_method_configuration={ - "disabled": ["acss_debit"], - "enabled": ["acss_debit"], + "disabled": ["string"], + "enabled": ["string"], "include_platform_defaults": True, }, + plan={ + "billing_period": 0, + "company_id": "company_id", + "currency": "currency", + "description": "description", + "expiration_days": 0, + "force_create_new_plan": True, + "initial_price": 0, + "metadata": {}, + "override_tax_type": "override_tax_type", + "payment_method_configuration": { + "disabled": ["string"], + "enabled": ["string"], + "include_platform_defaults": True, + }, + "plan_type": "plan_type", + "product_id": "product_id", + "release_method": "release_method", + "renewal_price": 0, + "stock": 0, + "title": "title", + "trial_period_days": 0, + "unlimited_stock": True, + "visibility": "visibility", + }, + plan_id="plan_id", redirect_url="redirect_url", - source_url="source_url", - three_ds_level="mandate_challenge", + three_ds_level="three_ds_level", ) - assert_matches_type(CheckoutConfiguration, checkout_configuration, path=["response"]) + assert_matches_type(CheckoutConfigurationCreateResponse, checkout_configuration, path=["response"]) @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize - async def test_raw_response_create_overload_3(self, async_client: AsyncWhop) -> None: - response = await async_client.checkout_configurations.with_raw_response.create( - company_id="biz_xxxxxxxxxxxxxx", - mode="setup", - ) + async def test_raw_response_create(self, async_client: AsyncWhop) -> None: + response = await async_client.checkout_configurations.with_raw_response.create() assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" checkout_configuration = await response.parse() - assert_matches_type(CheckoutConfiguration, checkout_configuration, path=["response"]) + assert_matches_type(CheckoutConfigurationCreateResponse, checkout_configuration, path=["response"]) @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize - async def test_streaming_response_create_overload_3(self, async_client: AsyncWhop) -> None: - async with async_client.checkout_configurations.with_streaming_response.create( - company_id="biz_xxxxxxxxxxxxxx", - mode="setup", - ) as response: + async def test_streaming_response_create(self, async_client: AsyncWhop) -> None: + async with async_client.checkout_configurations.with_streaming_response.create() as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" checkout_configuration = await response.parse() - assert_matches_type(CheckoutConfiguration, checkout_configuration, path=["response"]) + assert_matches_type(CheckoutConfigurationCreateResponse, checkout_configuration, path=["response"]) assert cast(Any, response.is_closed) is True @@ -618,33 +318,33 @@ async def test_streaming_response_create_overload_3(self, async_client: AsyncWho @parametrize async def test_method_retrieve(self, async_client: AsyncWhop) -> None: checkout_configuration = await async_client.checkout_configurations.retrieve( - "ch_xxxxxxxxxxxxxxx", + "id", ) - assert_matches_type(CheckoutConfiguration, checkout_configuration, path=["response"]) + assert_matches_type(CheckoutConfigurationRetrieveResponse, checkout_configuration, 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.checkout_configurations.with_raw_response.retrieve( - "ch_xxxxxxxxxxxxxxx", + "id", ) assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" checkout_configuration = await response.parse() - assert_matches_type(CheckoutConfiguration, checkout_configuration, path=["response"]) + assert_matches_type(CheckoutConfigurationRetrieveResponse, checkout_configuration, 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.checkout_configurations.with_streaming_response.retrieve( - "ch_xxxxxxxxxxxxxxx", + "id", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" checkout_configuration = await response.parse() - assert_matches_type(CheckoutConfiguration, checkout_configuration, path=["response"]) + assert_matches_type(CheckoutConfigurationRetrieveResponse, checkout_configuration, path=["response"]) assert cast(Any, response.is_closed) is True @@ -660,7 +360,7 @@ async def test_path_params_retrieve(self, async_client: AsyncWhop) -> None: @parametrize async def test_method_list(self, async_client: AsyncWhop) -> None: checkout_configuration = await async_client.checkout_configurations.list( - company_id="biz_xxxxxxxxxxxxxx", + company_id="company_id", ) assert_matches_type( AsyncCursorPage[CheckoutConfigurationListResponse], checkout_configuration, path=["response"] @@ -670,15 +370,13 @@ async def test_method_list(self, async_client: AsyncWhop) -> None: @parametrize async def test_method_list_with_all_params(self, async_client: AsyncWhop) -> None: checkout_configuration = await async_client.checkout_configurations.list( - company_id="biz_xxxxxxxxxxxxxx", + company_id="company_id", after="after", - before="before", - created_after=parse_datetime("2023-12-01T05:00:00.401Z"), - created_before=parse_datetime("2023-12-01T05:00:00.401Z"), - direction="asc", - first=42, - last=42, - plan_id="plan_xxxxxxxxxxxxx", + created_after=0, + created_before=0, + direction="direction", + first=0, + plan_id="plan_id", ) assert_matches_type( AsyncCursorPage[CheckoutConfigurationListResponse], checkout_configuration, path=["response"] @@ -688,7 +386,7 @@ async def test_method_list_with_all_params(self, async_client: AsyncWhop) -> Non @parametrize async def test_raw_response_list(self, async_client: AsyncWhop) -> None: response = await async_client.checkout_configurations.with_raw_response.list( - company_id="biz_xxxxxxxxxxxxxx", + company_id="company_id", ) assert response.is_closed is True @@ -702,7 +400,7 @@ async def test_raw_response_list(self, async_client: AsyncWhop) -> None: @parametrize async def test_streaming_response_list(self, async_client: AsyncWhop) -> None: async with async_client.checkout_configurations.with_streaming_response.list( - company_id="biz_xxxxxxxxxxxxxx", + company_id="company_id", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -713,3 +411,45 @@ async def test_streaming_response_list(self, async_client: AsyncWhop) -> None: ) 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: + checkout_configuration = await async_client.checkout_configurations.delete( + "id", + ) + assert checkout_configuration is None + + @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.checkout_configurations.with_raw_response.delete( + "id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + checkout_configuration = await response.parse() + assert checkout_configuration is None + + @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.checkout_configurations.with_streaming_response.delete( + "id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + checkout_configuration = await response.parse() + assert checkout_configuration is None + + 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.checkout_configurations.with_raw_response.delete( + "", + ) From 38b972a2c1c48c4c36a748fc835a3dced2897153 Mon Sep 17 00:00:00 2001 From: stlc-bot Date: Fri, 19 Jun 2026 02:53:24 +0000 Subject: [PATCH 072/109] feat(api): add payment status, capabilities, and actions to accounts Stainless-Generated-From: e9b856cdcb601618d2035489f73bb2b263f3d906 --- src/whop_sdk/types/account.py | 122 ++++++++++++++++++++++++++++++++-- 1 file changed, 117 insertions(+), 5 deletions(-) diff --git a/src/whop_sdk/types/account.py b/src/whop_sdk/types/account.py index 18257794..29809554 100644 --- a/src/whop_sdk/types/account.py +++ b/src/whop_sdk/types/account.py @@ -6,7 +6,7 @@ from .._models import BaseModel from .account_social_link import AccountSocialLink -__all__ = ["Account", "Balance", "RecommendedAction", "Wallet"] +__all__ = ["Account", "Balance", "Capabilities", "RecommendedAction", "RequiredAction", "Wallet"] class Balance(BaseModel): @@ -41,20 +41,110 @@ class Balance(BaseModel): """The total USD value of the holding, or null when no exchange rate is available""" +class Capabilities(BaseModel): + """Payment rails enabled for this account (active, inactive, or pending). + + pending means onboarding or review is in progress. Only computed on retrieve and me for callers with the company:balance:read scope; null otherwise + """ + + accept_bank_payments: Literal["active", "inactive", "pending"] + """Bank payins: debits, transfers, and local bank rails""" + + accept_bnpl_payments: Literal["active", "inactive", "pending"] + """Buy-now-pay-later payins; requires approval""" + + accept_card_payments: Literal["active", "inactive", "pending"] + """Card payins, including Apple Pay and Google Pay""" + + bank_deposit: Literal["active", "inactive", "pending"] + """Deposits by bank wire or ACH to the account's virtual bank account""" + + card_deposit: Literal["active", "inactive", "pending"] + """Balance top-ups by charging a stored payment method""" + + card_issuing: Literal["active", "inactive", "pending"] + """Issuing Whop cards; requires card application approval""" + + crypto_deposit: Literal["active", "inactive", "pending"] + """On-chain deposits to the account's crypto wallet""" + + crypto_payout: Literal["active", "inactive", "pending"] + """On-chain payouts to a crypto wallet""" + + instant_payout: Literal["active", "inactive", "pending"] + """Instant payouts to an eligible payout destination""" + + standard_payout: Literal["active", "inactive", "pending"] + """Standard payouts to an external payout destination""" + + transfer: Literal["active", "inactive", "pending"] + """Transfers to other accounts""" + + class RecommendedAction(BaseModel): - """Recommended actions to drive volume on the account""" + """Optional actions that unlock capabilities or grow the account. + + Same shape as required_actions. Only computed on retrieve and me; null otherwise + """ + + action: Literal["apply_for_financing", "migrate_from_stripe", "accept_first_payment", "join_whop_university"] + """ + The recommendation; new values may be added, so handle unknown actions + gracefully + """ + + blocked_capabilities: List[str] cta: str """The URL the call-to-action links to""" cta_label: str - """The label for the action's call-to-action button""" + """Button label""" + + description: str + """Supporting copy, or empty""" + + icon_url: Optional[str] = None + """Illustration icon URL, or null""" + + status: Literal["optional"] + """Always optional — never blocking""" + + title: str + """Headline for the recommendation""" + + +class RequiredAction(BaseModel): + """Obligations the account holder must resolve, ordered by display priority. + + Only computed on retrieve and me for callers with the company:balance:read scope; null otherwise + """ + + action: Literal["deposit_funds", "submit_information_request", "verify_identity", "connect_fulfillment_tracker"] + """ + What the holder must do; new values may be added, so handle unknown actions + gracefully + """ + + blocked_capabilities: List[str] + + cta: Optional[str] = None + """The URL the call-to-action links to, or null when there is no button""" + + cta_label: str + """Button label, or empty when there is no button""" + + description: str + """Supporting copy, or empty""" icon_url: Optional[str] = None """The URL of the action's illustration icon, or null if it has none""" + status: Literal["required", "pending"] + """required (act now) or pending (under review)""" + title: str - """The headline describing the recommended action""" + """Headline for the action""" class Wallet(BaseModel): @@ -82,6 +172,13 @@ class Account(BaseModel): business_type: Optional[str] = None """The high-level business category for the account""" + capabilities: Optional[Capabilities] = None + """Payment rails enabled for this account (active, inactive, or pending). + + pending means onboarding or review is in progress. Only computed on retrieve and + me for callers with the company:balance:read scope; null otherwise + """ + country: Optional[str] = None """The country the account is located in""" @@ -129,7 +226,7 @@ class Account(BaseModel): parent_account_id: Optional[str] = None """The parent account ID for connected accounts""" - recommended_actions: List[RecommendedAction] + recommended_actions: Optional[List[RecommendedAction]] = None require_2fa: bool """ @@ -137,6 +234,8 @@ class Account(BaseModel): enabled """ + required_actions: Optional[List[RequiredAction]] = None + route: str """The account's public route identifier""" @@ -154,6 +253,12 @@ class Account(BaseModel): social_links: List[AccountSocialLink] + status: Optional[str] = None + """Whether the account can operate on Whop: active or suspended. + + Only computed on retrieve and me; null otherwise + """ + store_page_config: object """Store page display configuration for the account""" @@ -163,6 +268,13 @@ class Account(BaseModel): title: str """The display name of the account""" + total_earned_usd: Optional[float] = None + """Lifetime sales for the account, normalized to USD. + + Only computed on retrieve and me for callers with the stats:read scope; null + otherwise + """ + total_usd: Optional[str] = None """Total USD value across all balances with a known exchange rate. From 222bc5a08bac5e421a5c45be8c905401ec8a0d8a Mon Sep 17 00:00:00 2001 From: stlc-bot Date: Fri, 19 Jun 2026 06:13:22 +0000 Subject: [PATCH 073/109] feat(api): social accounts REST [ENG-23741] Stainless-Generated-From: 952e516e793bad2e9d28a2cb9a1a65fe639bf7ab --- .stats.yml | 2 +- api.md | 13 + src/whop_sdk/_client.py | 38 ++ src/whop_sdk/resources/__init__.py | 14 + src/whop_sdk/resources/social_accounts.py | 376 ++++++++++++++++++ src/whop_sdk/types/__init__.py | 4 + src/whop_sdk/types/social_account.py | 36 ++ .../types/social_account_create_params.py | 21 + .../types/social_account_create_response.py | 10 + .../types/social_account_list_params.py | 43 ++ tests/api_resources/test_social_accounts.py | 210 ++++++++++ 11 files changed, 766 insertions(+), 1 deletion(-) create mode 100644 src/whop_sdk/resources/social_accounts.py create mode 100644 src/whop_sdk/types/social_account.py create mode 100644 src/whop_sdk/types/social_account_create_params.py create mode 100644 src/whop_sdk/types/social_account_create_response.py create mode 100644 src/whop_sdk/types/social_account_list_params.py create mode 100644 tests/api_resources/test_social_accounts.py diff --git a/.stats.yml b/.stats.yml index 1b7187f8..308607a7 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1 +1 @@ -configured_endpoints: 242 +configured_endpoints: 244 diff --git a/api.md b/api.md index 08fab0da..7a3af0ea 100644 --- a/api.md +++ b/api.md @@ -125,6 +125,19 @@ Methods: - client.products.list(\*\*params) -> SyncCursorPage[ProductListItem] - client.products.delete(id) -> ProductDeleteResponse +# SocialAccounts + +Types: + +```python +from whop_sdk.types import SocialAccount, SocialAccountCreateResponse +``` + +Methods: + +- client.social_accounts.create(\*\*params) -> SocialAccountCreateResponse +- client.social_accounts.list(\*\*params) -> SyncCursorPage[SocialAccount] + # Companies Types: diff --git a/src/whop_sdk/_client.py b/src/whop_sdk/_client.py index f7b08806..1cc7ccce 100644 --- a/src/whop_sdk/_client.py +++ b/src/whop_sdk/_client.py @@ -95,6 +95,7 @@ ledger_accounts, payment_methods, payout_accounts, + social_accounts, authorized_users, support_channels, financial_activity, @@ -160,6 +161,7 @@ from .resources.ledger_accounts import LedgerAccountsResource, AsyncLedgerAccountsResource from .resources.payment_methods import PaymentMethodsResource, AsyncPaymentMethodsResource from .resources.payout_accounts import PayoutAccountsResource, AsyncPayoutAccountsResource + from .resources.social_accounts import SocialAccountsResource, AsyncSocialAccountsResource from .resources.authorized_users import AuthorizedUsersResource, AsyncAuthorizedUsersResource from .resources.support_channels import SupportChannelsResource, AsyncSupportChannelsResource from .resources.financial_activity import FinancialActivityResource, AsyncFinancialActivityResource @@ -293,6 +295,12 @@ def products(self) -> ProductsResource: return ProductsResource(self) + @cached_property + def social_accounts(self) -> SocialAccountsResource: + from .resources.social_accounts import SocialAccountsResource + + return SocialAccountsResource(self) + @cached_property def companies(self) -> CompaniesResource: """Companies""" @@ -951,6 +959,12 @@ def products(self) -> AsyncProductsResource: return AsyncProductsResource(self) + @cached_property + def social_accounts(self) -> AsyncSocialAccountsResource: + from .resources.social_accounts import AsyncSocialAccountsResource + + return AsyncSocialAccountsResource(self) + @cached_property def companies(self) -> AsyncCompaniesResource: """Companies""" @@ -1529,6 +1543,12 @@ def products(self) -> products.ProductsResourceWithRawResponse: return ProductsResourceWithRawResponse(self._client.products) + @cached_property + def social_accounts(self) -> social_accounts.SocialAccountsResourceWithRawResponse: + from .resources.social_accounts import SocialAccountsResourceWithRawResponse + + return SocialAccountsResourceWithRawResponse(self._client.social_accounts) + @cached_property def companies(self) -> companies.CompaniesResourceWithRawResponse: """Companies""" @@ -1989,6 +2009,12 @@ def products(self) -> products.AsyncProductsResourceWithRawResponse: return AsyncProductsResourceWithRawResponse(self._client.products) + @cached_property + def social_accounts(self) -> social_accounts.AsyncSocialAccountsResourceWithRawResponse: + from .resources.social_accounts import AsyncSocialAccountsResourceWithRawResponse + + return AsyncSocialAccountsResourceWithRawResponse(self._client.social_accounts) + @cached_property def companies(self) -> companies.AsyncCompaniesResourceWithRawResponse: """Companies""" @@ -2451,6 +2477,12 @@ def products(self) -> products.ProductsResourceWithStreamingResponse: return ProductsResourceWithStreamingResponse(self._client.products) + @cached_property + def social_accounts(self) -> social_accounts.SocialAccountsResourceWithStreamingResponse: + from .resources.social_accounts import SocialAccountsResourceWithStreamingResponse + + return SocialAccountsResourceWithStreamingResponse(self._client.social_accounts) + @cached_property def companies(self) -> companies.CompaniesResourceWithStreamingResponse: """Companies""" @@ -2913,6 +2945,12 @@ def products(self) -> products.AsyncProductsResourceWithStreamingResponse: return AsyncProductsResourceWithStreamingResponse(self._client.products) + @cached_property + def social_accounts(self) -> social_accounts.AsyncSocialAccountsResourceWithStreamingResponse: + from .resources.social_accounts import AsyncSocialAccountsResourceWithStreamingResponse + + return AsyncSocialAccountsResourceWithStreamingResponse(self._client.social_accounts) + @cached_property def companies(self) -> companies.AsyncCompaniesResourceWithStreamingResponse: """Companies""" diff --git a/src/whop_sdk/resources/__init__.py b/src/whop_sdk/resources/__init__.py index 492cd7c6..e3b4f707 100644 --- a/src/whop_sdk/resources/__init__.py +++ b/src/whop_sdk/resources/__init__.py @@ -472,6 +472,14 @@ PayoutAccountsResourceWithStreamingResponse, AsyncPayoutAccountsResourceWithStreamingResponse, ) +from .social_accounts import ( + SocialAccountsResource, + AsyncSocialAccountsResource, + SocialAccountsResourceWithRawResponse, + AsyncSocialAccountsResourceWithRawResponse, + SocialAccountsResourceWithStreamingResponse, + AsyncSocialAccountsResourceWithStreamingResponse, +) from .authorized_users import ( AuthorizedUsersResource, AsyncAuthorizedUsersResource, @@ -554,6 +562,12 @@ "AsyncProductsResourceWithRawResponse", "ProductsResourceWithStreamingResponse", "AsyncProductsResourceWithStreamingResponse", + "SocialAccountsResource", + "AsyncSocialAccountsResource", + "SocialAccountsResourceWithRawResponse", + "AsyncSocialAccountsResourceWithRawResponse", + "SocialAccountsResourceWithStreamingResponse", + "AsyncSocialAccountsResourceWithStreamingResponse", "CompaniesResource", "AsyncCompaniesResource", "CompaniesResourceWithRawResponse", diff --git a/src/whop_sdk/resources/social_accounts.py b/src/whop_sdk/resources/social_accounts.py new file mode 100644 index 00000000..02c52250 --- /dev/null +++ b/src/whop_sdk/resources/social_accounts.py @@ -0,0 +1,376 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import List +from typing_extensions import Literal + +import httpx + +from ..types import social_account_list_params, social_account_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 ..pagination import SyncCursorPage, AsyncCursorPage +from .._base_client import AsyncPaginator, make_request_options +from ..types.social_account import SocialAccount +from ..types.social_account_create_response import SocialAccountCreateResponse + +__all__ = ["SocialAccountsResource", "AsyncSocialAccountsResource"] + + +class SocialAccountsResource(SyncAPIResource): + @cached_property + def with_raw_response(self) -> SocialAccountsResourceWithRawResponse: + """ + 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 SocialAccountsResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> SocialAccountsResourceWithStreamingResponse: + """ + 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 SocialAccountsResourceWithStreamingResponse(self) + + def create( + self, + *, + platform: Literal["meta_business"], + redirect_url: str, + scopes: List[Literal["advertise"]] | 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, + ) -> SocialAccountCreateResponse: + """ + Starts an OAuth connection flow for a social account and returns an + authorize_url to redirect the user to. Today the only supported platform is + meta_business, which grants the advertise scope so the connected Facebook page + and Instagram account can run ads. + + Args: + platform: The platform to connect the social account on. + + redirect_url: The Whop URL to redirect the user to after they finish connecting. + + scopes: Capabilities to grant for the connected social account, for example `advertise`. + + 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( + "/social_accounts", + body=maybe_transform( + { + "platform": platform, + "redirect_url": redirect_url, + "scopes": scopes, + }, + social_account_create_params.SocialAccountCreateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=SocialAccountCreateResponse, + ) + + def list( + self, + *, + account_id: str | Omit = omit, + after: str | Omit = omit, + before: str | Omit = omit, + first: int | Omit = omit, + last: int | Omit = omit, + platform: Literal["x", "instagram", "youtube", "tiktok", "facebook"] | Omit = omit, + scopes: List[Literal["advertise"]] | Omit = omit, + user_id: str | Omit = omit, + verified: bool | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> SyncCursorPage[SocialAccount]: + """Lists the social accounts linked to an account or user. + + The owner is passed as + exactly one of account*id (a biz* identifier) or user*id (a user* identifier); + an account-scoped API key defaults to its own account when neither is given. + + Args: + account_id: The Account that the social accounts are connected to. Provide either this or + user_id. + + after: Cursor to fetch the page after (from page_info.end_cursor). + + before: Cursor to fetch the page before (from page_info.start_cursor). + + first: The number of social accounts to return. + + last: The number of social accounts to return from the end of the range. + + platform: Only return social accounts for the platform that is specified. + + scopes: Only return social accounts that have these scopes. + + user_id: The User that the social accounts are connected to. Provide either this or + account_id. + + verified: Only return social accounts that are verified on the platform. + + 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( + "/social_accounts", + page=SyncCursorPage[SocialAccount], + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "account_id": account_id, + "after": after, + "before": before, + "first": first, + "last": last, + "platform": platform, + "scopes": scopes, + "user_id": user_id, + "verified": verified, + }, + social_account_list_params.SocialAccountListParams, + ), + ), + model=SocialAccount, + ) + + +class AsyncSocialAccountsResource(AsyncAPIResource): + @cached_property + def with_raw_response(self) -> AsyncSocialAccountsResourceWithRawResponse: + """ + 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 AsyncSocialAccountsResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncSocialAccountsResourceWithStreamingResponse: + """ + 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 AsyncSocialAccountsResourceWithStreamingResponse(self) + + async def create( + self, + *, + platform: Literal["meta_business"], + redirect_url: str, + scopes: List[Literal["advertise"]] | 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, + ) -> SocialAccountCreateResponse: + """ + Starts an OAuth connection flow for a social account and returns an + authorize_url to redirect the user to. Today the only supported platform is + meta_business, which grants the advertise scope so the connected Facebook page + and Instagram account can run ads. + + Args: + platform: The platform to connect the social account on. + + redirect_url: The Whop URL to redirect the user to after they finish connecting. + + scopes: Capabilities to grant for the connected social account, for example `advertise`. + + 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( + "/social_accounts", + body=await async_maybe_transform( + { + "platform": platform, + "redirect_url": redirect_url, + "scopes": scopes, + }, + social_account_create_params.SocialAccountCreateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=SocialAccountCreateResponse, + ) + + def list( + self, + *, + account_id: str | Omit = omit, + after: str | Omit = omit, + before: str | Omit = omit, + first: int | Omit = omit, + last: int | Omit = omit, + platform: Literal["x", "instagram", "youtube", "tiktok", "facebook"] | Omit = omit, + scopes: List[Literal["advertise"]] | Omit = omit, + user_id: str | Omit = omit, + verified: bool | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> AsyncPaginator[SocialAccount, AsyncCursorPage[SocialAccount]]: + """Lists the social accounts linked to an account or user. + + The owner is passed as + exactly one of account*id (a biz* identifier) or user*id (a user* identifier); + an account-scoped API key defaults to its own account when neither is given. + + Args: + account_id: The Account that the social accounts are connected to. Provide either this or + user_id. + + after: Cursor to fetch the page after (from page_info.end_cursor). + + before: Cursor to fetch the page before (from page_info.start_cursor). + + first: The number of social accounts to return. + + last: The number of social accounts to return from the end of the range. + + platform: Only return social accounts for the platform that is specified. + + scopes: Only return social accounts that have these scopes. + + user_id: The User that the social accounts are connected to. Provide either this or + account_id. + + verified: Only return social accounts that are verified on the platform. + + 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( + "/social_accounts", + page=AsyncCursorPage[SocialAccount], + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "account_id": account_id, + "after": after, + "before": before, + "first": first, + "last": last, + "platform": platform, + "scopes": scopes, + "user_id": user_id, + "verified": verified, + }, + social_account_list_params.SocialAccountListParams, + ), + ), + model=SocialAccount, + ) + + +class SocialAccountsResourceWithRawResponse: + def __init__(self, social_accounts: SocialAccountsResource) -> None: + self._social_accounts = social_accounts + + self.create = to_raw_response_wrapper( + social_accounts.create, + ) + self.list = to_raw_response_wrapper( + social_accounts.list, + ) + + +class AsyncSocialAccountsResourceWithRawResponse: + def __init__(self, social_accounts: AsyncSocialAccountsResource) -> None: + self._social_accounts = social_accounts + + self.create = async_to_raw_response_wrapper( + social_accounts.create, + ) + self.list = async_to_raw_response_wrapper( + social_accounts.list, + ) + + +class SocialAccountsResourceWithStreamingResponse: + def __init__(self, social_accounts: SocialAccountsResource) -> None: + self._social_accounts = social_accounts + + self.create = to_streamed_response_wrapper( + social_accounts.create, + ) + self.list = to_streamed_response_wrapper( + social_accounts.list, + ) + + +class AsyncSocialAccountsResourceWithStreamingResponse: + def __init__(self, social_accounts: AsyncSocialAccountsResource) -> None: + self._social_accounts = social_accounts + + self.create = async_to_streamed_response_wrapper( + social_accounts.create, + ) + self.list = async_to_streamed_response_wrapper( + social_accounts.list, + ) diff --git a/src/whop_sdk/types/__init__.py b/src/whop_sdk/types/__init__.py index 90baf49b..1a30ffab 100644 --- a/src/whop_sdk/types/__init__.py +++ b/src/whop_sdk/types/__init__.py @@ -98,6 +98,7 @@ from .checkout_shape import CheckoutShape as CheckoutShape from .course_chapter import CourseChapter as CourseChapter from .promo_duration import PromoDuration as PromoDuration +from .social_account import SocialAccount as SocialAccount from .ad_group_status import AdGroupStatus as AdGroupStatus from .app_list_params import AppListParams as AppListParams from .authorized_user import AuthorizedUser as AuthorizedUser @@ -326,6 +327,7 @@ 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 .social_account_list_params import SocialAccountListParams as SocialAccountListParams from .swap_create_quote_response import SwapCreateQuoteResponse as SwapCreateQuoteResponse from .transfer_retrieve_response import TransferRetrieveResponse as TransferRetrieveResponse from .user_check_access_response import UserCheckAccessResponse as UserCheckAccessResponse @@ -361,6 +363,7 @@ 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 .social_account_create_params import SocialAccountCreateParams as SocialAccountCreateParams from .verification_create_response import VerificationCreateResponse as VerificationCreateResponse from .verification_delete_response import VerificationDeleteResponse as VerificationDeleteResponse from .verification_update_response import VerificationUpdateResponse as VerificationUpdateResponse @@ -383,6 +386,7 @@ from .financial_activity_list_params import FinancialActivityListParams as FinancialActivityListParams from .invoice_past_due_webhook_event import InvoicePastDueWebhookEvent as InvoicePastDueWebhookEvent from .payment_method_retrieve_params import PaymentMethodRetrieveParams as PaymentMethodRetrieveParams +from .social_account_create_response import SocialAccountCreateResponse as SocialAccountCreateResponse 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 diff --git a/src/whop_sdk/types/social_account.py b/src/whop_sdk/types/social_account.py new file mode 100644 index 00000000..beb53370 --- /dev/null +++ b/src/whop_sdk/types/social_account.py @@ -0,0 +1,36 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Optional +from typing_extensions import Literal + +from .._models import BaseModel + +__all__ = ["SocialAccount"] + + +class SocialAccount(BaseModel): + id: str + """Unique identifier for the social account.""" + + external_id: Optional[str] = None + """The platform-specific ID for this social account.""" + + name: Optional[str] = None + """The display name of the social account on the platform.""" + + platform: Literal["x", "instagram", "youtube", "tiktok", "facebook"] + """The platform the social account exists on.""" + + profile_picture_url: Optional[str] = None + """The URL where the profile picture of the social account can be accessed.""" + + scopes: List[str] + + url: str + """The URL where the social account can be accessed on the platform.""" + + username: str + """The username of the social account on the platform.""" + + verified: bool + """Whether the social account is verified on the platform.""" diff --git a/src/whop_sdk/types/social_account_create_params.py b/src/whop_sdk/types/social_account_create_params.py new file mode 100644 index 00000000..b502579a --- /dev/null +++ b/src/whop_sdk/types/social_account_create_params.py @@ -0,0 +1,21 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import List +from typing_extensions import Literal, Required, TypedDict + +__all__ = ["SocialAccountCreateParams"] + + +class SocialAccountCreateParams(TypedDict, total=False): + platform: Required[Literal["meta_business"]] + """The platform to connect the social account on.""" + + redirect_url: Required[str] + """The Whop URL to redirect the user to after they finish connecting.""" + + scopes: List[Literal["advertise"]] + """ + Capabilities to grant for the connected social account, for example `advertise`. + """ diff --git a/src/whop_sdk/types/social_account_create_response.py b/src/whop_sdk/types/social_account_create_response.py new file mode 100644 index 00000000..ee66178b --- /dev/null +++ b/src/whop_sdk/types/social_account_create_response.py @@ -0,0 +1,10 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from .._models import BaseModel + +__all__ = ["SocialAccountCreateResponse"] + + +class SocialAccountCreateResponse(BaseModel): + authorize_url: str + """The OAuth authorization URL to redirect the user to.""" diff --git a/src/whop_sdk/types/social_account_list_params.py b/src/whop_sdk/types/social_account_list_params.py new file mode 100644 index 00000000..ab56f7ed --- /dev/null +++ b/src/whop_sdk/types/social_account_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 List +from typing_extensions import Literal, TypedDict + +__all__ = ["SocialAccountListParams"] + + +class SocialAccountListParams(TypedDict, total=False): + account_id: str + """The Account that the social accounts are connected to. + + Provide either this or user_id. + """ + + after: str + """Cursor to fetch the page after (from page_info.end_cursor).""" + + before: str + """Cursor to fetch the page before (from page_info.start_cursor).""" + + first: int + """The number of social accounts to return.""" + + last: int + """The number of social accounts to return from the end of the range.""" + + platform: Literal["x", "instagram", "youtube", "tiktok", "facebook"] + """Only return social accounts for the platform that is specified.""" + + scopes: List[Literal["advertise"]] + """Only return social accounts that have these scopes.""" + + user_id: str + """The User that the social accounts are connected to. + + Provide either this or account_id. + """ + + verified: bool + """Only return social accounts that are verified on the platform.""" diff --git a/tests/api_resources/test_social_accounts.py b/tests/api_resources/test_social_accounts.py new file mode 100644 index 00000000..133a86c9 --- /dev/null +++ b/tests/api_resources/test_social_accounts.py @@ -0,0 +1,210 @@ +# 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 ( + SocialAccount, + SocialAccountCreateResponse, +) +from whop_sdk.pagination import SyncCursorPage, AsyncCursorPage + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestSocialAccounts: + 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: + social_account = client.social_accounts.create( + platform="meta_business", + redirect_url="redirect_url", + ) + assert_matches_type(SocialAccountCreateResponse, social_account, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_create_with_all_params(self, client: Whop) -> None: + social_account = client.social_accounts.create( + platform="meta_business", + redirect_url="redirect_url", + scopes=["advertise"], + ) + assert_matches_type(SocialAccountCreateResponse, social_account, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_raw_response_create(self, client: Whop) -> None: + response = client.social_accounts.with_raw_response.create( + platform="meta_business", + redirect_url="redirect_url", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + social_account = response.parse() + assert_matches_type(SocialAccountCreateResponse, social_account, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_streaming_response_create(self, client: Whop) -> None: + with client.social_accounts.with_streaming_response.create( + platform="meta_business", + redirect_url="redirect_url", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + social_account = response.parse() + assert_matches_type(SocialAccountCreateResponse, social_account, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_list(self, client: Whop) -> None: + social_account = client.social_accounts.list() + assert_matches_type(SyncCursorPage[SocialAccount], social_account, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_list_with_all_params(self, client: Whop) -> None: + social_account = client.social_accounts.list( + account_id="account_id", + after="after", + before="before", + first=100, + last=100, + platform="x", + scopes=["advertise"], + user_id="user_id", + verified=True, + ) + assert_matches_type(SyncCursorPage[SocialAccount], social_account, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_raw_response_list(self, client: Whop) -> None: + response = client.social_accounts.with_raw_response.list() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + social_account = response.parse() + assert_matches_type(SyncCursorPage[SocialAccount], social_account, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_streaming_response_list(self, client: Whop) -> None: + with client.social_accounts.with_streaming_response.list() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + social_account = response.parse() + assert_matches_type(SyncCursorPage[SocialAccount], social_account, path=["response"]) + + assert cast(Any, response.is_closed) is True + + +class TestAsyncSocialAccounts: + 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: + social_account = await async_client.social_accounts.create( + platform="meta_business", + redirect_url="redirect_url", + ) + assert_matches_type(SocialAccountCreateResponse, social_account, 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: + social_account = await async_client.social_accounts.create( + platform="meta_business", + redirect_url="redirect_url", + scopes=["advertise"], + ) + assert_matches_type(SocialAccountCreateResponse, social_account, 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.social_accounts.with_raw_response.create( + platform="meta_business", + redirect_url="redirect_url", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + social_account = await response.parse() + assert_matches_type(SocialAccountCreateResponse, social_account, 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.social_accounts.with_streaming_response.create( + platform="meta_business", + redirect_url="redirect_url", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + social_account = await response.parse() + assert_matches_type(SocialAccountCreateResponse, social_account, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_list(self, async_client: AsyncWhop) -> None: + social_account = await async_client.social_accounts.list() + assert_matches_type(AsyncCursorPage[SocialAccount], social_account, 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: + social_account = await async_client.social_accounts.list( + account_id="account_id", + after="after", + before="before", + first=100, + last=100, + platform="x", + scopes=["advertise"], + user_id="user_id", + verified=True, + ) + assert_matches_type(AsyncCursorPage[SocialAccount], social_account, 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.social_accounts.with_raw_response.list() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + social_account = await response.parse() + assert_matches_type(AsyncCursorPage[SocialAccount], social_account, 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.social_accounts.with_streaming_response.list() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + social_account = await response.parse() + assert_matches_type(AsyncCursorPage[SocialAccount], social_account, path=["response"]) + + assert cast(Any, response.is_closed) is True From 2521b535a249e6b7580ccc9ffe88908148529d1e Mon Sep 17 00:00:00 2001 From: stlc-bot Date: Sat, 20 Jun 2026 00:20:26 +0000 Subject: [PATCH 074/109] Ads: dashboard Meta connect dual-writes to social_accounts + auto-shares ad accounts Stainless-Generated-From: ff59e029fd81bf406699daf5611c93f164f53fa5 --- src/whop_sdk/resources/social_accounts.py | 26 ++++++++++++++++--- .../types/social_account_create_params.py | 6 +++++ tests/api_resources/test_social_accounts.py | 2 ++ 3 files changed, 30 insertions(+), 4 deletions(-) diff --git a/src/whop_sdk/resources/social_accounts.py b/src/whop_sdk/resources/social_accounts.py index 02c52250..a799e0e5 100644 --- a/src/whop_sdk/resources/social_accounts.py +++ b/src/whop_sdk/resources/social_accounts.py @@ -51,6 +51,7 @@ def create( *, platform: Literal["meta_business"], redirect_url: str, + account_id: str | Omit = omit, scopes: List[Literal["advertise"]] | 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. @@ -61,15 +62,22 @@ def create( ) -> SocialAccountCreateResponse: """ Starts an OAuth connection flow for a social account and returns an - authorize_url to redirect the user to. Today the only supported platform is + authorize*url to redirect the user to. Today the only supported platform is meta_business, which grants the advertise scope so the connected Facebook page - and Instagram account can run ads. + and Instagram account can run ads. The required permission follows the requested + capability: the advertise scope requires ad_campaign:create (so advertiser roles + can connect), other scopes require social_account:create. The connection is + authorized against the account given by account_id (a biz* identifier); an + account-scoped API key may omit it to default to its own account. Args: platform: The platform to connect the social account on. redirect_url: The Whop URL to redirect the user to after they finish connecting. + account_id: The Account (biz\\__ identifier) to connect the social account for. An + account-scoped API key may omit this to default to its own account. + scopes: Capabilities to grant for the connected social account, for example `advertise`. extra_headers: Send extra headers @@ -86,6 +94,7 @@ def create( { "platform": platform, "redirect_url": redirect_url, + "account_id": account_id, "scopes": scopes, }, social_account_create_params.SocialAccountCreateParams, @@ -202,6 +211,7 @@ async def create( *, platform: Literal["meta_business"], redirect_url: str, + account_id: str | Omit = omit, scopes: List[Literal["advertise"]] | 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. @@ -212,15 +222,22 @@ async def create( ) -> SocialAccountCreateResponse: """ Starts an OAuth connection flow for a social account and returns an - authorize_url to redirect the user to. Today the only supported platform is + authorize*url to redirect the user to. Today the only supported platform is meta_business, which grants the advertise scope so the connected Facebook page - and Instagram account can run ads. + and Instagram account can run ads. The required permission follows the requested + capability: the advertise scope requires ad_campaign:create (so advertiser roles + can connect), other scopes require social_account:create. The connection is + authorized against the account given by account_id (a biz* identifier); an + account-scoped API key may omit it to default to its own account. Args: platform: The platform to connect the social account on. redirect_url: The Whop URL to redirect the user to after they finish connecting. + account_id: The Account (biz\\__ identifier) to connect the social account for. An + account-scoped API key may omit this to default to its own account. + scopes: Capabilities to grant for the connected social account, for example `advertise`. extra_headers: Send extra headers @@ -237,6 +254,7 @@ async def create( { "platform": platform, "redirect_url": redirect_url, + "account_id": account_id, "scopes": scopes, }, social_account_create_params.SocialAccountCreateParams, diff --git a/src/whop_sdk/types/social_account_create_params.py b/src/whop_sdk/types/social_account_create_params.py index b502579a..3d4f9325 100644 --- a/src/whop_sdk/types/social_account_create_params.py +++ b/src/whop_sdk/types/social_account_create_params.py @@ -15,6 +15,12 @@ class SocialAccountCreateParams(TypedDict, total=False): redirect_url: Required[str] """The Whop URL to redirect the user to after they finish connecting.""" + account_id: str + """The Account (biz\\__ identifier) to connect the social account for. + + An account-scoped API key may omit this to default to its own account. + """ + scopes: List[Literal["advertise"]] """ Capabilities to grant for the connected social account, for example `advertise`. diff --git a/tests/api_resources/test_social_accounts.py b/tests/api_resources/test_social_accounts.py index 133a86c9..713d4d36 100644 --- a/tests/api_resources/test_social_accounts.py +++ b/tests/api_resources/test_social_accounts.py @@ -36,6 +36,7 @@ def test_method_create_with_all_params(self, client: Whop) -> None: social_account = client.social_accounts.create( platform="meta_business", redirect_url="redirect_url", + account_id="account_id", scopes=["advertise"], ) assert_matches_type(SocialAccountCreateResponse, social_account, path=["response"]) @@ -133,6 +134,7 @@ async def test_method_create_with_all_params(self, async_client: AsyncWhop) -> N social_account = await async_client.social_accounts.create( platform="meta_business", redirect_url="redirect_url", + account_id="account_id", scopes=["advertise"], ) assert_matches_type(SocialAccountCreateResponse, social_account, path=["response"]) From 0eda3edb0e0a2ee1b1ae341e01349aa09e0d0213 Mon Sep 17 00:00:00 2001 From: stlc-bot Date: Sun, 21 Jun 2026 00:45:18 +0000 Subject: [PATCH 075/109] feat(api): Add delete endpoint to social_accounts resource Stainless-Generated-From: 6c11439909d7f3829ef697578308e4647397f149 --- .stats.yml | 2 +- api.md | 3 +- src/whop_sdk/resources/social_accounts.py | 121 +++++++++++++++++- src/whop_sdk/types/__init__.py | 2 + .../types/social_account_delete_params.py | 21 +++ .../types/social_account_delete_response.py | 7 + tests/api_resources/test_social_accounts.py | 105 +++++++++++++++ 7 files changed, 257 insertions(+), 4 deletions(-) create mode 100644 src/whop_sdk/types/social_account_delete_params.py create mode 100644 src/whop_sdk/types/social_account_delete_response.py diff --git a/.stats.yml b/.stats.yml index 308607a7..5e092744 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1 +1 @@ -configured_endpoints: 244 +configured_endpoints: 245 diff --git a/api.md b/api.md index 7a3af0ea..3cca8f8a 100644 --- a/api.md +++ b/api.md @@ -130,13 +130,14 @@ Methods: Types: ```python -from whop_sdk.types import SocialAccount, SocialAccountCreateResponse +from whop_sdk.types import SocialAccount, SocialAccountCreateResponse, SocialAccountDeleteResponse ``` Methods: - client.social_accounts.create(\*\*params) -> SocialAccountCreateResponse - client.social_accounts.list(\*\*params) -> SyncCursorPage[SocialAccount] +- client.social_accounts.delete(id, \*\*params) -> SocialAccountDeleteResponse # Companies diff --git a/src/whop_sdk/resources/social_accounts.py b/src/whop_sdk/resources/social_accounts.py index a799e0e5..166c80fe 100644 --- a/src/whop_sdk/resources/social_accounts.py +++ b/src/whop_sdk/resources/social_accounts.py @@ -7,9 +7,9 @@ import httpx -from ..types import social_account_list_params, social_account_create_params +from ..types import social_account_list_params, social_account_create_params, social_account_delete_params from .._types import Body, Omit, Query, Headers, NotGiven, omit, not_given -from .._utils import maybe_transform, async_maybe_transform +from .._utils import path_template, maybe_transform, async_maybe_transform from .._compat import cached_property from .._resource import SyncAPIResource, AsyncAPIResource from .._response import ( @@ -22,6 +22,7 @@ from .._base_client import AsyncPaginator, make_request_options from ..types.social_account import SocialAccount from ..types.social_account_create_response import SocialAccountCreateResponse +from ..types.social_account_delete_response import SocialAccountDeleteResponse __all__ = ["SocialAccountsResource", "AsyncSocialAccountsResource"] @@ -185,6 +186,58 @@ def list( model=SocialAccount, ) + def delete( + self, + id: str, + *, + account_id: str | Omit = omit, + user_id: 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, + ) -> SocialAccountDeleteResponse: + """ + Disconnects a social account from an account or user by discarding the link + record. The underlying social account record is retained. + + Args: + account_id: The Account that the social account is connected to. Provide either this or + user_id. + + user_id: The User that the social account is connected to. Provide either this or + account_id. + + 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("/social_accounts/{id}", id=id), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "account_id": account_id, + "user_id": user_id, + }, + social_account_delete_params.SocialAccountDeleteParams, + ), + ), + cast_to=SocialAccountDeleteResponse, + ) + class AsyncSocialAccountsResource(AsyncAPIResource): @cached_property @@ -345,6 +398,58 @@ def list( model=SocialAccount, ) + async def delete( + self, + id: str, + *, + account_id: str | Omit = omit, + user_id: 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, + ) -> SocialAccountDeleteResponse: + """ + Disconnects a social account from an account or user by discarding the link + record. The underlying social account record is retained. + + Args: + account_id: The Account that the social account is connected to. Provide either this or + user_id. + + user_id: The User that the social account is connected to. Provide either this or + account_id. + + 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("/social_accounts/{id}", id=id), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=await async_maybe_transform( + { + "account_id": account_id, + "user_id": user_id, + }, + social_account_delete_params.SocialAccountDeleteParams, + ), + ), + cast_to=SocialAccountDeleteResponse, + ) + class SocialAccountsResourceWithRawResponse: def __init__(self, social_accounts: SocialAccountsResource) -> None: @@ -356,6 +461,9 @@ def __init__(self, social_accounts: SocialAccountsResource) -> None: self.list = to_raw_response_wrapper( social_accounts.list, ) + self.delete = to_raw_response_wrapper( + social_accounts.delete, + ) class AsyncSocialAccountsResourceWithRawResponse: @@ -368,6 +476,9 @@ def __init__(self, social_accounts: AsyncSocialAccountsResource) -> None: self.list = async_to_raw_response_wrapper( social_accounts.list, ) + self.delete = async_to_raw_response_wrapper( + social_accounts.delete, + ) class SocialAccountsResourceWithStreamingResponse: @@ -380,6 +491,9 @@ def __init__(self, social_accounts: SocialAccountsResource) -> None: self.list = to_streamed_response_wrapper( social_accounts.list, ) + self.delete = to_streamed_response_wrapper( + social_accounts.delete, + ) class AsyncSocialAccountsResourceWithStreamingResponse: @@ -392,3 +506,6 @@ def __init__(self, social_accounts: AsyncSocialAccountsResource) -> None: self.list = async_to_streamed_response_wrapper( social_accounts.list, ) + self.delete = async_to_streamed_response_wrapper( + social_accounts.delete, + ) diff --git a/src/whop_sdk/types/__init__.py b/src/whop_sdk/types/__init__.py index 1a30ffab..58d2f5d6 100644 --- a/src/whop_sdk/types/__init__.py +++ b/src/whop_sdk/types/__init__.py @@ -364,6 +364,7 @@ from .refund_created_webhook_event import RefundCreatedWebhookEvent as RefundCreatedWebhookEvent from .refund_updated_webhook_event import RefundUpdatedWebhookEvent as RefundUpdatedWebhookEvent from .social_account_create_params import SocialAccountCreateParams as SocialAccountCreateParams +from .social_account_delete_params import SocialAccountDeleteParams as SocialAccountDeleteParams from .verification_create_response import VerificationCreateResponse as VerificationCreateResponse from .verification_delete_response import VerificationDeleteResponse as VerificationDeleteResponse from .verification_update_response import VerificationUpdateResponse as VerificationUpdateResponse @@ -387,6 +388,7 @@ from .invoice_past_due_webhook_event import InvoicePastDueWebhookEvent as InvoicePastDueWebhookEvent from .payment_method_retrieve_params import PaymentMethodRetrieveParams as PaymentMethodRetrieveParams from .social_account_create_response import SocialAccountCreateResponse as SocialAccountCreateResponse +from .social_account_delete_response import SocialAccountDeleteResponse as SocialAccountDeleteResponse 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 diff --git a/src/whop_sdk/types/social_account_delete_params.py b/src/whop_sdk/types/social_account_delete_params.py new file mode 100644 index 00000000..cd4b58f1 --- /dev/null +++ b/src/whop_sdk/types/social_account_delete_params.py @@ -0,0 +1,21 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import TypedDict + +__all__ = ["SocialAccountDeleteParams"] + + +class SocialAccountDeleteParams(TypedDict, total=False): + account_id: str + """The Account that the social account is connected to. + + Provide either this or user_id. + """ + + user_id: str + """The User that the social account is connected to. + + Provide either this or account_id. + """ diff --git a/src/whop_sdk/types/social_account_delete_response.py b/src/whop_sdk/types/social_account_delete_response.py new file mode 100644 index 00000000..a37179be --- /dev/null +++ b/src/whop_sdk/types/social_account_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__ = ["SocialAccountDeleteResponse"] + +SocialAccountDeleteResponse: TypeAlias = bool diff --git a/tests/api_resources/test_social_accounts.py b/tests/api_resources/test_social_accounts.py index 713d4d36..b83a3df3 100644 --- a/tests/api_resources/test_social_accounts.py +++ b/tests/api_resources/test_social_accounts.py @@ -12,6 +12,7 @@ from whop_sdk.types import ( SocialAccount, SocialAccountCreateResponse, + SocialAccountDeleteResponse, ) from whop_sdk.pagination import SyncCursorPage, AsyncCursorPage @@ -113,6 +114,58 @@ 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_delete(self, client: Whop) -> None: + social_account = client.social_accounts.delete( + id="id", + ) + assert_matches_type(SocialAccountDeleteResponse, social_account, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_delete_with_all_params(self, client: Whop) -> None: + social_account = client.social_accounts.delete( + id="id", + account_id="account_id", + user_id="user_id", + ) + assert_matches_type(SocialAccountDeleteResponse, social_account, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_raw_response_delete(self, client: Whop) -> None: + response = client.social_accounts.with_raw_response.delete( + id="id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + social_account = response.parse() + assert_matches_type(SocialAccountDeleteResponse, social_account, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_streaming_response_delete(self, client: Whop) -> None: + with client.social_accounts.with_streaming_response.delete( + id="id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + social_account = response.parse() + assert_matches_type(SocialAccountDeleteResponse, social_account, 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.social_accounts.with_raw_response.delete( + id="", + ) + class TestAsyncSocialAccounts: parametrize = pytest.mark.parametrize( @@ -210,3 +263,55 @@ async def test_streaming_response_list(self, async_client: AsyncWhop) -> None: assert_matches_type(AsyncCursorPage[SocialAccount], social_account, 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: + social_account = await async_client.social_accounts.delete( + id="id", + ) + assert_matches_type(SocialAccountDeleteResponse, social_account, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_delete_with_all_params(self, async_client: AsyncWhop) -> None: + social_account = await async_client.social_accounts.delete( + id="id", + account_id="account_id", + user_id="user_id", + ) + assert_matches_type(SocialAccountDeleteResponse, social_account, 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.social_accounts.with_raw_response.delete( + id="id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + social_account = await response.parse() + assert_matches_type(SocialAccountDeleteResponse, social_account, 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.social_accounts.with_streaming_response.delete( + id="id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + social_account = await response.parse() + assert_matches_type(SocialAccountDeleteResponse, social_account, 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.social_accounts.with_raw_response.delete( + id="", + ) From cb0032aadc8001a54f3a3814b89c9324fb40d0ee Mon Sep 17 00:00:00 2001 From: stlc-bot Date: Mon, 22 Jun 2026 22:26:15 +0000 Subject: [PATCH 076/109] docs(api): clearer account payment-status field descriptions Stainless-Generated-From: 6b422d0b349f646e8dc69ed1a4a6bf8e4218a6b0 --- src/whop_sdk/types/account.py | 33 +++++++++++---------------------- 1 file changed, 11 insertions(+), 22 deletions(-) diff --git a/src/whop_sdk/types/account.py b/src/whop_sdk/types/account.py index 29809554..1c837082 100644 --- a/src/whop_sdk/types/account.py +++ b/src/whop_sdk/types/account.py @@ -42,9 +42,8 @@ class Balance(BaseModel): class Capabilities(BaseModel): - """Payment rails enabled for this account (active, inactive, or pending). - - pending means onboarding or review is in progress. Only computed on retrieve and me for callers with the company:balance:read scope; null otherwise + """ + Each payment rail's status: active, inactive, or pending (pending means onboarding or review is in progress) """ accept_bank_payments: Literal["active", "inactive", "pending"] @@ -82,9 +81,8 @@ class Capabilities(BaseModel): class RecommendedAction(BaseModel): - """Optional actions that unlock capabilities or grow the account. - - Same shape as required_actions. Only computed on retrieve and me; null otherwise + """ + Optional actions that unlock capabilities or grow the account, same shape as required_actions """ action: Literal["apply_for_financing", "migrate_from_stripe", "accept_first_payment", "join_whop_university"] @@ -115,9 +113,8 @@ class RecommendedAction(BaseModel): class RequiredAction(BaseModel): - """Obligations the account holder must resolve, ordered by display priority. - - Only computed on retrieve and me for callers with the company:balance:read scope; null otherwise + """ + Actions the account owner must take to unblock capabilities like payouts and card spend, ordered by display priority """ action: Literal["deposit_funds", "submit_information_request", "verify_identity", "connect_fulfillment_tracker"] @@ -173,10 +170,9 @@ class Account(BaseModel): """The high-level business category for the account""" capabilities: Optional[Capabilities] = None - """Payment rails enabled for this account (active, inactive, or pending). - - pending means onboarding or review is in progress. Only computed on retrieve and - me for callers with the company:balance:read scope; null otherwise + """ + Each payment rail's status: active, inactive, or pending (pending means + onboarding or review is in progress) """ country: Optional[str] = None @@ -254,10 +250,7 @@ class Account(BaseModel): social_links: List[AccountSocialLink] status: Optional[str] = None - """Whether the account can operate on Whop: active or suspended. - - Only computed on retrieve and me; null otherwise - """ + """Whether the account can operate on Whop — active or suspended""" store_page_config: object """Store page display configuration for the account""" @@ -269,11 +262,7 @@ class Account(BaseModel): """The display name of the account""" total_earned_usd: Optional[float] = None - """Lifetime sales for the account, normalized to USD. - - Only computed on retrieve and me for callers with the stats:read scope; null - otherwise - """ + """Lifetime sales for the account, normalized to USD""" total_usd: Optional[str] = None """Total USD value across all balances with a known exchange rate. From e7f957dfb4b09b52e23a0fb74301a02654d9418d Mon Sep 17 00:00:00 2001 From: stlc-bot Date: Tue, 23 Jun 2026 11:07:33 +0000 Subject: [PATCH 077/109] feat(backend): update referral fields Stainless-Generated-From: 84baa4f3fb2267b4af2781db88161f95073de5a5 --- README.md | 4 +- src/whop_sdk/_client.py | 4 +- .../business_list_earnings_response.py | 21 ++++++---- .../types/referrals/business_list_response.py | 42 +++++++++++++------ .../referrals/business_retrieve_response.py | 42 +++++++++++++------ .../businesses/earning_list_response.py | 21 ++++++---- 6 files changed, 88 insertions(+), 46 deletions(-) diff --git a/README.md b/README.md index c1218d8e..6c5faec4 100644 --- a/README.md +++ b/README.md @@ -13,8 +13,8 @@ It is generated with [Stainless](https://www.stainless.com/). Use the Whop MCP Server to enable AI assistants to interact with this API, allowing them to explore endpoints, make test requests, and use documentation to help integrate this SDK into your application. -[![Add to Cursor](https://cursor.com/deeplink/mcp-install-dark.svg)](https://cursor.com/en-US/install-mcp?name=%40whop%2Fmcp&config=eyJjb21tYW5kIjoibnB4IiwiYXJncyI6WyIteSIsIkB3aG9wL21jcCJdLCJlbnYiOnsiV0hPUF9BUElfS0VZIjoiTXkgQVBJIEtleSIsIldIT1BfV0VCSE9PS19TRUNSRVQiOiJNeSBXZWJob29rIEtleSIsIldIT1BfQVBQX0lEIjoiYXBwX3h4eHh4eHh4eHh4eHh4IiwiV0hPUF9BUElfVkVSU0lPTiI6IjIwMjYtMDYtMDkifX0) -[![Install in VS Code](https://img.shields.io/badge/_-Add_to_VS_Code-blue?style=for-the-badge&logo=data:image/svg%2bxml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGZpbGw9Im5vbmUiIHZpZXdCb3g9IjAgMCA0MCA0MCI+PHBhdGggZmlsbD0iI0VFRSIgZmlsbC1ydWxlPSJldmVub2RkIiBkPSJNMzAuMjM1IDM5Ljg4NGEyLjQ5MSAyLjQ5MSAwIDAgMS0xLjc4MS0uNzNMMTIuNyAyNC43OGwtMy40NiAyLjYyNC0zLjQwNiAyLjU4MmExLjY2NSAxLjY2NSAwIDAgMS0xLjA4Mi4zMzggMS42NjQgMS42NjQgMCAwIDEtMS4wNDYtLjQzMWwtMi4yLTJhMS42NjYgMS42NjYgMCAwIDEgMC0yLjQ2M0w3LjQ1OCAyMCA0LjY3IDE3LjQ1MyAxLjUwNyAxNC41N2ExLjY2NSAxLjY2NSAwIDAgMSAwLTIuNDYzbDIuMi0yYTEuNjY1IDEuNjY1IDAgMCAxIDIuMTMtLjA5N2w2Ljg2MyA1LjIwOUwyOC40NTIuODQ0YTIuNDg4IDIuNDg4IDAgMCAxIDEuODQxLS43MjljLjM1MS4wMDkuNjk5LjA5MSAxLjAxOS4yNDVsOC4yMzYgMy45NjFhMi41IDIuNSAwIDAgMSAxLjQxNSAyLjI1M3YuMDk5LS4wNDVWMzMuMzd2LS4wNDUuMDk1YTIuNTAxIDIuNTAxIDAgMCAxLTEuNDE2IDIuMjU3bC04LjIzNSAzLjk2MWEyLjQ5MiAyLjQ5MiAwIDAgMS0xLjA3Ny4yNDZabS43MTYtMjguOTQ3LTExLjk0OCA5LjA2MiAxMS45NTIgOS4wNjUtLjAwNC0xOC4xMjdaIi8+PC9zdmc+)](https://vscode.stainless.com/mcp/%7B%22name%22%3A%22%40whop%2Fmcp%22%2C%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22-y%22%2C%22%40whop%2Fmcp%22%5D%2C%22env%22%3A%7B%22WHOP_API_KEY%22%3A%22My%20API%20Key%22%2C%22WHOP_WEBHOOK_SECRET%22%3A%22My%20Webhook%20Key%22%2C%22WHOP_APP_ID%22%3A%22app_xxxxxxxxxxxxxx%22%2C%22WHOP_API_VERSION%22%3A%222026-06-09%22%7D%7D) +[![Add to Cursor](https://cursor.com/deeplink/mcp-install-dark.svg)](https://cursor.com/en-US/install-mcp?name=%40whop%2Fmcp&config=eyJjb21tYW5kIjoibnB4IiwiYXJncyI6WyIteSIsIkB3aG9wL21jcCJdLCJlbnYiOnsiV0hPUF9BUElfS0VZIjoiTXkgQVBJIEtleSIsIldIT1BfV0VCSE9PS19TRUNSRVQiOiJNeSBXZWJob29rIEtleSIsIldIT1BfQVBQX0lEIjoiYXBwX3h4eHh4eHh4eHh4eHh4IiwiV0hPUF9BUElfVkVSU0lPTiI6IjIwMjYtMDYtMjAifX0) +[![Install in VS Code](https://img.shields.io/badge/_-Add_to_VS_Code-blue?style=for-the-badge&logo=data:image/svg%2bxml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGZpbGw9Im5vbmUiIHZpZXdCb3g9IjAgMCA0MCA0MCI+PHBhdGggZmlsbD0iI0VFRSIgZmlsbC1ydWxlPSJldmVub2RkIiBkPSJNMzAuMjM1IDM5Ljg4NGEyLjQ5MSAyLjQ5MSAwIDAgMS0xLjc4MS0uNzNMMTIuNyAyNC43OGwtMy40NiAyLjYyNC0zLjQwNiAyLjU4MmExLjY2NSAxLjY2NSAwIDAgMS0xLjA4Mi4zMzggMS42NjQgMS42NjQgMCAwIDEtMS4wNDYtLjQzMWwtMi4yLTJhMS42NjYgMS42NjYgMCAwIDEgMC0yLjQ2M0w3LjQ1OCAyMCA0LjY3IDE3LjQ1MyAxLjUwNyAxNC41N2ExLjY2NSAxLjY2NSAwIDAgMSAwLTIuNDYzbDIuMi0yYTEuNjY1IDEuNjY1IDAgMCAxIDIuMTMtLjA5N2w2Ljg2MyA1LjIwOUwyOC40NTIuODQ0YTIuNDg4IDIuNDg4IDAgMCAxIDEuODQxLS43MjljLjM1MS4wMDkuNjk5LjA5MSAxLjAxOS4yNDVsOC4yMzYgMy45NjFhMi41IDIuNSAwIDAgMSAxLjQxNSAyLjI1M3YuMDk5LS4wNDVWMzMuMzd2LS4wNDUuMDk1YTIuNTAxIDIuNTAxIDAgMCAxLTEuNDE2IDIuMjU3bC04LjIzNSAzLjk2MWEyLjQ5MiAyLjQ5MiAwIDAgMS0xLjA3Ny4yNDZabS43MTYtMjguOTQ3LTExLjk0OCA5LjA2MiAxMS45NTIgOS4wNjUtLjAwNC0xOC4xMjdaIi8+PC9zdmc+)](https://vscode.stainless.com/mcp/%7B%22name%22%3A%22%40whop%2Fmcp%22%2C%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22-y%22%2C%22%40whop%2Fmcp%22%5D%2C%22env%22%3A%7B%22WHOP_API_KEY%22%3A%22My%20API%20Key%22%2C%22WHOP_WEBHOOK_SECRET%22%3A%22My%20Webhook%20Key%22%2C%22WHOP_APP_ID%22%3A%22app_xxxxxxxxxxxxxx%22%2C%22WHOP_API_VERSION%22%3A%222026-06-20%22%7D%7D) > Note: You may need to set environment variables in your MCP client. diff --git a/src/whop_sdk/_client.py b/src/whop_sdk/_client.py index 1cc7ccce..e40dbc36 100644 --- a/src/whop_sdk/_client.py +++ b/src/whop_sdk/_client.py @@ -239,7 +239,7 @@ def __init__( self.app_id = app_id if version is None: - version = os.environ.get("WHOP_API_VERSION") or "2026-06-09" + version = os.environ.get("WHOP_API_VERSION") or "2026-06-20" self.version = version if base_url is None: @@ -903,7 +903,7 @@ def __init__( self.app_id = app_id if version is None: - version = os.environ.get("WHOP_API_VERSION") or "2026-06-09" + version = os.environ.get("WHOP_API_VERSION") or "2026-06-20" self.version = version if base_url is None: diff --git a/src/whop_sdk/types/referrals/business_list_earnings_response.py b/src/whop_sdk/types/referrals/business_list_earnings_response.py index e76b2bfb..92c38d02 100644 --- a/src/whop_sdk/types/referrals/business_list_earnings_response.py +++ b/src/whop_sdk/types/referrals/business_list_earnings_response.py @@ -88,25 +88,30 @@ class BusinessListEarningsResponse(BaseModel): account: Optional[Account] = None - amount: Optional[float] = None - """What the referrer earns, in USD. Null until the earning settles.""" - - base_amount: float - """The seller payment the earning was calculated from, in USD.""" - cancelation_reason: Optional[str] = None """Why the earning was canceled or reversed, if applicable.""" - created_at: datetime + commission_amount_usd: Optional[str] = None + """What the referrer earns, in USD. Null until the earning settles.""" - currency: str + created_at: datetime object: Literal["business_referral_earning"] payout_at: Optional[datetime] = None payout_percentage: Optional[float] = None + """The referrer's share of Whop's gross profit, as a fraction (0.3 = 30%). + + Null until the earning settles. + """ receipt: Optional[Receipt] = None status: Literal["awaiting_settlement", "pending", "completed", "canceled", "reversed"] + + transaction_amount_usd: str + """The sale amount the commission is calculated from, in USD.""" + + whop_gross_profit_usd: Optional[str] = None + """Whop's gross profit on the sale, in USD. Null until the earning settles.""" diff --git a/src/whop_sdk/types/referrals/business_list_response.py b/src/whop_sdk/types/referrals/business_list_response.py index cba6a34b..9d0fe682 100644 --- a/src/whop_sdk/types/referrals/business_list_response.py +++ b/src/whop_sdk/types/referrals/business_list_response.py @@ -6,7 +6,7 @@ from ..._models import BaseModel -__all__ = ["BusinessListResponse", "Account"] +__all__ = ["BusinessListResponse", "Account", "EarningsUsd", "VolumeUsd"] class Account(BaseModel): @@ -20,27 +20,44 @@ class Account(BaseModel): title: str +class EarningsUsd(BaseModel): + completed: str + """Commission already paid out, in USD.""" + + pending: str + """Commission scheduled but not yet paid, in USD.""" + + total: str + """Pending + completed commission, in USD.""" + + +class VolumeUsd(BaseModel): + attributed: str + """ + Credited GMV (awaiting_settlement + settled); excludes canceled and reversed, in + USD. + """ + + awaiting_settlement: str + """GMV awaiting settlement (commission not yet computed), in USD.""" + + settled: str + """GMV of pending + completed payments, in USD.""" + + class BusinessListResponse(BaseModel): id: str account: Optional[Account] = None - completed_payout: float - """Earnings already paid out, in USD.""" - created_at: datetime - currency: str + earnings_usd: EarningsUsd object: Literal["business_referral"] payout_percentage: float - - pending_payout: float - """Earnings awaiting payout, in USD.""" - - processing_volume: float - """All-time gross processing volume for the business, in USD.""" + """The referrer's share of Whop's gross profit, as a fraction (0.3 = 30%).""" referral_expires_at: Optional[datetime] = None @@ -48,5 +65,4 @@ class BusinessListResponse(BaseModel): status: Literal["active", "removed"] - total_earnings: float - """All-time affiliate earnings from this business (pending + completed), in USD.""" + volume_usd: VolumeUsd diff --git a/src/whop_sdk/types/referrals/business_retrieve_response.py b/src/whop_sdk/types/referrals/business_retrieve_response.py index 08a4566c..2b0846a8 100644 --- a/src/whop_sdk/types/referrals/business_retrieve_response.py +++ b/src/whop_sdk/types/referrals/business_retrieve_response.py @@ -6,7 +6,7 @@ from ..._models import BaseModel -__all__ = ["BusinessRetrieveResponse", "Account"] +__all__ = ["BusinessRetrieveResponse", "Account", "EarningsUsd", "VolumeUsd"] class Account(BaseModel): @@ -20,27 +20,44 @@ class Account(BaseModel): title: str +class EarningsUsd(BaseModel): + completed: str + """Commission already paid out, in USD.""" + + pending: str + """Commission scheduled but not yet paid, in USD.""" + + total: str + """Pending + completed commission, in USD.""" + + +class VolumeUsd(BaseModel): + attributed: str + """ + Credited GMV (awaiting_settlement + settled); excludes canceled and reversed, in + USD. + """ + + awaiting_settlement: str + """GMV awaiting settlement (commission not yet computed), in USD.""" + + settled: str + """GMV of pending + completed payments, in USD.""" + + class BusinessRetrieveResponse(BaseModel): id: str account: Optional[Account] = None - completed_payout: float - """Earnings already paid out, in USD.""" - created_at: datetime - currency: str + earnings_usd: EarningsUsd object: Literal["business_referral"] payout_percentage: float - - pending_payout: float - """Earnings awaiting payout, in USD.""" - - processing_volume: float - """All-time gross processing volume for the business, in USD.""" + """The referrer's share of Whop's gross profit, as a fraction (0.3 = 30%).""" referral_expires_at: Optional[datetime] = None @@ -48,5 +65,4 @@ class BusinessRetrieveResponse(BaseModel): status: Literal["active", "removed"] - total_earnings: float - """All-time affiliate earnings from this business (pending + completed), in USD.""" + volume_usd: VolumeUsd diff --git a/src/whop_sdk/types/referrals/businesses/earning_list_response.py b/src/whop_sdk/types/referrals/businesses/earning_list_response.py index ee6cd30b..e82df872 100644 --- a/src/whop_sdk/types/referrals/businesses/earning_list_response.py +++ b/src/whop_sdk/types/referrals/businesses/earning_list_response.py @@ -88,25 +88,30 @@ class EarningListResponse(BaseModel): account: Optional[Account] = None - amount: Optional[float] = None - """What the referrer earns, in USD. Null until the earning settles.""" - - base_amount: float - """The seller payment the earning was calculated from, in USD.""" - cancelation_reason: Optional[str] = None """Why the earning was canceled or reversed, if applicable.""" - created_at: datetime + commission_amount_usd: Optional[str] = None + """What the referrer earns, in USD. Null until the earning settles.""" - currency: str + created_at: datetime object: Literal["business_referral_earning"] payout_at: Optional[datetime] = None payout_percentage: Optional[float] = None + """The referrer's share of Whop's gross profit, as a fraction (0.3 = 30%). + + Null until the earning settles. + """ receipt: Optional[Receipt] = None status: Literal["awaiting_settlement", "pending", "completed", "canceled", "reversed"] + + transaction_amount_usd: str + """The sale amount the commission is calculated from, in USD.""" + + whop_gross_profit_usd: Optional[str] = None + """Whop's gross profit on the sale, in USD. Null until the earning settles.""" From 6cbfcac6ea566b6e9ac2cff1955047b1fcff2df4 Mon Sep 17 00:00:00 2001 From: stlc-bot Date: Tue, 23 Jun 2026 21:22:00 +0000 Subject: [PATCH 078/109] feat(ads): REST API Stainless-Generated-From: 90496f831fa9e753819a0e67e470217a94998334 --- .stats.yml | 2 +- api.md | 25 +- src/whop_sdk/_client.py | 18 - src/whop_sdk/resources/ad_campaigns.py | 438 +++++----- src/whop_sdk/resources/ad_groups.py | 784 +++++++++++------- src/whop_sdk/resources/ads.py | 713 +++++++++++----- src/whop_sdk/types/__init__.py | 13 +- src/whop_sdk/types/ad.py | 208 +++-- src/whop_sdk/types/ad_budget_type.py | 7 - src/whop_sdk/types/ad_campaign.py | 165 ++-- .../types/ad_campaign_create_params.py | 49 ++ src/whop_sdk/types/ad_campaign_list_params.py | 100 +-- .../types/ad_campaign_list_response.py | 156 ---- src/whop_sdk/types/ad_campaign_platform.py | 7 - .../types/ad_campaign_retrieve_params.py | 20 +- src/whop_sdk/types/ad_campaign_status.py | 7 - .../types/ad_campaign_update_params.py | 13 +- src/whop_sdk/types/ad_create_params.py | 63 ++ src/whop_sdk/types/ad_delete_response.py | 7 + src/whop_sdk/types/ad_group.py | 215 ++--- src/whop_sdk/types/ad_group_create_params.py | 88 ++ src/whop_sdk/types/ad_group_list_params.py | 90 +- src/whop_sdk/types/ad_group_list_response.py | 160 +--- .../types/ad_group_retrieve_params.py | 25 - src/whop_sdk/types/ad_group_status.py | 7 - src/whop_sdk/types/ad_group_update_params.py | 81 +- src/whop_sdk/types/ad_list_params.py | 122 +-- src/whop_sdk/types/ad_list_response.py | 169 ---- src/whop_sdk/types/ad_retrieve_params.py | 21 +- src/whop_sdk/types/ad_update_params.py | 53 ++ src/whop_sdk/types/external_ad_status.py | 7 - tests/api_resources/test_ad_campaigns.py | 232 ++++-- tests/api_resources/test_ad_groups.py | 272 ++++-- tests/api_resources/test_ads.py | 419 ++++++++-- 34 files changed, 2691 insertions(+), 2065 deletions(-) delete mode 100644 src/whop_sdk/types/ad_budget_type.py create mode 100644 src/whop_sdk/types/ad_campaign_create_params.py delete mode 100644 src/whop_sdk/types/ad_campaign_list_response.py delete mode 100644 src/whop_sdk/types/ad_campaign_platform.py delete mode 100644 src/whop_sdk/types/ad_campaign_status.py create mode 100644 src/whop_sdk/types/ad_create_params.py create mode 100644 src/whop_sdk/types/ad_delete_response.py create mode 100644 src/whop_sdk/types/ad_group_create_params.py delete mode 100644 src/whop_sdk/types/ad_group_retrieve_params.py delete mode 100644 src/whop_sdk/types/ad_group_status.py delete mode 100644 src/whop_sdk/types/ad_list_response.py create mode 100644 src/whop_sdk/types/ad_update_params.py delete mode 100644 src/whop_sdk/types/external_ad_status.py diff --git a/.stats.yml b/.stats.yml index 5e092744..dcab66f6 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1 +1 @@ -configured_endpoints: 245 +configured_endpoints: 250 diff --git a/api.md b/api.md index 3cca8f8a..95ef2e78 100644 --- a/api.md +++ b/api.md @@ -1165,14 +1165,15 @@ Methods: Types: ```python -from whop_sdk.types import AdCampaign, AdCampaignPlatform, AdCampaignStatus, AdCampaignListResponse +from whop_sdk.types import AdCampaign ``` Methods: +- client.ad_campaigns.create(\*\*params) -> AdCampaign - client.ad_campaigns.retrieve(id, \*\*params) -> AdCampaign - client.ad_campaigns.update(id, \*\*params) -> AdCampaign -- client.ad_campaigns.list(\*\*params) -> SyncCursorPage[AdCampaignListResponse] +- client.ad_campaigns.list(\*\*params) -> SyncCursorPage[AdCampaign] - client.ad_campaigns.pause(id) -> AdCampaign - client.ad_campaigns.unpause(id) -> AdCampaign @@ -1181,20 +1182,15 @@ Methods: Types: ```python -from whop_sdk.types import ( - AdBudgetType, - AdGroup, - AdGroupStatus, - AdGroupListResponse, - AdGroupDeleteResponse, -) +from whop_sdk.types import AdGroup, AdGroupListResponse, AdGroupDeleteResponse ``` Methods: -- client.ad_groups.retrieve(id, \*\*params) -> AdGroup +- client.ad_groups.create(\*\*params) -> AdGroup +- client.ad_groups.retrieve(id) -> AdGroup - client.ad_groups.update(id, \*\*params) -> AdGroup -- client.ad_groups.list(\*\*params) -> SyncCursorPage[AdGroupListResponse] +- client.ad_groups.list(\*\*params) -> AdGroupListResponse - client.ad_groups.delete(id) -> AdGroupDeleteResponse - client.ad_groups.pause(id) -> AdGroup - client.ad_groups.unpause(id) -> AdGroup @@ -1204,13 +1200,16 @@ Methods: Types: ```python -from whop_sdk.types import Ad, ExternalAdStatus, AdListResponse +from whop_sdk.types import Ad, AdDeleteResponse ``` Methods: +- client.ads.create(\*\*params) -> Ad - client.ads.retrieve(id, \*\*params) -> Ad -- client.ads.list(\*\*params) -> SyncCursorPage[AdListResponse] +- client.ads.update(id, \*\*params) -> Ad +- client.ads.list(\*\*params) -> SyncCursorPage[Ad] +- client.ads.delete(id) -> AdDeleteResponse - client.ads.pause(id) -> Ad - client.ads.unpause(id) -> Ad diff --git a/src/whop_sdk/_client.py b/src/whop_sdk/_client.py index e40dbc36..ff0d4a66 100644 --- a/src/whop_sdk/_client.py +++ b/src/whop_sdk/_client.py @@ -691,21 +691,18 @@ def bounties(self) -> BountiesResource: @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) @@ -1355,21 +1352,18 @@ def bounties(self) -> AsyncBountiesResource: @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) @@ -1939,21 +1933,18 @@ def bounties(self) -> bounties.BountiesResourceWithRawResponse: @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) @@ -2407,21 +2398,18 @@ def bounties(self) -> bounties.AsyncBountiesResourceWithRawResponse: @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) @@ -2875,21 +2863,18 @@ def bounties(self) -> bounties.BountiesResourceWithStreamingResponse: @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) @@ -3347,21 +3332,18 @@ def bounties(self) -> bounties.AsyncBountiesResourceWithStreamingResponse: @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) diff --git a/src/whop_sdk/resources/ad_campaigns.py b/src/whop_sdk/resources/ad_campaigns.py index f166f0ce..039e99b7 100644 --- a/src/whop_sdk/resources/ad_campaigns.py +++ b/src/whop_sdk/resources/ad_campaigns.py @@ -2,15 +2,14 @@ from __future__ import annotations -from typing import Union, Optional -from datetime import datetime +from typing import List from typing_extensions import Literal import httpx from ..types import ( - AdCampaignStatus, ad_campaign_list_params, + ad_campaign_create_params, ad_campaign_update_params, ad_campaign_retrieve_params, ) @@ -27,16 +26,11 @@ from ..pagination import SyncCursorPage, AsyncCursorPage from .._base_client import AsyncPaginator, make_request_options from ..types.ad_campaign import AdCampaign -from ..types.shared.direction import Direction -from ..types.ad_campaign_status import AdCampaignStatus -from ..types.ad_campaign_list_response import AdCampaignListResponse __all__ = ["AdCampaignsResource", "AsyncAdCampaignsResource"] class AdCampaignsResource(SyncAPIResource): - """Ad campaigns""" - @cached_property def with_raw_response(self) -> AdCampaignsResourceWithRawResponse: """ @@ -56,12 +50,17 @@ def with_streaming_response(self) -> AdCampaignsResourceWithStreamingResponse: """ return AdCampaignsResourceWithStreamingResponse(self) - def retrieve( + def create( self, - id: str, *, - stats_from: Union[str, datetime, None] | Omit = omit, - stats_to: Union[str, datetime, None] | Omit = omit, + objective: Literal["awareness", "traffic", "engagement", "leads", "sales"], + platform: Literal["meta"], + title: str, + account_id: str | Omit = omit, + budget_amount: float | Omit = omit, + budget_optimization: Literal["ad_campaign", "ad_group"] | Omit = omit, + budget_type: Literal["daily", "lifetime"] | Omit = omit, + special_ad_categories: List[Literal["housing", "employment", "financial_products", "politics"]] | 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, @@ -70,18 +69,80 @@ def retrieve( timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> AdCampaign: """ - Retrieves a single ad campaign by its unique identifier. + Creates an ad campaign for an account. + + Args: + objective: The goal the campaign optimizes toward. + + platform: The ad network the campaign runs on. + + title: The title of the campaign. - Required permissions: + account_id: The account to create the campaign under. Defaults to the account-scoped key's + own account. - - `ad_campaign:basic:read` + budget_amount: + The campaign budget, in USD. Required for CBO (budget_optimization: + ad_campaign); omit for ABO. + + budget_optimization: Which level owns the budget — the campaign (CBO) or each ad group (ABO). + Defaults to ad_group. + + budget_type: Whether the budget is spent per day or over the campaign's lifetime. Defaults to + daily. + + special_ad_categories: Regulated categories the campaign falls under. Ads in these categories are + subject to extra targeting restrictions. + + 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( + { + "objective": objective, + "platform": platform, + "title": title, + "account_id": account_id, + "budget_amount": budget_amount, + "budget_optimization": budget_optimization, + "budget_type": budget_type, + "special_ad_categories": special_ad_categories, + }, + 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=AdCampaign, + ) + + def retrieve( + self, + id: str, + *, + stats_from: str | Omit = omit, + stats_to: 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, + ) -> AdCampaign: + """ + Retrieves a single ad campaign with stats over the requested window. Args: - stats_from: Inclusive start of the window for the campaign's metric fields (spend, - impressions, …). Omit both statsFrom and statsTo for all-time stats. + stats_from: Start of the stats window. - stats_to: Inclusive end of the window for the campaign's metric fields. Omit both - statsFrom and statsTo for all-time stats. + stats_to: End of the stats window. extra_headers: Send extra headers @@ -115,8 +176,8 @@ def update( self, id: str, *, - budget: Optional[float] | Omit = omit, - desired_cpr: Optional[float] | Omit = omit, + budget_amount: float | Omit = omit, + title: 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, @@ -125,18 +186,12 @@ def update( timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> AdCampaign: """ - Updates an ad campaign synchronously and returns it immediately (local-first). - The platform push runs in the background; any errors surface on the dashboard. - - Required permissions: - - - `ad_campaign:update` + Updates an ad campaign's editable fields. Args: - budget: The campaign budget in dollars. The interpretation (daily or lifetime) follows - the campaign's existing budget type. + budget_amount: The campaign budget, in the account's currency. - desired_cpr: The advertiser's desired cost per result in dollars. + title: The name of the campaign. extra_headers: Send extra headers @@ -152,8 +207,8 @@ def update( path_template("/ad_campaigns/{id}", id=id), body=maybe_transform( { - "budget": budget, - "desired_cpr": desired_cpr, + "budget_amount": budget_amount, + "title": title, }, ad_campaign_update_params.AdCampaignUpdateParams, ), @@ -166,80 +221,56 @@ def update( def list( self, *, - after: Optional[str] | Omit = omit, - before: 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, - direction: Optional[Direction] | Omit = omit, - first: Optional[int] | Omit = omit, - last: Optional[int] | Omit = omit, - order: Optional[ - Literal[ - "created_at", - "spend", - "impressions", - "clicks", - "reach", - "unique_clicks", - "results", - "click_through_rate", - "cost_per_click", - "cost_per_mille", - "cost_per_result", - "frequency", - "return_on_ad_spend", - ] - ] - | Omit = omit, - query: Optional[str] | Omit = omit, - stats_from: Union[str, datetime, None] | Omit = omit, - stats_to: Union[str, datetime, None] | Omit = omit, - status: Optional[AdCampaignStatus] | Omit = omit, + account_id: str | Omit = omit, + after: str | Omit = omit, + before: str | Omit = omit, + created_after: str | Omit = omit, + created_before: str | Omit = omit, + direction: Literal["asc", "desc"] | Omit = omit, + first: int | Omit = omit, + last: int | Omit = omit, + order: Literal["created_at", "updated_at"] | Omit = omit, + query: str | Omit = omit, + stats_from: str | Omit = omit, + stats_to: str | Omit = omit, + status: Literal["draft", "active", "paused", "payment_failed"] | 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]: + ) -> SyncCursorPage[AdCampaign]: """ - Returns a paginated list of ad campaigns for a company, with optional filtering - by status, and creation date. - - Required permissions: - - - `ad_campaign:basic:read` + Lists the ad campaigns for an account, with stats over the requested window. Args: - after: Returns the elements in the list that come after the specified cursor. + account_id: The account the campaigns belong to. Defaults to the account-scoped key's own + account. - before: Returns the elements in the list that come before the specified cursor. + after: Cursor to fetch the page after (from page_info.end_cursor). - company_id: The unique identifier of the company to list ad campaigns for. + before: Cursor to fetch the page before (from page_info.start_cursor). - created_after: Only return ad campaigns created after this timestamp. + created_after: Only return campaigns created after this timestamp. - created_before: Only return ad campaigns created before this timestamp. + created_before: Only return campaigns created before this timestamp. - direction: The direction of the sort. + direction: The sort direction. Defaults to desc. - first: Returns the first _n_ elements from the list. + first: The number of campaigns to return. - last: Returns the last _n_ elements from the list. + last: The number of campaigns to return from the end of the range. - order: The fields the ads dashboard lists (campaigns, ad sets) can be ordered by. Stat - columns are computed over the provided stats date range. + order: The field to sort by. Defaults to created_at. - query: Case-insensitive substring match against the campaign title or ID. + query: Filter campaigns by a title or ID substring. - stats_from: Inclusive start of the window for each campaign's metric fields (spend, - impressions, …). Omit both statsFrom and statsTo for all-time stats. + stats_from: Start of the stats window. Defaults to all-time. - stats_to: Inclusive end of the window for each campaign's metric fields. Omit both - statsFrom and statsTo for all-time stats. + stats_to: End of the stats window. Defaults to now. - status: The status of an ad campaign. + status: Only return campaigns with this status. extra_headers: Send extra headers @@ -251,7 +282,7 @@ def list( """ return self._get_api_list( "/ad_campaigns", - page=SyncCursorPage[AdCampaignListResponse], + page=SyncCursorPage[AdCampaign], options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, @@ -259,9 +290,9 @@ def list( timeout=timeout, query=maybe_transform( { + "account_id": account_id, "after": after, "before": before, - "company_id": company_id, "created_after": created_after, "created_before": created_before, "direction": direction, @@ -276,7 +307,7 @@ def list( ad_campaign_list_params.AdCampaignListParams, ), ), - model=AdCampaignListResponse, + model=AdCampaign, ) def pause( @@ -291,11 +322,7 @@ def pause( timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> AdCampaign: """ - Pauses an ad campaign, optionally until a specific date. - - Required permissions: - - - `ad_campaign:update` + Pauses an active ad campaign. Args: extra_headers: Send extra headers @@ -330,10 +357,6 @@ def unpause( """ Resumes a paused ad campaign. - Required permissions: - - - `ad_campaign:update` - Args: extra_headers: Send extra headers @@ -355,8 +378,6 @@ def unpause( class AsyncAdCampaignsResource(AsyncAPIResource): - """Ad campaigns""" - @cached_property def with_raw_response(self) -> AsyncAdCampaignsResourceWithRawResponse: """ @@ -376,12 +397,17 @@ def with_streaming_response(self) -> AsyncAdCampaignsResourceWithStreamingRespon """ return AsyncAdCampaignsResourceWithStreamingResponse(self) - async def retrieve( + async def create( self, - id: str, *, - stats_from: Union[str, datetime, None] | Omit = omit, - stats_to: Union[str, datetime, None] | Omit = omit, + objective: Literal["awareness", "traffic", "engagement", "leads", "sales"], + platform: Literal["meta"], + title: str, + account_id: str | Omit = omit, + budget_amount: float | Omit = omit, + budget_optimization: Literal["ad_campaign", "ad_group"] | Omit = omit, + budget_type: Literal["daily", "lifetime"] | Omit = omit, + special_ad_categories: List[Literal["housing", "employment", "financial_products", "politics"]] | 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, @@ -390,18 +416,80 @@ async def retrieve( timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> AdCampaign: """ - Retrieves a single ad campaign by its unique identifier. + Creates an ad campaign for an account. + + Args: + objective: The goal the campaign optimizes toward. + + platform: The ad network the campaign runs on. + + title: The title of the campaign. + + account_id: The account to create the campaign under. Defaults to the account-scoped key's + own account. + + budget_amount: + The campaign budget, in USD. Required for CBO (budget_optimization: + ad_campaign); omit for ABO. - Required permissions: + budget_optimization: Which level owns the budget — the campaign (CBO) or each ad group (ABO). + Defaults to ad_group. - - `ad_campaign:basic:read` + budget_type: Whether the budget is spent per day or over the campaign's lifetime. Defaults to + daily. + + special_ad_categories: Regulated categories the campaign falls under. Ads in these categories are + subject to extra targeting restrictions. + + 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( + { + "objective": objective, + "platform": platform, + "title": title, + "account_id": account_id, + "budget_amount": budget_amount, + "budget_optimization": budget_optimization, + "budget_type": budget_type, + "special_ad_categories": special_ad_categories, + }, + 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=AdCampaign, + ) + + async def retrieve( + self, + id: str, + *, + stats_from: str | Omit = omit, + stats_to: 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, + ) -> AdCampaign: + """ + Retrieves a single ad campaign with stats over the requested window. Args: - stats_from: Inclusive start of the window for the campaign's metric fields (spend, - impressions, …). Omit both statsFrom and statsTo for all-time stats. + stats_from: Start of the stats window. - stats_to: Inclusive end of the window for the campaign's metric fields. Omit both - statsFrom and statsTo for all-time stats. + stats_to: End of the stats window. extra_headers: Send extra headers @@ -435,8 +523,8 @@ async def update( self, id: str, *, - budget: Optional[float] | Omit = omit, - desired_cpr: Optional[float] | Omit = omit, + budget_amount: float | Omit = omit, + title: 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, @@ -445,18 +533,12 @@ async def update( timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> AdCampaign: """ - Updates an ad campaign synchronously and returns it immediately (local-first). - The platform push runs in the background; any errors surface on the dashboard. - - Required permissions: - - - `ad_campaign:update` + Updates an ad campaign's editable fields. Args: - budget: The campaign budget in dollars. The interpretation (daily or lifetime) follows - the campaign's existing budget type. + budget_amount: The campaign budget, in the account's currency. - desired_cpr: The advertiser's desired cost per result in dollars. + title: The name of the campaign. extra_headers: Send extra headers @@ -472,8 +554,8 @@ async def update( path_template("/ad_campaigns/{id}", id=id), body=await async_maybe_transform( { - "budget": budget, - "desired_cpr": desired_cpr, + "budget_amount": budget_amount, + "title": title, }, ad_campaign_update_params.AdCampaignUpdateParams, ), @@ -486,80 +568,56 @@ async def update( def list( self, *, - after: Optional[str] | Omit = omit, - before: 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, - direction: Optional[Direction] | Omit = omit, - first: Optional[int] | Omit = omit, - last: Optional[int] | Omit = omit, - order: Optional[ - Literal[ - "created_at", - "spend", - "impressions", - "clicks", - "reach", - "unique_clicks", - "results", - "click_through_rate", - "cost_per_click", - "cost_per_mille", - "cost_per_result", - "frequency", - "return_on_ad_spend", - ] - ] - | Omit = omit, - query: Optional[str] | Omit = omit, - stats_from: Union[str, datetime, None] | Omit = omit, - stats_to: Union[str, datetime, None] | Omit = omit, - status: Optional[AdCampaignStatus] | Omit = omit, + account_id: str | Omit = omit, + after: str | Omit = omit, + before: str | Omit = omit, + created_after: str | Omit = omit, + created_before: str | Omit = omit, + direction: Literal["asc", "desc"] | Omit = omit, + first: int | Omit = omit, + last: int | Omit = omit, + order: Literal["created_at", "updated_at"] | Omit = omit, + query: str | Omit = omit, + stats_from: str | Omit = omit, + stats_to: str | Omit = omit, + status: Literal["draft", "active", "paused", "payment_failed"] | 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]]: + ) -> AsyncPaginator[AdCampaign, AsyncCursorPage[AdCampaign]]: """ - Returns a paginated list of ad campaigns for a company, with optional filtering - by status, and creation date. - - Required permissions: - - - `ad_campaign:basic:read` + Lists the ad campaigns for an account, with stats over the requested window. Args: - after: Returns the elements in the list that come after the specified cursor. + account_id: The account the campaigns belong to. Defaults to the account-scoped key's own + account. - before: Returns the elements in the list that come before the specified cursor. + after: Cursor to fetch the page after (from page_info.end_cursor). - company_id: The unique identifier of the company to list ad campaigns for. + before: Cursor to fetch the page before (from page_info.start_cursor). - created_after: Only return ad campaigns created after this timestamp. + created_after: Only return campaigns created after this timestamp. - created_before: Only return ad campaigns created before this timestamp. + created_before: Only return campaigns created before this timestamp. - direction: The direction of the sort. + direction: The sort direction. Defaults to desc. - first: Returns the first _n_ elements from the list. + first: The number of campaigns to return. - last: Returns the last _n_ elements from the list. + last: The number of campaigns to return from the end of the range. - order: The fields the ads dashboard lists (campaigns, ad sets) can be ordered by. Stat - columns are computed over the provided stats date range. + order: The field to sort by. Defaults to created_at. - query: Case-insensitive substring match against the campaign title or ID. + query: Filter campaigns by a title or ID substring. - stats_from: Inclusive start of the window for each campaign's metric fields (spend, - impressions, …). Omit both statsFrom and statsTo for all-time stats. + stats_from: Start of the stats window. Defaults to all-time. - stats_to: Inclusive end of the window for each campaign's metric fields. Omit both - statsFrom and statsTo for all-time stats. + stats_to: End of the stats window. Defaults to now. - status: The status of an ad campaign. + status: Only return campaigns with this status. extra_headers: Send extra headers @@ -571,7 +629,7 @@ def list( """ return self._get_api_list( "/ad_campaigns", - page=AsyncCursorPage[AdCampaignListResponse], + page=AsyncCursorPage[AdCampaign], options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, @@ -579,9 +637,9 @@ def list( timeout=timeout, query=maybe_transform( { + "account_id": account_id, "after": after, "before": before, - "company_id": company_id, "created_after": created_after, "created_before": created_before, "direction": direction, @@ -596,7 +654,7 @@ def list( ad_campaign_list_params.AdCampaignListParams, ), ), - model=AdCampaignListResponse, + model=AdCampaign, ) async def pause( @@ -611,11 +669,7 @@ async def pause( timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> AdCampaign: """ - Pauses an ad campaign, optionally until a specific date. - - Required permissions: - - - `ad_campaign:update` + Pauses an active ad campaign. Args: extra_headers: Send extra headers @@ -650,10 +704,6 @@ async def unpause( """ Resumes a paused ad campaign. - Required permissions: - - - `ad_campaign:update` - Args: extra_headers: Send extra headers @@ -678,6 +728,9 @@ 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, ) @@ -699,6 +752,9 @@ 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, ) @@ -720,6 +776,9 @@ 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, ) @@ -741,6 +800,9 @@ 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, ) diff --git a/src/whop_sdk/resources/ad_groups.py b/src/whop_sdk/resources/ad_groups.py index 73bb01cf..b4ddae2a 100644 --- a/src/whop_sdk/resources/ad_groups.py +++ b/src/whop_sdk/resources/ad_groups.py @@ -2,14 +2,13 @@ from __future__ import annotations -from typing import Union, Optional -from datetime import datetime +from typing import Union from typing_extensions import Literal import httpx -from ..types import AdGroupStatus, ad_group_list_params, ad_group_update_params, ad_group_retrieve_params -from .._types import Body, Omit, Query, Headers, NotGiven, SequenceNotStr, omit, not_given +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 @@ -19,11 +18,8 @@ async_to_raw_response_wrapper, async_to_streamed_response_wrapper, ) -from ..pagination import SyncCursorPage, AsyncCursorPage -from .._base_client import AsyncPaginator, make_request_options +from .._base_client import make_request_options from ..types.ad_group import AdGroup -from ..types.ad_group_status import AdGroupStatus -from ..types.shared.direction import Direction from ..types.ad_group_list_response import AdGroupListResponse from ..types.ad_group_delete_response import AdGroupDeleteResponse @@ -31,8 +27,6 @@ class AdGroupsResource(SyncAPIResource): - """Ad groups""" - @cached_property def with_raw_response(self) -> AdGroupsResourceWithRawResponse: """ @@ -52,12 +46,49 @@ def with_streaming_response(self) -> AdGroupsResourceWithStreamingResponse: """ return AdGroupsResourceWithStreamingResponse(self) - def retrieve( + def create( self, - id: str, *, - stats_from: Union[str, datetime, None] | Omit = omit, - stats_to: Union[str, datetime, None] | Omit = omit, + ad_campaign_id: str, + audience: object | Omit = omit, + bid_type: Literal["minimum_cost", "average_target", "maximum_target"] | Omit = omit, + budget_amount: float | Omit = omit, + budget_type: Literal["daily", "lifetime"] | Omit = omit, + conversion_event: Union[ + Literal[ + "purchase", + "add_to_cart", + "initiated_checkout", + "add_payment_info", + "complete_registration", + "lead", + "content_view", + "search", + "contact", + "customize_product", + "donate", + "find_location", + "schedule", + "start_trial", + "submit_application", + "subscribe", + ], + str, + None, + ] + | Omit = omit, + conversion_location: Literal["website"] | Omit = omit, + desired_cost_per_result: float | Omit = omit, + devices: object | Omit = omit, + ends_at: str | Omit = omit, + frequency_cap: object | Omit = omit, + minimum_daily_spend: float | Omit = omit, + optimization_goal: str | Omit = omit, + placements: object | Omit = omit, + regions: object | Omit = omit, + starts_at: str | Omit = omit, + status: Literal["active", "paused"] | Omit = omit, + title: 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, @@ -66,18 +97,44 @@ def retrieve( timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> AdGroup: """ - Retrieves a single ad group by its unique identifier. + Creates an ad group (ad set) in a campaign. + + Args: + ad_campaign_id: The ad campaign to create the ad group in. - Required permissions: + audience: Demographic targeting: { automatic, minimum_age, maximum_age, gender }. - - `ad_campaign:basic:read` + bid_type: Bid strategy. - Args: - stats_from: Inclusive start of the window for the ad group's metric fields (spend, - impressions, …). Omit both statsFrom and statsTo for all-time stats. + budget_amount: Ad-set budget in dollars (ABO only; omit under CBO). + + budget_type: Whether the budget is daily or lifetime. + + conversion_event: The pixel event optimized for. A standard event, or any custom pixel event name. + + conversion_location: Where conversions happen. + + desired_cost_per_result: Target/cap cost for average_target / maximum_target. + + devices: Device targeting: { platforms, operating_systems: [{ os, minimum_version }] }. - stats_to: Inclusive end of the window for the ad group's metric fields. Omit both - statsFrom and statsTo for all-time stats. + ends_at: Schedule end, ISO 8601. + + frequency_cap: { maximum_impressions, per_days } — only valid for reach optimization. + + minimum_daily_spend: Daily spend floor within the budget. + + optimization_goal: What the ad group optimizes for (e.g. conversions, link_clicks, reach). + + placements: 'automatic' (Advantage+) or a list of { platform, positions }. + + regions: Geo targeting: { include / exclude: { countries, cities, zips } }. + + starts_at: Schedule start, ISO 8601. + + status: Initial status (default: active). + + title: The display name of the ad group. extra_headers: Send extra headers @@ -87,22 +144,66 @@ def retrieve( timeout: Override the client-level default timeout for this request, in seconds """ + return self._post( + "/ad_groups", + body=maybe_transform( + { + "ad_campaign_id": ad_campaign_id, + "audience": audience, + "bid_type": bid_type, + "budget_amount": budget_amount, + "budget_type": budget_type, + "conversion_event": conversion_event, + "conversion_location": conversion_location, + "desired_cost_per_result": desired_cost_per_result, + "devices": devices, + "ends_at": ends_at, + "frequency_cap": frequency_cap, + "minimum_daily_spend": minimum_daily_spend, + "optimization_goal": optimization_goal, + "placements": placements, + "regions": regions, + "starts_at": starts_at, + "status": status, + "title": title, + }, + 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=AdGroup, + ) + + 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, + ) -> AdGroup: + """ + Retrieves a single ad group. + + 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, - query=maybe_transform( - { - "stats_from": stats_from, - "stats_to": stats_to, - }, - ad_group_retrieve_params.AdGroupRetrieveParams, - ), + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), cast_to=AdGroup, ) @@ -111,8 +212,45 @@ def update( self, id: str, *, - budget: Optional[float] | Omit = omit, - title: Optional[str] | Omit = omit, + audience: object | Omit = omit, + bid_type: Literal["minimum_cost", "average_target", "maximum_target"] | Omit = omit, + budget_amount: float | Omit = omit, + budget_type: Literal["daily", "lifetime"] | Omit = omit, + conversion_event: Union[ + Literal[ + "purchase", + "add_to_cart", + "initiated_checkout", + "add_payment_info", + "complete_registration", + "lead", + "content_view", + "search", + "contact", + "customize_product", + "donate", + "find_location", + "schedule", + "start_trial", + "submit_application", + "subscribe", + ], + str, + None, + ] + | Omit = omit, + conversion_location: Literal["website"] | Omit = omit, + desired_cost_per_result: float | Omit = omit, + devices: object | Omit = omit, + ends_at: str | Omit = omit, + frequency_cap: object | Omit = omit, + minimum_daily_spend: float | Omit = omit, + optimization_goal: str | Omit = omit, + placements: object | Omit = omit, + regions: object | Omit = omit, + starts_at: str | Omit = omit, + status: Literal["active", "paused"] | Omit = omit, + title: 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, @@ -120,21 +258,44 @@ def update( extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> AdGroup: - """Updates an ad group synchronously and returns it immediately (local-first). + """Updates an ad group's editable fields. + + Only the keys you send are changed. - The - platform push runs in the background; any errors surface on the dashboard. + Args: + audience: Demographic targeting: { automatic, minimum_age, maximum_age, gender }. - Required permissions: + bid_type: Bid strategy. - - `ad_campaign:update` - - `ad_campaign:basic:read` + budget_amount: Ad-set budget in dollars (ABO only; omit under CBO). - Args: - budget: Budget amount in dollars. The interpretation (daily or lifetime) follows the ad - group's existing budget type. + budget_type: Whether the budget is daily or lifetime. + + conversion_event: The pixel event optimized for. A standard event, or any custom pixel event name. + + conversion_location: Where conversions happen. + + desired_cost_per_result: Target/cap cost for average_target / maximum_target. + + devices: Device targeting: { platforms, operating_systems: [{ os, minimum_version }] }. + + ends_at: Schedule end, ISO 8601. + + frequency_cap: { maximum_impressions, per_days } — only valid for reach optimization. + + minimum_daily_spend: Daily spend floor within the budget. + + optimization_goal: What the ad group optimizes for (e.g. conversions, link_clicks, reach). + + placements: 'automatic' (Advantage+) or a list of { platform, positions }. + + regions: Geo targeting: { include / exclude: { countries, cities, zips } }. + + starts_at: Schedule start, ISO 8601. + + status: Initial status (default: active). - title: Human-readable ad group title. Max 255 characters. + title: The display name of the ad group. extra_headers: Send extra headers @@ -150,7 +311,22 @@ def update( path_template("/ad_groups/{id}", id=id), body=maybe_transform( { - "budget": budget, + "audience": audience, + "bid_type": bid_type, + "budget_amount": budget_amount, + "budget_type": budget_type, + "conversion_event": conversion_event, + "conversion_location": conversion_location, + "desired_cost_per_result": desired_cost_per_result, + "devices": devices, + "ends_at": ends_at, + "frequency_cap": frequency_cap, + "minimum_daily_spend": minimum_daily_spend, + "optimization_goal": optimization_goal, + "placements": placements, + "regions": regions, + "starts_at": starts_at, + "status": status, "title": title, }, ad_group_update_params.AdGroupUpdateParams, @@ -164,90 +340,31 @@ def update( def list( self, *, - ad_campaign_id: Optional[str] | Omit = omit, - ad_campaign_ids: Optional[SequenceNotStr[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, - direction: Optional[Direction] | Omit = omit, - first: Optional[int] | Omit = omit, - last: Optional[int] | Omit = omit, - order: Optional[ - Literal[ - "created_at", - "spend", - "impressions", - "clicks", - "reach", - "unique_clicks", - "results", - "click_through_rate", - "cost_per_click", - "cost_per_mille", - "cost_per_result", - "frequency", - "return_on_ad_spend", - ] - ] - | Omit = omit, - query: Optional[str] | Omit = omit, - stats_from: Union[str, datetime, None] | Omit = omit, - stats_to: Union[str, datetime, None] | Omit = omit, - status: Optional[AdGroupStatus] | Omit = omit, + account_id: str | Omit = omit, + ad_campaign_id: str | Omit = omit, + direction: Literal["asc", "desc"] | Omit = omit, + order: Literal["created_at", "updated_at"] | Omit = omit, + status: 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, - ) -> SyncCursorPage[AdGroupListResponse]: + ) -> 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` + Lists ad groups for the account, newest first. Args: - ad_campaign_id: Filter by ad campaign. Provide exactly one of ad_campaign_id or company_id. - - ad_campaign_ids: Only return ad groups belonging to these ad campaigns (max 100). Can be combined - with companyId or used on its own. - - 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. - - company_id: Filter by company. Provide companyId or adCampaignIds. - - created_after: Only return ad groups created after this timestamp. - - created_before: Only return ad groups created before this timestamp. - - direction: The direction of the sort. - - first: Returns the first _n_ elements from the list. + account_id: Account whose ad groups to list. Defaults to the authenticated account. - last: Returns the last _n_ elements from the list. + ad_campaign_id: Filter to ad groups in this campaign. - order: The fields the ads dashboard lists (campaigns, ad sets) can be ordered by. Stat - columns are computed over the provided stats date range. + direction: The sort direction. Defaults to desc. - query: Case-insensitive substring match against the ad group name or ID. + order: The field to sort by. Defaults to created_at. - stats_from: Inclusive start of the window for each ad group's metric fields (spend, - impressions, …). Omit both statsFrom and statsTo for all-time stats. - - stats_to: Inclusive end of the window for each ad group's metric fields. Omit both - statsFrom and statsTo for all-time stats. - - status: The status of an external ad group. + status: Filter to a status (active, paused, in_review, rejected). extra_headers: Send extra headers @@ -257,9 +374,8 @@ def list( timeout: Override the client-level default timeout for this request, in seconds """ - return self._get_api_list( + return self._get( "/ad_groups", - page=SyncCursorPage[AdGroupListResponse], options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, @@ -267,27 +383,16 @@ def list( timeout=timeout, query=maybe_transform( { + "account_id": account_id, "ad_campaign_id": ad_campaign_id, - "ad_campaign_ids": ad_campaign_ids, - "after": after, - "before": before, - "campaign_id": campaign_id, - "company_id": company_id, - "created_after": created_after, - "created_before": created_before, "direction": direction, - "first": first, - "last": last, "order": order, - "query": query, - "stats_from": stats_from, - "stats_to": stats_to, "status": status, }, ad_group_list_params.AdGroupListParams, ), ), - model=AdGroupListResponse, + cast_to=AdGroupListResponse, ) def delete( @@ -301,12 +406,9 @@ def delete( extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> AdGroupDeleteResponse: - """ - Soft-deletes an ad group. + """Deletes (discards) an ad group. - Required permissions: - - - `ad_campaign:update` + Returns true on success. Args: extra_headers: Send extra headers @@ -339,12 +441,7 @@ def pause( timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> AdGroup: """ - Pauses an ad group. - - Required permissions: - - - `ad_campaign:update` - - `ad_campaign:basic:read` + Pauses delivery of an ad group. Args: extra_headers: Send extra headers @@ -377,12 +474,7 @@ def unpause( timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> AdGroup: """ - Resumes a paused ad group. - - Required permissions: - - - `ad_campaign:update` - - `ad_campaign:basic:read` + Resumes delivery of a paused ad group. Args: extra_headers: Send extra headers @@ -405,8 +497,6 @@ def unpause( class AsyncAdGroupsResource(AsyncAPIResource): - """Ad groups""" - @cached_property def with_raw_response(self) -> AsyncAdGroupsResourceWithRawResponse: """ @@ -426,12 +516,49 @@ def with_streaming_response(self) -> AsyncAdGroupsResourceWithStreamingResponse: """ return AsyncAdGroupsResourceWithStreamingResponse(self) - async def retrieve( + async def create( self, - id: str, *, - stats_from: Union[str, datetime, None] | Omit = omit, - stats_to: Union[str, datetime, None] | Omit = omit, + ad_campaign_id: str, + audience: object | Omit = omit, + bid_type: Literal["minimum_cost", "average_target", "maximum_target"] | Omit = omit, + budget_amount: float | Omit = omit, + budget_type: Literal["daily", "lifetime"] | Omit = omit, + conversion_event: Union[ + Literal[ + "purchase", + "add_to_cart", + "initiated_checkout", + "add_payment_info", + "complete_registration", + "lead", + "content_view", + "search", + "contact", + "customize_product", + "donate", + "find_location", + "schedule", + "start_trial", + "submit_application", + "subscribe", + ], + str, + None, + ] + | Omit = omit, + conversion_location: Literal["website"] | Omit = omit, + desired_cost_per_result: float | Omit = omit, + devices: object | Omit = omit, + ends_at: str | Omit = omit, + frequency_cap: object | Omit = omit, + minimum_daily_spend: float | Omit = omit, + optimization_goal: str | Omit = omit, + placements: object | Omit = omit, + regions: object | Omit = omit, + starts_at: str | Omit = omit, + status: Literal["active", "paused"] | Omit = omit, + title: 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, @@ -440,18 +567,44 @@ async def retrieve( timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> AdGroup: """ - Retrieves a single ad group by its unique identifier. + Creates an ad group (ad set) in a campaign. - Required permissions: + Args: + ad_campaign_id: The ad campaign to create the ad group in. - - `ad_campaign:basic:read` + audience: Demographic targeting: { automatic, minimum_age, maximum_age, gender }. - Args: - stats_from: Inclusive start of the window for the ad group's metric fields (spend, - impressions, …). Omit both statsFrom and statsTo for all-time stats. + bid_type: Bid strategy. + + budget_amount: Ad-set budget in dollars (ABO only; omit under CBO). + + budget_type: Whether the budget is daily or lifetime. + + conversion_event: The pixel event optimized for. A standard event, or any custom pixel event name. + + conversion_location: Where conversions happen. + + desired_cost_per_result: Target/cap cost for average_target / maximum_target. + + devices: Device targeting: { platforms, operating_systems: [{ os, minimum_version }] }. + + ends_at: Schedule end, ISO 8601. + + frequency_cap: { maximum_impressions, per_days } — only valid for reach optimization. + + minimum_daily_spend: Daily spend floor within the budget. + + optimization_goal: What the ad group optimizes for (e.g. conversions, link_clicks, reach). - stats_to: Inclusive end of the window for the ad group's metric fields. Omit both - statsFrom and statsTo for all-time stats. + placements: 'automatic' (Advantage+) or a list of { platform, positions }. + + regions: Geo targeting: { include / exclude: { countries, cities, zips } }. + + starts_at: Schedule start, ISO 8601. + + status: Initial status (default: active). + + title: The display name of the ad group. extra_headers: Send extra headers @@ -461,22 +614,66 @@ async def retrieve( timeout: Override the client-level default timeout for this request, in seconds """ + return await self._post( + "/ad_groups", + body=await async_maybe_transform( + { + "ad_campaign_id": ad_campaign_id, + "audience": audience, + "bid_type": bid_type, + "budget_amount": budget_amount, + "budget_type": budget_type, + "conversion_event": conversion_event, + "conversion_location": conversion_location, + "desired_cost_per_result": desired_cost_per_result, + "devices": devices, + "ends_at": ends_at, + "frequency_cap": frequency_cap, + "minimum_daily_spend": minimum_daily_spend, + "optimization_goal": optimization_goal, + "placements": placements, + "regions": regions, + "starts_at": starts_at, + "status": status, + "title": title, + }, + 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=AdGroup, + ) + + 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, + ) -> AdGroup: + """ + Retrieves a single ad group. + + 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, - query=await async_maybe_transform( - { - "stats_from": stats_from, - "stats_to": stats_to, - }, - ad_group_retrieve_params.AdGroupRetrieveParams, - ), + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), cast_to=AdGroup, ) @@ -485,8 +682,45 @@ async def update( self, id: str, *, - budget: Optional[float] | Omit = omit, - title: Optional[str] | Omit = omit, + audience: object | Omit = omit, + bid_type: Literal["minimum_cost", "average_target", "maximum_target"] | Omit = omit, + budget_amount: float | Omit = omit, + budget_type: Literal["daily", "lifetime"] | Omit = omit, + conversion_event: Union[ + Literal[ + "purchase", + "add_to_cart", + "initiated_checkout", + "add_payment_info", + "complete_registration", + "lead", + "content_view", + "search", + "contact", + "customize_product", + "donate", + "find_location", + "schedule", + "start_trial", + "submit_application", + "subscribe", + ], + str, + None, + ] + | Omit = omit, + conversion_location: Literal["website"] | Omit = omit, + desired_cost_per_result: float | Omit = omit, + devices: object | Omit = omit, + ends_at: str | Omit = omit, + frequency_cap: object | Omit = omit, + minimum_daily_spend: float | Omit = omit, + optimization_goal: str | Omit = omit, + placements: object | Omit = omit, + regions: object | Omit = omit, + starts_at: str | Omit = omit, + status: Literal["active", "paused"] | Omit = omit, + title: 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, @@ -494,21 +728,44 @@ async def update( extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> AdGroup: - """Updates an ad group synchronously and returns it immediately (local-first). + """Updates an ad group's editable fields. - The - platform push runs in the background; any errors surface on the dashboard. + Only the keys you send are changed. - Required permissions: + Args: + audience: Demographic targeting: { automatic, minimum_age, maximum_age, gender }. - - `ad_campaign:update` - - `ad_campaign:basic:read` + bid_type: Bid strategy. - Args: - budget: Budget amount in dollars. The interpretation (daily or lifetime) follows the ad - group's existing budget type. + budget_amount: Ad-set budget in dollars (ABO only; omit under CBO). + + budget_type: Whether the budget is daily or lifetime. + + conversion_event: The pixel event optimized for. A standard event, or any custom pixel event name. - title: Human-readable ad group title. Max 255 characters. + conversion_location: Where conversions happen. + + desired_cost_per_result: Target/cap cost for average_target / maximum_target. + + devices: Device targeting: { platforms, operating_systems: [{ os, minimum_version }] }. + + ends_at: Schedule end, ISO 8601. + + frequency_cap: { maximum_impressions, per_days } — only valid for reach optimization. + + minimum_daily_spend: Daily spend floor within the budget. + + optimization_goal: What the ad group optimizes for (e.g. conversions, link_clicks, reach). + + placements: 'automatic' (Advantage+) or a list of { platform, positions }. + + regions: Geo targeting: { include / exclude: { countries, cities, zips } }. + + starts_at: Schedule start, ISO 8601. + + status: Initial status (default: active). + + title: The display name of the ad group. extra_headers: Send extra headers @@ -524,7 +781,22 @@ async def update( path_template("/ad_groups/{id}", id=id), body=await async_maybe_transform( { - "budget": budget, + "audience": audience, + "bid_type": bid_type, + "budget_amount": budget_amount, + "budget_type": budget_type, + "conversion_event": conversion_event, + "conversion_location": conversion_location, + "desired_cost_per_result": desired_cost_per_result, + "devices": devices, + "ends_at": ends_at, + "frequency_cap": frequency_cap, + "minimum_daily_spend": minimum_daily_spend, + "optimization_goal": optimization_goal, + "placements": placements, + "regions": regions, + "starts_at": starts_at, + "status": status, "title": title, }, ad_group_update_params.AdGroupUpdateParams, @@ -535,93 +807,34 @@ async def update( cast_to=AdGroup, ) - def list( + async def list( self, *, - ad_campaign_id: Optional[str] | Omit = omit, - ad_campaign_ids: Optional[SequenceNotStr[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, - direction: Optional[Direction] | Omit = omit, - first: Optional[int] | Omit = omit, - last: Optional[int] | Omit = omit, - order: Optional[ - Literal[ - "created_at", - "spend", - "impressions", - "clicks", - "reach", - "unique_clicks", - "results", - "click_through_rate", - "cost_per_click", - "cost_per_mille", - "cost_per_result", - "frequency", - "return_on_ad_spend", - ] - ] - | Omit = omit, - query: Optional[str] | Omit = omit, - stats_from: Union[str, datetime, None] | Omit = omit, - stats_to: Union[str, datetime, None] | Omit = omit, - status: Optional[AdGroupStatus] | Omit = omit, + account_id: str | Omit = omit, + ad_campaign_id: str | Omit = omit, + direction: Literal["asc", "desc"] | Omit = omit, + order: Literal["created_at", "updated_at"] | Omit = omit, + status: 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, - ) -> AsyncPaginator[AdGroupListResponse, AsyncCursorPage[AdGroupListResponse]]: + ) -> 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` + Lists ad groups for the account, newest first. Args: - ad_campaign_id: Filter by ad campaign. Provide exactly one of ad_campaign_id or company_id. - - ad_campaign_ids: Only return ad groups belonging to these ad campaigns (max 100). Can be combined - with companyId or used on its own. - - 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. - - company_id: Filter by company. Provide companyId or adCampaignIds. - - created_after: Only return ad groups created after this timestamp. - - created_before: Only return ad groups created before this timestamp. - - direction: The direction of the sort. - - first: Returns the first _n_ elements from the list. - - last: Returns the last _n_ elements from the list. - - order: The fields the ads dashboard lists (campaigns, ad sets) can be ordered by. Stat - columns are computed over the provided stats date range. + account_id: Account whose ad groups to list. Defaults to the authenticated account. - query: Case-insensitive substring match against the ad group name or ID. + ad_campaign_id: Filter to ad groups in this campaign. - stats_from: Inclusive start of the window for each ad group's metric fields (spend, - impressions, …). Omit both statsFrom and statsTo for all-time stats. + direction: The sort direction. Defaults to desc. - stats_to: Inclusive end of the window for each ad group's metric fields. Omit both - statsFrom and statsTo for all-time stats. + order: The field to sort by. Defaults to created_at. - status: The status of an external ad group. + status: Filter to a status (active, paused, in_review, rejected). extra_headers: Send extra headers @@ -631,37 +844,25 @@ def list( timeout: Override the client-level default timeout for this request, in seconds """ - return self._get_api_list( + return await self._get( "/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( + query=await async_maybe_transform( { + "account_id": account_id, "ad_campaign_id": ad_campaign_id, - "ad_campaign_ids": ad_campaign_ids, - "after": after, - "before": before, - "campaign_id": campaign_id, - "company_id": company_id, - "created_after": created_after, - "created_before": created_before, "direction": direction, - "first": first, - "last": last, "order": order, - "query": query, - "stats_from": stats_from, - "stats_to": stats_to, "status": status, }, ad_group_list_params.AdGroupListParams, ), ), - model=AdGroupListResponse, + cast_to=AdGroupListResponse, ) async def delete( @@ -675,12 +876,9 @@ async def delete( extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> AdGroupDeleteResponse: - """ - Soft-deletes an ad group. + """Deletes (discards) an ad group. - Required permissions: - - - `ad_campaign:update` + Returns true on success. Args: extra_headers: Send extra headers @@ -713,12 +911,7 @@ async def pause( timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> AdGroup: """ - Pauses an ad group. - - Required permissions: - - - `ad_campaign:update` - - `ad_campaign:basic:read` + Pauses delivery of an ad group. Args: extra_headers: Send extra headers @@ -751,12 +944,7 @@ async def unpause( timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> AdGroup: """ - Resumes a paused ad group. - - Required permissions: - - - `ad_campaign:update` - - `ad_campaign:basic:read` + Resumes delivery of a paused ad group. Args: extra_headers: Send extra headers @@ -782,6 +970,9 @@ 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, ) @@ -806,6 +997,9 @@ 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, ) @@ -830,6 +1024,9 @@ 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, ) @@ -854,6 +1051,9 @@ 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, ) diff --git a/src/whop_sdk/resources/ads.py b/src/whop_sdk/resources/ads.py index 7304fe6d..dd772a57 100644 --- a/src/whop_sdk/resources/ads.py +++ b/src/whop_sdk/resources/ads.py @@ -2,13 +2,12 @@ from __future__ import annotations -from typing import Union, Optional -from datetime import datetime +from typing import Iterable from typing_extensions import Literal import httpx -from ..types import ExternalAdStatus, ad_list_params, ad_retrieve_params +from ..types import ad_list_params, ad_create_params, ad_update_params, ad_retrieve_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 @@ -22,16 +21,12 @@ ) from ..pagination import SyncCursorPage, AsyncCursorPage from .._base_client import AsyncPaginator, make_request_options -from ..types.ad_list_response import AdListResponse -from ..types.shared.direction import Direction -from ..types.external_ad_status import ExternalAdStatus +from ..types.ad_delete_response import AdDeleteResponse __all__ = ["AdsResource", "AsyncAdsResource"] class AdsResource(SyncAPIResource): - """Ads""" - @cached_property def with_raw_response(self) -> AdsResourceWithRawResponse: """ @@ -51,12 +46,23 @@ def with_streaming_response(self) -> AdsResourceWithStreamingResponse: """ return AdsResourceWithStreamingResponse(self) - def retrieve( + def create( self, - id: str, *, - stats_from: Union[str, datetime, None] | Omit = omit, - stats_to: Union[str, datetime, None] | Omit = omit, + ad_group: object | Omit = omit, + ad_group_id: str | Omit = omit, + call_to_action: Literal[ + "shop_now", "learn_more", "sign_up", "subscribe", "order_now", "get_offer", "see_details" + ] + | Omit = omit, + creatives: Iterable[ad_create_params.Creative] | Omit = omit, + descriptions: SequenceNotStr[str] | Omit = omit, + headlines: SequenceNotStr[str] | Omit = omit, + primary_texts: SequenceNotStr[str] | Omit = omit, + social_accounts: Iterable[ad_create_params.SocialAccount] | Omit = omit, + title: str | Omit = omit, + url: str | Omit = omit, + url_parameters: object | 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, @@ -65,18 +71,86 @@ def retrieve( timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> Ad: """ - Retrieve an ad by its unique identifier. + Creates an ad in an ad group. + + Args: + ad_group: An inline ad group to create (same shape as POST /ad_groups, including + ad_campaign_id). Creates the ad group and the ad together. Provide this OR + ad_group_id. + + ad_group_id: The existing ad group to create the ad in. Provide this OR ad_group, not both. + + call_to_action: The call-to-action button shown on the ad. + + creatives: The ad's creatives. Each entry is an uploaded file id with an optional format; + omit format for the original/uncropped asset. + + descriptions: The description variants shown on the ad. + + headlines: The headline variants shown on the ad. + + primary_texts: The primary text variants shown in the ad body. + + social_accounts: The social accounts (Facebook page, Instagram profile) the ad runs under. + + title: The display name of the ad. + + url: The URL the ad links to. - Required permissions: + url_parameters: Query parameters appended to the destination URL, as a string-to-string map. + + 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": ad_group, + "ad_group_id": ad_group_id, + "call_to_action": call_to_action, + "creatives": creatives, + "descriptions": descriptions, + "headlines": headlines, + "primary_texts": primary_texts, + "social_accounts": social_accounts, + "title": title, + "url": url, + "url_parameters": url_parameters, + }, + ad_create_params.AdCreateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=Ad, + ) - - `ad_campaign:basic:read` + def retrieve( + self, + id: str, + *, + stats_from: str | Omit = omit, + stats_to: 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, + ) -> Ad: + """ + Retrieves a single ad with stats over the requested window. Args: - stats_from: Inclusive start of the window for the ad's metric fields (spend, impressions, - …). Omit both statsFrom and statsTo for all-time stats. + stats_from: Start of the stats window. - stats_to: Inclusive end of the window for the ad's metric fields. Omit both statsFrom and - statsTo for all-time stats. + stats_to: End of the stats window. extra_headers: Send extra headers @@ -106,109 +180,143 @@ def retrieve( cast_to=Ad, ) - def list( + def update( self, + id: str, *, - ad_campaign_id: Optional[str] | Omit = omit, - ad_campaign_ids: Optional[SequenceNotStr[str]] | Omit = omit, - ad_group_id: Optional[str] | Omit = omit, - ad_group_ids: Optional[SequenceNotStr[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, - direction: Optional[Direction] | Omit = omit, - first: Optional[int] | Omit = omit, - last: Optional[int] | Omit = omit, - order: Optional[ - Literal[ - "created_at", - "spend", - "impressions", - "clicks", - "reach", - "unique_clicks", - "results", - "click_through_rate", - "cost_per_click", - "cost_per_mille", - "cost_per_result", - "frequency", - "return_on_ad_spend", - ] + call_to_action: Literal[ + "shop_now", "learn_more", "sign_up", "subscribe", "order_now", "get_offer", "see_details" ] | Omit = omit, - order_by: Optional[Literal["spend", "return_on_ad_spend", "roas"]] | Omit = omit, - order_direction: Optional[Direction] | Omit = omit, - query: Optional[str] | Omit = omit, - stats_from: Union[str, datetime, None] | Omit = omit, - stats_to: Union[str, datetime, None] | Omit = omit, - status: Optional[ExternalAdStatus] | Omit = omit, + creatives: Iterable[ad_update_params.Creative] | Omit = omit, + descriptions: SequenceNotStr[str] | Omit = omit, + headlines: SequenceNotStr[str] | Omit = omit, + primary_texts: SequenceNotStr[str] | Omit = omit, + social_accounts: Iterable[ad_update_params.SocialAccount] | Omit = omit, + title: str | Omit = omit, + url: str | Omit = omit, + url_parameters: object | 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]: + ) -> Ad: """ - List ads scoped by ad group, campaign, or company. + Updates an ad's editable fields. - Required permissions: + Args: + call_to_action: The call-to-action button shown on the ad. - - `ad_campaign:basic:read` + creatives: The ad's creatives. Each entry is an uploaded file id with an optional format; + omit format for the original/uncropped asset. Replaces a live ad's creative on + the platform. - Args: - ad_campaign_id: Filter by ad campaign. Provide exactly one of ad_group_id, ad_campaign_id, or - company_id. + descriptions: The description variants shown on the ad. - ad_campaign_ids: Only return ads belonging to these ad campaigns (max 100). Can be combined with - companyId or used on its own. + headlines: The headline variants shown on the ad. - ad_group_id: Filter by ad group. Provide exactly one of ad_group_id, ad_campaign_id, or - company_id. + primary_texts: The primary text variants shown in the ad body. - ad_group_ids: Only return ads belonging to these ad groups (max 100). Can be combined with - companyId or used on its own. + social_accounts: The social accounts the ad runs under. - after: Returns the elements in the list that come after the specified cursor. + title: The display name of the ad. - before: Returns the elements in the list that come before the specified cursor. + url: The URL the ad links to. - campaign_id: Filter by campaign. + url_parameters: Query parameters appended to the destination URL, as a string-to-string map. - company_id: Filter by company. Provide exactly one of ad_group_id, ad_campaign_id, or - company_id. + extra_headers: Send extra headers - created_after: Only return ads created after this timestamp. + extra_query: Add additional query parameters to the request - created_before: Only return ads created before this timestamp. + 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("/ads/{id}", id=id), + body=maybe_transform( + { + "call_to_action": call_to_action, + "creatives": creatives, + "descriptions": descriptions, + "headlines": headlines, + "primary_texts": primary_texts, + "social_accounts": social_accounts, + "title": title, + "url": url, + "url_parameters": url_parameters, + }, + ad_update_params.AdUpdateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=Ad, + ) - direction: The direction of the sort. + def list( + self, + *, + account_id: str | Omit = omit, + ad_campaign_id: str | Omit = omit, + ad_group_id: str | Omit = omit, + after: str | Omit = omit, + before: str | Omit = omit, + created_after: str | Omit = omit, + created_before: str | Omit = omit, + direction: Literal["asc", "desc"] | Omit = omit, + first: int | Omit = omit, + last: int | Omit = omit, + order: Literal["created_at", "updated_at"] | Omit = omit, + query: str | Omit = omit, + stats_from: str | Omit = omit, + stats_to: str | Omit = omit, + status: Literal["active", "paused", "in_review", "rejected"] | 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[Ad]: + """ + Lists the ads for an account, with stats over the requested window. - first: Returns the first _n_ elements from the list. + Args: + account_id: The account the ads belong to. Defaults to the account-scoped key's own account. - last: Returns the last _n_ elements from the list. + ad_campaign_id: Only return ads in this ad campaign. - order: The fields the ads dashboard lists (campaigns, ad sets) can be ordered by. Stat - columns are computed over the provided stats date range. + ad_group_id: Only return ads in this ad group. - order_by: Columns that the listAds query can sort by. Deprecated — use AdStatOrder. + after: Cursor to fetch the page after (from page_info.end_cursor). - order_direction: The direction of the sort. + before: Cursor to fetch the page before (from page_info.start_cursor). - query: Case-insensitive substring match against the ad title or ID. + created_after: Only return ads created after this timestamp. + + created_before: Only return ads created before this timestamp. + + direction: The sort direction. Defaults to desc. + + first: The number of ads to return. + + last: The number of ads to return from the end of the range. + + order: The field to sort by. Defaults to created_at. + + query: Filter ads by a title or ID substring. - stats_from: Inclusive start of the window for each ad's metric fields (spend, impressions, - …) and for stats-column sorting. Omit both statsFrom and statsTo for all-time - stats. + stats_from: Start of the stats window. Defaults to all-time. - stats_to: Inclusive end of the window for each ad's metric fields and for stats-column - sorting. Omit both statsFrom and statsTo for all-time stats. + stats_to: End of the stats window. Defaults to now. - status: The status of an external ad. + status: Only return ads with this status. extra_headers: Send extra headers @@ -220,7 +328,7 @@ def list( """ return self._get_api_list( "/ads", - page=SyncCursorPage[AdListResponse], + page=SyncCursorPage[Ad], options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, @@ -228,22 +336,17 @@ def list( timeout=timeout, query=maybe_transform( { + "account_id": account_id, "ad_campaign_id": ad_campaign_id, - "ad_campaign_ids": ad_campaign_ids, "ad_group_id": ad_group_id, - "ad_group_ids": ad_group_ids, "after": after, "before": before, - "campaign_id": campaign_id, - "company_id": company_id, "created_after": created_after, "created_before": created_before, "direction": direction, "first": first, "last": last, "order": order, - "order_by": order_by, - "order_direction": order_direction, "query": query, "stats_from": stats_from, "stats_to": stats_to, @@ -252,7 +355,41 @@ def list( ad_list_params.AdListParams, ), ), - model=AdListResponse, + model=Ad, + ) + + 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, + ) -> AdDeleteResponse: + """Deletes (discards) an ad. + + Returns true on success. + + 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("/ads/{id}", id=id), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=AdDeleteResponse, ) def pause( @@ -267,12 +404,7 @@ def pause( timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> Ad: """ - Pauses an ad. - - Required permissions: - - - `ad_campaign:update` - - `ad_campaign:basic:read` + Pauses an active ad. Args: extra_headers: Send extra headers @@ -307,11 +439,6 @@ def unpause( """ Resumes a paused ad. - Required permissions: - - - `ad_campaign:update` - - `ad_campaign:basic:read` - Args: extra_headers: Send extra headers @@ -333,8 +460,6 @@ def unpause( class AsyncAdsResource(AsyncAPIResource): - """Ads""" - @cached_property def with_raw_response(self) -> AsyncAdsResourceWithRawResponse: """ @@ -354,12 +479,23 @@ def with_streaming_response(self) -> AsyncAdsResourceWithStreamingResponse: """ return AsyncAdsResourceWithStreamingResponse(self) - async def retrieve( + async def create( self, - id: str, *, - stats_from: Union[str, datetime, None] | Omit = omit, - stats_to: Union[str, datetime, None] | Omit = omit, + ad_group: object | Omit = omit, + ad_group_id: str | Omit = omit, + call_to_action: Literal[ + "shop_now", "learn_more", "sign_up", "subscribe", "order_now", "get_offer", "see_details" + ] + | Omit = omit, + creatives: Iterable[ad_create_params.Creative] | Omit = omit, + descriptions: SequenceNotStr[str] | Omit = omit, + headlines: SequenceNotStr[str] | Omit = omit, + primary_texts: SequenceNotStr[str] | Omit = omit, + social_accounts: Iterable[ad_create_params.SocialAccount] | Omit = omit, + title: str | Omit = omit, + url: str | Omit = omit, + url_parameters: object | 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, @@ -368,18 +504,86 @@ async def retrieve( timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> Ad: """ - Retrieve an ad by its unique identifier. + Creates an ad in an ad group. + + Args: + ad_group: An inline ad group to create (same shape as POST /ad_groups, including + ad_campaign_id). Creates the ad group and the ad together. Provide this OR + ad_group_id. + + ad_group_id: The existing ad group to create the ad in. Provide this OR ad_group, not both. + + call_to_action: The call-to-action button shown on the ad. + + creatives: The ad's creatives. Each entry is an uploaded file id with an optional format; + omit format for the original/uncropped asset. + + descriptions: The description variants shown on the ad. + + headlines: The headline variants shown on the ad. + + primary_texts: The primary text variants shown in the ad body. + + social_accounts: The social accounts (Facebook page, Instagram profile) the ad runs under. - Required permissions: + title: The display name of the ad. + + url: The URL the ad links to. + + url_parameters: Query parameters appended to the destination URL, as a string-to-string map. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request - - `ad_campaign:basic:read` + timeout: Override the client-level default timeout for this request, in seconds + """ + return await self._post( + "/ads", + body=await async_maybe_transform( + { + "ad_group": ad_group, + "ad_group_id": ad_group_id, + "call_to_action": call_to_action, + "creatives": creatives, + "descriptions": descriptions, + "headlines": headlines, + "primary_texts": primary_texts, + "social_accounts": social_accounts, + "title": title, + "url": url, + "url_parameters": url_parameters, + }, + ad_create_params.AdCreateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=Ad, + ) + + async def retrieve( + self, + id: str, + *, + stats_from: str | Omit = omit, + stats_to: 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, + ) -> Ad: + """ + Retrieves a single ad with stats over the requested window. Args: - stats_from: Inclusive start of the window for the ad's metric fields (spend, impressions, - …). Omit both statsFrom and statsTo for all-time stats. + stats_from: Start of the stats window. - stats_to: Inclusive end of the window for the ad's metric fields. Omit both statsFrom and - statsTo for all-time stats. + stats_to: End of the stats window. extra_headers: Send extra headers @@ -409,109 +613,143 @@ async def retrieve( cast_to=Ad, ) - def list( + async def update( self, + id: str, *, - ad_campaign_id: Optional[str] | Omit = omit, - ad_campaign_ids: Optional[SequenceNotStr[str]] | Omit = omit, - ad_group_id: Optional[str] | Omit = omit, - ad_group_ids: Optional[SequenceNotStr[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, - direction: Optional[Direction] | Omit = omit, - first: Optional[int] | Omit = omit, - last: Optional[int] | Omit = omit, - order: Optional[ - Literal[ - "created_at", - "spend", - "impressions", - "clicks", - "reach", - "unique_clicks", - "results", - "click_through_rate", - "cost_per_click", - "cost_per_mille", - "cost_per_result", - "frequency", - "return_on_ad_spend", - ] + call_to_action: Literal[ + "shop_now", "learn_more", "sign_up", "subscribe", "order_now", "get_offer", "see_details" ] | Omit = omit, - order_by: Optional[Literal["spend", "return_on_ad_spend", "roas"]] | Omit = omit, - order_direction: Optional[Direction] | Omit = omit, - query: Optional[str] | Omit = omit, - stats_from: Union[str, datetime, None] | Omit = omit, - stats_to: Union[str, datetime, None] | Omit = omit, - status: Optional[ExternalAdStatus] | Omit = omit, + creatives: Iterable[ad_update_params.Creative] | Omit = omit, + descriptions: SequenceNotStr[str] | Omit = omit, + headlines: SequenceNotStr[str] | Omit = omit, + primary_texts: SequenceNotStr[str] | Omit = omit, + social_accounts: Iterable[ad_update_params.SocialAccount] | Omit = omit, + title: str | Omit = omit, + url: str | Omit = omit, + url_parameters: object | 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]]: + ) -> Ad: """ - List ads scoped by ad group, campaign, or company. + Updates an ad's editable fields. - Required permissions: + Args: + call_to_action: The call-to-action button shown on the ad. - - `ad_campaign:basic:read` + creatives: The ad's creatives. Each entry is an uploaded file id with an optional format; + omit format for the original/uncropped asset. Replaces a live ad's creative on + the platform. - Args: - ad_campaign_id: Filter by ad campaign. Provide exactly one of ad_group_id, ad_campaign_id, or - company_id. + descriptions: The description variants shown on the ad. - ad_campaign_ids: Only return ads belonging to these ad campaigns (max 100). Can be combined with - companyId or used on its own. + headlines: The headline variants shown on the ad. - ad_group_id: Filter by ad group. Provide exactly one of ad_group_id, ad_campaign_id, or - company_id. + primary_texts: The primary text variants shown in the ad body. - ad_group_ids: Only return ads belonging to these ad groups (max 100). Can be combined with - companyId or used on its own. + social_accounts: The social accounts the ad runs under. - after: Returns the elements in the list that come after the specified cursor. + title: The display name of the ad. - before: Returns the elements in the list that come before the specified cursor. + url: The URL the ad links to. - campaign_id: Filter by campaign. + url_parameters: Query parameters appended to the destination URL, as a string-to-string map. - company_id: Filter by company. Provide exactly one of ad_group_id, ad_campaign_id, or - company_id. + extra_headers: Send extra headers - created_after: Only return ads created after this timestamp. + extra_query: Add additional query parameters to the request - created_before: Only return ads created before this timestamp. + 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("/ads/{id}", id=id), + body=await async_maybe_transform( + { + "call_to_action": call_to_action, + "creatives": creatives, + "descriptions": descriptions, + "headlines": headlines, + "primary_texts": primary_texts, + "social_accounts": social_accounts, + "title": title, + "url": url, + "url_parameters": url_parameters, + }, + ad_update_params.AdUpdateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=Ad, + ) + + def list( + self, + *, + account_id: str | Omit = omit, + ad_campaign_id: str | Omit = omit, + ad_group_id: str | Omit = omit, + after: str | Omit = omit, + before: str | Omit = omit, + created_after: str | Omit = omit, + created_before: str | Omit = omit, + direction: Literal["asc", "desc"] | Omit = omit, + first: int | Omit = omit, + last: int | Omit = omit, + order: Literal["created_at", "updated_at"] | Omit = omit, + query: str | Omit = omit, + stats_from: str | Omit = omit, + stats_to: str | Omit = omit, + status: Literal["active", "paused", "in_review", "rejected"] | 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[Ad, AsyncCursorPage[Ad]]: + """ + Lists the ads for an account, with stats over the requested window. + + Args: + account_id: The account the ads belong to. Defaults to the account-scoped key's own account. - direction: The direction of the sort. + ad_campaign_id: Only return ads in this ad campaign. - first: Returns the first _n_ elements from the list. + ad_group_id: Only return ads in this ad group. - last: Returns the last _n_ elements from the list. + after: Cursor to fetch the page after (from page_info.end_cursor). - order: The fields the ads dashboard lists (campaigns, ad sets) can be ordered by. Stat - columns are computed over the provided stats date range. + before: Cursor to fetch the page before (from page_info.start_cursor). + + created_after: Only return ads created after this timestamp. + + created_before: Only return ads created before this timestamp. - order_by: Columns that the listAds query can sort by. Deprecated — use AdStatOrder. + direction: The sort direction. Defaults to desc. - order_direction: The direction of the sort. + first: The number of ads to return. - query: Case-insensitive substring match against the ad title or ID. + last: The number of ads to return from the end of the range. - stats_from: Inclusive start of the window for each ad's metric fields (spend, impressions, - …) and for stats-column sorting. Omit both statsFrom and statsTo for all-time - stats. + order: The field to sort by. Defaults to created_at. - stats_to: Inclusive end of the window for each ad's metric fields and for stats-column - sorting. Omit both statsFrom and statsTo for all-time stats. + query: Filter ads by a title or ID substring. - status: The status of an external ad. + stats_from: Start of the stats window. Defaults to all-time. + + stats_to: End of the stats window. Defaults to now. + + status: Only return ads with this status. extra_headers: Send extra headers @@ -523,7 +761,7 @@ def list( """ return self._get_api_list( "/ads", - page=AsyncCursorPage[AdListResponse], + page=AsyncCursorPage[Ad], options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, @@ -531,22 +769,17 @@ def list( timeout=timeout, query=maybe_transform( { + "account_id": account_id, "ad_campaign_id": ad_campaign_id, - "ad_campaign_ids": ad_campaign_ids, "ad_group_id": ad_group_id, - "ad_group_ids": ad_group_ids, "after": after, "before": before, - "campaign_id": campaign_id, - "company_id": company_id, "created_after": created_after, "created_before": created_before, "direction": direction, "first": first, "last": last, "order": order, - "order_by": order_by, - "order_direction": order_direction, "query": query, "stats_from": stats_from, "stats_to": stats_to, @@ -555,7 +788,41 @@ def list( ad_list_params.AdListParams, ), ), - model=AdListResponse, + model=Ad, + ) + + 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, + ) -> AdDeleteResponse: + """Deletes (discards) an ad. + + Returns true on success. + + 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("/ads/{id}", id=id), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=AdDeleteResponse, ) async def pause( @@ -570,12 +837,7 @@ async def pause( timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> Ad: """ - Pauses an ad. - - Required permissions: - - - `ad_campaign:update` - - `ad_campaign:basic:read` + Pauses an active ad. Args: extra_headers: Send extra headers @@ -610,11 +872,6 @@ async def unpause( """ Resumes a paused ad. - Required permissions: - - - `ad_campaign:update` - - `ad_campaign:basic:read` - Args: extra_headers: Send extra headers @@ -639,12 +896,21 @@ 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.update = to_raw_response_wrapper( + ads.update, + ) self.list = to_raw_response_wrapper( ads.list, ) + self.delete = to_raw_response_wrapper( + ads.delete, + ) self.pause = to_raw_response_wrapper( ads.pause, ) @@ -657,12 +923,21 @@ 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.update = async_to_raw_response_wrapper( + ads.update, + ) self.list = async_to_raw_response_wrapper( ads.list, ) + self.delete = async_to_raw_response_wrapper( + ads.delete, + ) self.pause = async_to_raw_response_wrapper( ads.pause, ) @@ -675,12 +950,21 @@ 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.update = to_streamed_response_wrapper( + ads.update, + ) self.list = to_streamed_response_wrapper( ads.list, ) + self.delete = to_streamed_response_wrapper( + ads.delete, + ) self.pause = to_streamed_response_wrapper( ads.pause, ) @@ -693,12 +977,21 @@ 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.update = async_to_streamed_response_wrapper( + ads.update, + ) self.list = async_to_streamed_response_wrapper( ads.list, ) + self.delete = async_to_streamed_response_wrapper( + ads.delete, + ) self.pause = async_to_streamed_response_wrapper( ads.pause, ) diff --git a/src/whop_sdk/types/__init__.py b/src/whop_sdk/types/__init__.py index 58d2f5d6..b5fcc2f8 100644 --- a/src/whop_sdk/types/__init__.py +++ b/src/whop_sdk/types/__init__.py @@ -91,7 +91,6 @@ from .review_status import ReviewStatus as ReviewStatus from .upload_status import UploadStatus as UploadStatus from .webhook_event import WebhookEvent as WebhookEvent -from .ad_budget_type import AdBudgetType as AdBudgetType from .ad_list_params import AdListParams as AdListParams from .cancel_options import CancelOptions as CancelOptions from .checkout_modes import CheckoutModes as CheckoutModes @@ -99,13 +98,13 @@ from .course_chapter import CourseChapter as CourseChapter from .promo_duration import PromoDuration as PromoDuration from .social_account import SocialAccount as SocialAccount -from .ad_group_status import AdGroupStatus as AdGroupStatus from .app_list_params import AppListParams as AppListParams from .authorized_user import AuthorizedUser as AuthorizedUser from .billing_reasons import BillingReasons as BillingReasons from .fee_markup_type import FeeMarkupType as FeeMarkupType from .file_visibility import FileVisibility as FileVisibility -from .ad_list_response import AdListResponse as AdListResponse +from .ad_create_params import AdCreateParams as AdCreateParams +from .ad_update_params import AdUpdateParams as AdUpdateParams from .card_list_params import CardListParams as CardListParams from .dispute_statuses import DisputeStatuses as DisputeStatuses from .lead_list_params import LeadListParams as LeadListParams @@ -122,14 +121,13 @@ from .result_label_keys import ResultLabelKeys as ResultLabelKeys from .withdrawal_speeds import WithdrawalSpeeds as WithdrawalSpeeds from .withdrawal_status import WithdrawalStatus as WithdrawalStatus -from .ad_campaign_status import AdCampaignStatus as AdCampaignStatus +from .ad_delete_response import AdDeleteResponse as AdDeleteResponse from .ad_retrieve_params import AdRetrieveParams as AdRetrieveParams from .bounty_list_params import BountyListParams as BountyListParams from .card_create_params import CardCreateParams as CardCreateParams from .card_list_response import CardListResponse as CardListResponse from .course_list_params import CourseListParams as CourseListParams from .dispute_alert_type import DisputeAlertType as DisputeAlertType -from .external_ad_status import ExternalAdStatus as ExternalAdStatus from .file_create_params import FileCreateParams as FileCreateParams from .lead_create_params import LeadCreateParams as LeadCreateParams from .lead_list_response import LeadListResponse as LeadListResponse @@ -164,7 +162,6 @@ 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_campaign_platform import AdCampaignPlatform as AdCampaignPlatform from .ad_group_list_params import AdGroupListParams as AdGroupListParams from .bounty_create_params import BountyCreateParams as BountyCreateParams from .bounty_list_response import BountyListResponse as BountyListResponse @@ -220,6 +217,7 @@ 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 @@ -262,7 +260,6 @@ from .webhook_create_response import WebhookCreateResponse as WebhookCreateResponse from .webhook_delete_response import WebhookDeleteResponse as WebhookDeleteResponse from .ad_group_delete_response import AdGroupDeleteResponse as AdGroupDeleteResponse -from .ad_group_retrieve_params import AdGroupRetrieveParams as AdGroupRetrieveParams 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 @@ -296,7 +293,7 @@ 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_list_response import AdCampaignListResponse as AdCampaignListResponse +from .ad_campaign_create_params import AdCampaignCreateParams as AdCampaignCreateParams from .ad_campaign_update_params import AdCampaignUpdateParams as AdCampaignUpdateParams from .ad_report_retrieve_params import AdReportRetrieveParams as AdReportRetrieveParams from .assessment_question_types import AssessmentQuestionTypes as AssessmentQuestionTypes diff --git a/src/whop_sdk/types/ad.py b/src/whop_sdk/types/ad.py index 9c405ea6..8e64293f 100644 --- a/src/whop_sdk/types/ad.py +++ b/src/whop_sdk/types/ad.py @@ -1,169 +1,159 @@ # 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 -from .shared.currency import Currency -from .external_ad_status import ExternalAdStatus -from .ad_campaign_platform import AdCampaignPlatform -__all__ = ["Ad", "AdCampaign", "AdGroup", "Issue"] +__all__ = ["Ad", "Issue"] -class AdCampaign(BaseModel): - """The ad campaign this ad belongs to.""" - - id: str - """The unique identifier for this ad campaign.""" - - -class AdGroup(BaseModel): - """The parent ad group this ad belongs to.""" +class Issue(BaseModel): + """Open issues affecting this ad. Empty when there are none.""" id: str - """The unique identifier for this ad group.""" - - -class Issue(BaseModel): - """A platform-reported issue on an ad object (rejection, policy flag, etc.).""" + """Unique identifier for the issue.""" category: Optional[Literal["policy_rejection", "creative_media", "audience_targeting", "ad_volume_limit"]] = None - """Whop's canonical category that a raw platform issue is bucketed into.""" - - created_at: datetime - """When the issue was first reported.""" - - resolution_status: Literal["open", "resolved", "acknowledged"] - """Current resolution status.""" + """The kind of problem the issue represents.""" resource_id: Optional[str] = None - """The Whop ID of the ad object this issue is on (the ad, ad group, or campaign). + """The ID of the campaign, ad group, or ad the issue is attached to.""" - Null when the issue isn't tied to a local object. - """ - - resource_type: str - """The kind of ad object this issue is on: `ad`, `ad_group`, or `ad_campaign`. - - Pairs with `resourceId`. - """ - - subtype: Optional[str] = None - """Finer-grained sub-bucket within the category (e.g. - - the specific Meta policy for a rejection). - """ + resource_type: Literal["ad_campaign", "ad_group", "ad"] + """The type of resource the issue is attached to.""" class Ad(BaseModel): - """An ad belonging to an ad group.""" - id: str - """The unique identifier for this ad.""" - - ad_campaign: AdCampaign - """The ad campaign this ad belongs to.""" - - ad_group: AdGroup - """The parent ad group this ad belongs to.""" + """Unique identifier for the ad.""" + + ad_campaign: object + """The ad campaign this ad belongs to, an object with an id.""" + + ad_group: object + """The ad group this ad belongs to, an object with an id.""" + + call_to_action: 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", + "see_details", + "view_instagram_profile", + ] + ] = None + """The call-to-action button shown on the ad.""" click_through_rate: float - """Click-through rate as a fraction of impressions (clicks / impressions, 0–1).""" + """Clicks divided by impressions, between 0 and 1.""" - clicks: int - """Total clicks on this ad in the stats window.""" + clicks: float + """The number of clicks.""" cost_per_click: float - """Cost per click in dollars (spend / clicks). 0 when there are no clicks.""" + """Spend divided by clicks; 0 when there are no clicks.""" cost_per_lead: Optional[float] = None - """Cost in dollars per Whop pixel-attributed lead (spend / leads). - - 0 when leads are tracked but none happened yet; null when leads are not a goal - and none were attributed. + """ + Spend divided by attributed leads; null when leads are not a goal and none are + attributed. """ cost_per_mille: float - """Cost per 1,000 impressions in dollars (spend / impressions × 1000). - - 0 when there are no impressions. - """ + """Spend per 1,000 impressions; 0 when there are no impressions.""" cost_per_purchase: Optional[float] = None - """Cost in dollars per Whop pixel-attributed purchase (spend / purchases). - - 0 when purchases are tracked but none happened yet; null when purchases are not - a goal and none were attributed. + """ + Spend divided by attributed purchases; null when purchases are not a goal and + none are attributed. """ - cost_per_result: Optional[float] = None - """Cost in dollars per optimization result (spend / results). + created_at: str + """When the ad was created, as an ISO 8601 timestamp.""" - 0 when a result is being optimized for but none happened yet; null when nothing - is being optimized for. - """ + creatives: List[object] - created_at: datetime - """When the ad was created.""" + descriptions: List[str] frequency: Optional[float] = None - """ - Average number of times each person saw an ad (impressions / reach), as reported - by the platform. - """ + """Platform-reported impressions divided by reach.""" - impressions: int - """Total impressions (views) on this ad in the stats window.""" + headlines: List[str] - issues: List[Issue] - """Open platform issues affecting this ad, deduplicated per object. + impressions: float + """The number of impressions.""" - Empty when there are none. - """ + issues: List[Issue] - leads: int - """Number of Whop pixel-attributed leads (last-click) in the stats window.""" + leads: float + """Whop pixel-attributed leads, last-click.""" - platform: AdCampaignPlatform - """The external ad platform this ad is running on (e.g., meta, tiktok).""" + primary_texts: List[str] purchase_value: float - """Total USD value of Whop pixel-attributed purchases in the stats window.""" + """USD value of pixel-attributed purchases.""" - purchases: int - """Number of Whop pixel-attributed purchases (last-click) in the stats window.""" + purchases: float + """Whop pixel-attributed purchases, last-click.""" - reach: int - """Unique users reached in the stats window (deduplicated by the platform).""" + reach: float + """The number of unique people who saw this.""" return_on_ad_spend: float - """ - Return on ad spend as a ratio (purchaseValue / spend) — 2.5 means $2.50 of - attributed purchase value per $1 spent. 0 when there is no spend. - """ + """Purchase value divided by spend; 0 when there is no spend.""" + + social_accounts: List[object] spend: float - """Amount charged in dollars in the stats window.""" + """The amount charged, in spend_currency.""" - spend_currency: Optional[Currency] = None - """The available currencies on the platform""" + spend_currency: Optional[str] = None + """The ISO 4217 currency code of all monetary metrics.""" - status: ExternalAdStatus - """Current delivery status of the ad.""" + status: Literal["active", "paused", "in_review", "rejected"] + """The delivery status of the ad.""" title: Optional[str] = None """The display title of the ad. Falls back to the creative set caption when unset.""" unique_click_through_rate: Optional[float] = None - """ - Unique click-through rate as a fraction of impressions (unique clicks / - impressions, 0–1). - """ + """Unique clicks divided by impressions, between 0 and 1.""" + + unique_clicks: float + """The number of unique clicks.""" + + updated_at: str + """When the ad was last updated, as an ISO 8601 timestamp.""" - unique_clicks: int - """Unique clicks (deduplicated by the platform) in the stats window.""" + url: Optional[str] = None + """The URL the ad links to.""" - updated_at: datetime - """When the ad was last updated.""" + url_parameters: object + """Query parameters appended to the URL, as a string-to-string map.""" diff --git a/src/whop_sdk/types/ad_budget_type.py b/src/whop_sdk/types/ad_budget_type.py deleted file mode 100644 index d3eee5dd..00000000 --- a/src/whop_sdk/types/ad_budget_type.py +++ /dev/null @@ -1,7 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from typing_extensions import Literal, TypeAlias - -__all__ = ["AdBudgetType"] - -AdBudgetType: TypeAlias = Literal["daily", "lifetime"] diff --git a/src/whop_sdk/types/ad_campaign.py b/src/whop_sdk/types/ad_campaign.py index 80f875e1..f54443fe 100644 --- a/src/whop_sdk/types/ad_campaign.py +++ b/src/whop_sdk/types/ad_campaign.py @@ -1,156 +1,133 @@ # 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 -from .ad_budget_type import AdBudgetType -from .shared.currency import Currency -from .ad_campaign_status import AdCampaignStatus -from .ad_campaign_platform import AdCampaignPlatform __all__ = ["AdCampaign", "Issue"] class Issue(BaseModel): - """A platform-reported issue on an ad object (rejection, policy flag, etc.).""" + """Open issues affecting the campaign and its descendant ad groups and ads.""" - category: Optional[Literal["policy_rejection", "creative_media", "audience_targeting", "ad_volume_limit"]] = None - """Whop's canonical category that a raw platform issue is bucketed into.""" - - created_at: datetime - """When the issue was first reported.""" + id: str + """Unique identifier for the issue.""" - resolution_status: Literal["open", "resolved", "acknowledged"] - """Current resolution status.""" + category: Optional[Literal["policy_rejection", "creative_media", "audience_targeting", "ad_volume_limit"]] = None + """The kind of problem the issue represents.""" resource_id: Optional[str] = None - """The Whop ID of the ad object this issue is on (the ad, ad group, or campaign). + """The ID of the campaign, ad group, or ad the issue is attached to.""" - Null when the issue isn't tied to a local object. - """ + resource_type: Literal["ad_campaign", "ad_group", "ad"] + """The type of resource the issue is attached to.""" - resource_type: str - """The kind of ad object this issue is on: `ad`, `ad_group`, or `ad_campaign`. - Pairs with `resourceId`. - """ - - subtype: Optional[str] = None - """Finer-grained sub-bucket within the category (e.g. - - the specific Meta policy for a rejection). - """ +class AdCampaign(BaseModel): + id: str + """Unique identifier for the ad campaign.""" + bid_type: Optional[Literal["minimum_cost", "average_target", "maximum_target"]] = None + """The bidding strategy the campaign uses.""" -class AdCampaign(BaseModel): - """An advertising campaign running on an external platform or within Whop.""" + budget_amount: Optional[float] = None + """The campaign budget in USD. - id: str - """The unique identifier for this ad campaign.""" + Null when budget is set at the ad group level (ABO). + """ - budget: Optional[float] = None - """Total budget in dollars.""" + budget_optimization: Optional[Literal["ad_campaign", "ad_group"]] = None + """Which level owns the budget — the campaign (CBO) or each ad group (ABO).""" - budget_type: Optional[AdBudgetType] = None - """The budget type for an ad campaign or ad group.""" + budget_type: Optional[Literal["daily", "lifetime"]] = None + """Whether the budget is spent per day or over the campaign's lifetime.""" click_through_rate: float - """Click-through rate as a fraction of impressions (clicks / impressions, 0–1).""" + """Clicks divided by impressions, between 0 and 1.""" - clicks: int - """Total clicks on the campaign's ads in the stats window.""" + clicks: float + """The number of clicks for all ads within this campaign.""" cost_per_click: float - """Cost per click in dollars (spend / clicks). 0 when there are no clicks.""" + """Spend divided by clicks; 0 when there are no clicks.""" cost_per_lead: Optional[float] = None - """Cost in dollars per Whop pixel-attributed lead (spend / leads). - - 0 when leads are tracked but none happened yet; null when leads are not a goal - and none were attributed. + """ + Spend divided by attributed leads; null when leads are not a goal and none are + attributed. """ cost_per_mille: float - """Cost per 1,000 impressions in dollars (spend / impressions × 1000). - - 0 when there are no impressions. - """ + """Spend per 1,000 impressions; 0 when there are no impressions.""" cost_per_purchase: Optional[float] = None - """Cost in dollars per Whop pixel-attributed purchase (spend / purchases). - - 0 when purchases are tracked but none happened yet; null when purchases are not - a goal and none were attributed. + """ + Spend divided by attributed purchases; null when purchases are not a goal and + none are attributed. """ cost_per_result: Optional[float] = None - """Cost in dollars per optimization result (spend / results). + """Spend divided by results; null when nothing is being optimized for.""" - 0 when a result is being optimized for but none happened yet; null when nothing - is being optimized for. - """ - - created_at: datetime - """When the ad campaign was created.""" + created_at: str + """When the campaign was created, as an ISO 8601 timestamp.""" frequency: Optional[float] = None - """ - Average number of times each person saw an ad (impressions / reach), as reported - by the platform. - """ + """Platform-reported impressions divided by reach.""" - impressions: int - """Total impressions (views) on the campaign's ads in the stats window.""" + impressions: float + """The number of impressions.""" issues: List[Issue] - """ - Open platform issues affecting this campaign and its descendant ad groups and - ads, deduplicated per object. Empty when there are none. - """ - leads: int - """Number of Whop pixel-attributed leads (last-click) in the stats window.""" + leads: float + """Whop pixel-attributed leads, last-click.""" + + objective: Optional[Literal["awareness", "traffic", "engagement", "leads", "sales"]] = None + """The goal the campaign optimizes toward.""" - platform: AdCampaignPlatform - """The external ad platform this campaign is running on (e.g., meta, tiktok).""" + optimization_goal: Optional[str] = None + """The specific event the campaign optimizes for. + + If the campaign is CBO, then all ad groups will have the same optimization goal, + which will be returned here. + """ + + platform: Literal["meta"] + """The ad network the campaign runs on.""" purchase_value: float - """Total USD value of Whop pixel-attributed purchases in the stats window.""" + """USD value of pixel-attributed purchases.""" - purchases: int - """Number of Whop pixel-attributed purchases (last-click) in the stats window.""" + purchases: float + """Whop pixel-attributed purchases, last-click.""" - reach: int - """Unique users reached in the stats window (deduplicated by the platform).""" + reach: float + """The number of unique people who saw an ad.""" return_on_ad_spend: float - """ - Return on ad spend as a ratio (purchaseValue / spend) — 2.5 means $2.50 of - attributed purchase value per $1 spent. 0 when there is no spend. - """ + """Purchase value divided by spend; 0 when there is no spend.""" + + special_ad_categories: List[Literal["housing", "employment", "financial_products", "politics"]] spend: float - """Amount charged in dollars in the stats window.""" + """The amount charged, in spend_currency.""" - spend_currency: Optional[Currency] = None - """The available currencies on the platform""" + spend_currency: Optional[str] = None + """The ISO 4217 currency code of all monetary metrics.""" - status: AdCampaignStatus - """Current status of the campaign.""" + status: Literal["draft", "active", "paused", "payment_failed"] + """The lifecycle status of the ad campaign.""" title: str - """The campaign name shown in the Whop dashboard.""" + """The title of the ad campaign.""" unique_click_through_rate: Optional[float] = None - """ - Unique click-through rate as a fraction of impressions (unique clicks / - impressions, 0–1). - """ + """Unique clicks divided by impressions, between 0 and 1.""" - unique_clicks: int - """Unique clicks (deduplicated by the platform) in the stats window.""" + unique_clicks: float + """The number of unique clicks.""" - updated_at: datetime - """When the ad campaign was last updated.""" + updated_at: str + """When the campaign was last updated, as an ISO 8601 timestamp.""" 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 00000000..7240e233 --- /dev/null +++ b/src/whop_sdk/types/ad_campaign_create_params.py @@ -0,0 +1,49 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import List +from typing_extensions import Literal, Required, TypedDict + +__all__ = ["AdCampaignCreateParams"] + + +class AdCampaignCreateParams(TypedDict, total=False): + objective: Required[Literal["awareness", "traffic", "engagement", "leads", "sales"]] + """The goal the campaign optimizes toward.""" + + platform: Required[Literal["meta"]] + """The ad network the campaign runs on.""" + + title: Required[str] + """The title of the campaign.""" + + account_id: str + """The account to create the campaign under. + + Defaults to the account-scoped key's own account. + """ + + budget_amount: float + """The campaign budget, in USD. + + Required for CBO (budget_optimization: ad_campaign); omit for ABO. + """ + + budget_optimization: Literal["ad_campaign", "ad_group"] + """Which level owns the budget — the campaign (CBO) or each ad group (ABO). + + Defaults to ad_group. + """ + + budget_type: Literal["daily", "lifetime"] + """Whether the budget is spent per day or over the campaign's lifetime. + + Defaults to daily. + """ + + special_ad_categories: List[Literal["housing", "employment", "financial_products", "politics"]] + """Regulated categories the campaign falls under. + + Ads in these categories are subject to extra targeting restrictions. + """ diff --git a/src/whop_sdk/types/ad_campaign_list_params.py b/src/whop_sdk/types/ad_campaign_list_params.py index 526ecf84..2c0ad3c3 100644 --- a/src/whop_sdk/types/ad_campaign_list_params.py +++ b/src/whop_sdk/types/ad_campaign_list_params.py @@ -2,78 +2,50 @@ from __future__ import annotations -from typing import Union, Optional -from datetime import datetime -from typing_extensions import Literal, Annotated, TypedDict - -from .._utils import PropertyInfo -from .shared.direction import Direction -from .ad_campaign_status import AdCampaignStatus +from typing_extensions import Literal, TypedDict __all__ = ["AdCampaignListParams"] class AdCampaignListParams(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.""" - - company_id: Optional[str] - """The unique identifier of the company to list ad campaigns for.""" - - 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.""" - - direction: Optional[Direction] - """The direction of the sort.""" - - first: Optional[int] - """Returns the first _n_ elements from the list.""" - - last: Optional[int] - """Returns the last _n_ elements from the list.""" - - order: Optional[ - Literal[ - "created_at", - "spend", - "impressions", - "clicks", - "reach", - "unique_clicks", - "results", - "click_through_rate", - "cost_per_click", - "cost_per_mille", - "cost_per_result", - "frequency", - "return_on_ad_spend", - ] - ] - """The fields the ads dashboard lists (campaigns, ad sets) can be ordered by. - - Stat columns are computed over the provided stats date range. + account_id: str + """The account the campaigns belong to. + + Defaults to the account-scoped key's own account. """ - query: Optional[str] - """Case-insensitive substring match against the campaign title or ID.""" + after: str + """Cursor to fetch the page after (from page_info.end_cursor).""" - stats_from: Annotated[Union[str, datetime, None], PropertyInfo(format="iso8601")] - """ - Inclusive start of the window for each campaign's metric fields (spend, - impressions, …). Omit both statsFrom and statsTo for all-time stats. - """ + before: str + """Cursor to fetch the page before (from page_info.start_cursor).""" - stats_to: Annotated[Union[str, datetime, None], PropertyInfo(format="iso8601")] - """Inclusive end of the window for each campaign's metric fields. + created_after: str + """Only return campaigns created after this timestamp.""" - Omit both statsFrom and statsTo for all-time stats. - """ + created_before: str + """Only return campaigns created before this timestamp.""" + + direction: Literal["asc", "desc"] + """The sort direction. Defaults to desc.""" + + first: int + """The number of campaigns to return.""" + + last: int + """The number of campaigns to return from the end of the range.""" + + order: Literal["created_at", "updated_at"] + """The field to sort by. Defaults to created_at.""" + + query: str + """Filter campaigns by a title or ID substring.""" + + stats_from: str + """Start of the stats window. Defaults to all-time.""" + + stats_to: str + """End of the stats window. Defaults to now.""" - status: Optional[AdCampaignStatus] - """The status of an ad campaign.""" + status: Literal["draft", "active", "paused", "payment_failed"] + """Only return campaigns with this status.""" diff --git a/src/whop_sdk/types/ad_campaign_list_response.py b/src/whop_sdk/types/ad_campaign_list_response.py deleted file mode 100644 index 731878bf..00000000 --- a/src/whop_sdk/types/ad_campaign_list_response.py +++ /dev/null @@ -1,156 +0,0 @@ -# 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 -from .ad_budget_type import AdBudgetType -from .shared.currency import Currency -from .ad_campaign_status import AdCampaignStatus -from .ad_campaign_platform import AdCampaignPlatform - -__all__ = ["AdCampaignListResponse", "Issue"] - - -class Issue(BaseModel): - """A platform-reported issue on an ad object (rejection, policy flag, etc.).""" - - category: Optional[Literal["policy_rejection", "creative_media", "audience_targeting", "ad_volume_limit"]] = None - """Whop's canonical category that a raw platform issue is bucketed into.""" - - created_at: datetime - """When the issue was first reported.""" - - resolution_status: Literal["open", "resolved", "acknowledged"] - """Current resolution status.""" - - resource_id: Optional[str] = None - """The Whop ID of the ad object this issue is on (the ad, ad group, or campaign). - - Null when the issue isn't tied to a local object. - """ - - resource_type: str - """The kind of ad object this issue is on: `ad`, `ad_group`, or `ad_campaign`. - - Pairs with `resourceId`. - """ - - subtype: Optional[str] = None - """Finer-grained sub-bucket within the category (e.g. - - the specific Meta policy for a rejection). - """ - - -class AdCampaignListResponse(BaseModel): - """An advertising campaign running on an external platform or within Whop.""" - - id: str - """The unique identifier for this ad campaign.""" - - budget: Optional[float] = None - """Total budget in dollars.""" - - budget_type: Optional[AdBudgetType] = None - """The budget type for an ad campaign or ad group.""" - - click_through_rate: float - """Click-through rate as a fraction of impressions (clicks / impressions, 0–1).""" - - clicks: int - """Total clicks on the campaign's ads in the stats window.""" - - cost_per_click: float - """Cost per click in dollars (spend / clicks). 0 when there are no clicks.""" - - cost_per_lead: Optional[float] = None - """Cost in dollars per Whop pixel-attributed lead (spend / leads). - - 0 when leads are tracked but none happened yet; null when leads are not a goal - and none were attributed. - """ - - cost_per_mille: float - """Cost per 1,000 impressions in dollars (spend / impressions × 1000). - - 0 when there are no impressions. - """ - - cost_per_purchase: Optional[float] = None - """Cost in dollars per Whop pixel-attributed purchase (spend / purchases). - - 0 when purchases are tracked but none happened yet; null when purchases are not - a goal and none were attributed. - """ - - cost_per_result: Optional[float] = None - """Cost in dollars per optimization result (spend / results). - - 0 when a result is being optimized for but none happened yet; null when nothing - is being optimized for. - """ - - created_at: datetime - """When the ad campaign was created.""" - - frequency: Optional[float] = None - """ - Average number of times each person saw an ad (impressions / reach), as reported - by the platform. - """ - - impressions: int - """Total impressions (views) on the campaign's ads in the stats window.""" - - issues: List[Issue] - """ - Open platform issues affecting this campaign and its descendant ad groups and - ads, deduplicated per object. Empty when there are none. - """ - - leads: int - """Number of Whop pixel-attributed leads (last-click) in the stats window.""" - - platform: AdCampaignPlatform - """The external ad platform this campaign is running on (e.g., meta, tiktok).""" - - purchase_value: float - """Total USD value of Whop pixel-attributed purchases in the stats window.""" - - purchases: int - """Number of Whop pixel-attributed purchases (last-click) in the stats window.""" - - reach: int - """Unique users reached in the stats window (deduplicated by the platform).""" - - return_on_ad_spend: float - """ - Return on ad spend as a ratio (purchaseValue / spend) — 2.5 means $2.50 of - attributed purchase value per $1 spent. 0 when there is no spend. - """ - - spend: float - """Amount charged in dollars in the stats window.""" - - spend_currency: Optional[Currency] = None - """The available currencies on the platform""" - - status: AdCampaignStatus - """Current status of the campaign.""" - - title: str - """The campaign name shown in the Whop dashboard.""" - - unique_click_through_rate: Optional[float] = None - """ - Unique click-through rate as a fraction of impressions (unique clicks / - impressions, 0–1). - """ - - unique_clicks: int - """Unique clicks (deduplicated by the platform) in the stats window.""" - - updated_at: datetime - """When the ad campaign was last updated.""" diff --git a/src/whop_sdk/types/ad_campaign_platform.py b/src/whop_sdk/types/ad_campaign_platform.py deleted file mode 100644 index 1e841130..00000000 --- a/src/whop_sdk/types/ad_campaign_platform.py +++ /dev/null @@ -1,7 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from typing_extensions import Literal, TypeAlias - -__all__ = ["AdCampaignPlatform"] - -AdCampaignPlatform: TypeAlias = Literal["meta", "tiktok"] diff --git a/src/whop_sdk/types/ad_campaign_retrieve_params.py b/src/whop_sdk/types/ad_campaign_retrieve_params.py index 4bd49c3b..63e9b2b8 100644 --- a/src/whop_sdk/types/ad_campaign_retrieve_params.py +++ b/src/whop_sdk/types/ad_campaign_retrieve_params.py @@ -2,24 +2,14 @@ from __future__ import annotations -from typing import Union -from datetime import datetime -from typing_extensions import Annotated, TypedDict - -from .._utils import PropertyInfo +from typing_extensions import TypedDict __all__ = ["AdCampaignRetrieveParams"] class AdCampaignRetrieveParams(TypedDict, total=False): - stats_from: Annotated[Union[str, datetime, None], PropertyInfo(format="iso8601")] - """ - Inclusive start of the window for the campaign's metric fields (spend, - impressions, …). Omit both statsFrom and statsTo for all-time stats. - """ - - stats_to: Annotated[Union[str, datetime, None], PropertyInfo(format="iso8601")] - """Inclusive end of the window for the campaign's metric fields. + stats_from: str + """Start of the stats window.""" - Omit both statsFrom and statsTo for all-time stats. - """ + stats_to: str + """End of the stats window.""" diff --git a/src/whop_sdk/types/ad_campaign_status.py b/src/whop_sdk/types/ad_campaign_status.py deleted file mode 100644 index ddfbdf4e..00000000 --- a/src/whop_sdk/types/ad_campaign_status.py +++ /dev/null @@ -1,7 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from typing_extensions import Literal, TypeAlias - -__all__ = ["AdCampaignStatus"] - -AdCampaignStatus: TypeAlias = Literal["active", "paused", "payment_failed", "draft", "in_review", "flagged"] diff --git a/src/whop_sdk/types/ad_campaign_update_params.py b/src/whop_sdk/types/ad_campaign_update_params.py index 63afb37f..e4b4b010 100644 --- a/src/whop_sdk/types/ad_campaign_update_params.py +++ b/src/whop_sdk/types/ad_campaign_update_params.py @@ -2,19 +2,14 @@ from __future__ import annotations -from typing import Optional from typing_extensions import TypedDict __all__ = ["AdCampaignUpdateParams"] class AdCampaignUpdateParams(TypedDict, total=False): - budget: Optional[float] - """The campaign budget in dollars. + budget_amount: float + """The campaign budget, in the account's currency.""" - The interpretation (daily or lifetime) follows the campaign's existing budget - type. - """ - - desired_cpr: Optional[float] - """The advertiser's desired cost per result in dollars.""" + title: str + """The name of the campaign.""" 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 00000000..8d3d2dd5 --- /dev/null +++ b/src/whop_sdk/types/ad_create_params.py @@ -0,0 +1,63 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Iterable +from typing_extensions import Literal, TypedDict + +from .._types import SequenceNotStr + +__all__ = ["AdCreateParams", "Creative", "SocialAccount"] + + +class AdCreateParams(TypedDict, total=False): + ad_group: object + """ + An inline ad group to create (same shape as POST /ad_groups, including + ad_campaign_id). Creates the ad group and the ad together. Provide this OR + ad_group_id. + """ + + ad_group_id: str + """The existing ad group to create the ad in. Provide this OR ad_group, not both.""" + + call_to_action: Literal["shop_now", "learn_more", "sign_up", "subscribe", "order_now", "get_offer", "see_details"] + """The call-to-action button shown on the ad.""" + + creatives: Iterable[Creative] + """The ad's creatives. + + Each entry is an uploaded file id with an optional format; omit format for the + original/uncropped asset. + """ + + descriptions: SequenceNotStr[str] + """The description variants shown on the ad.""" + + headlines: SequenceNotStr[str] + """The headline variants shown on the ad.""" + + primary_texts: SequenceNotStr[str] + """The primary text variants shown in the ad body.""" + + social_accounts: Iterable[SocialAccount] + """The social accounts (Facebook page, Instagram profile) the ad runs under.""" + + title: str + """The display name of the ad.""" + + url: str + """The URL the ad links to.""" + + url_parameters: object + """Query parameters appended to the destination URL, as a string-to-string map.""" + + +class Creative(TypedDict, total=False): + id: str + + format: Literal["square", "vertical", "horizontal"] + + +class SocialAccount(TypedDict, total=False): + id: str diff --git a/src/whop_sdk/types/ad_delete_response.py b/src/whop_sdk/types/ad_delete_response.py new file mode 100644 index 00000000..7bb78972 --- /dev/null +++ b/src/whop_sdk/types/ad_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__ = ["AdDeleteResponse"] + +AdDeleteResponse: TypeAlias = bool diff --git a/src/whop_sdk/types/ad_group.py b/src/whop_sdk/types/ad_group.py index de67e0bb..fb1d5894 100644 --- a/src/whop_sdk/types/ad_group.py +++ b/src/whop_sdk/types/ad_group.py @@ -1,166 +1,171 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -from typing import List, Optional -from datetime import datetime +from typing import List, Union, Optional from typing_extensions import Literal from .._models import BaseModel -from .ad_budget_type import AdBudgetType -from .ad_group_status import AdGroupStatus -from .shared.currency import Currency -from .ad_campaign_platform import AdCampaignPlatform -__all__ = ["AdGroup", "AdCampaign", "Issue"] +__all__ = ["AdGroup", "Issue"] -class AdCampaign(BaseModel): - """The ad campaign this ad group belongs to.""" +class Issue(BaseModel): + """Open issues affecting this ad group. Empty when there are none.""" id: str - """The unique identifier for this ad campaign.""" - - -class Issue(BaseModel): - """A platform-reported issue on an ad object (rejection, policy flag, etc.).""" + """Unique identifier for the issue.""" category: Optional[Literal["policy_rejection", "creative_media", "audience_targeting", "ad_volume_limit"]] = None - """Whop's canonical category that a raw platform issue is bucketed into.""" - - created_at: datetime - """When the issue was first reported.""" - - resolution_status: Literal["open", "resolved", "acknowledged"] - """Current resolution status.""" + """The kind of problem the issue represents.""" resource_id: Optional[str] = None - """The Whop ID of the ad object this issue is on (the ad, ad group, or campaign). - - Null when the issue isn't tied to a local object. - """ + """The ID of the campaign, ad group, or ad the issue is attached to.""" - resource_type: str - """The kind of ad object this issue is on: `ad`, `ad_group`, or `ad_campaign`. - - Pairs with `resourceId`. - """ - - subtype: Optional[str] = None - """Finer-grained sub-bucket within the category (e.g. - - the specific Meta policy for a rejection). - """ + resource_type: Literal["ad_campaign", "ad_group", "ad"] + """The type of resource the issue is attached to.""" class AdGroup(BaseModel): - """An ad group belonging to an ad campaign.""" - id: str - """The unique identifier for this ad group.""" + """Unique identifier for the ad group.""" + + ad_campaign: object + """The ad campaign this ad group belongs to, an object with an id.""" - ad_campaign: AdCampaign - """The ad campaign this ad group belongs to.""" + audience: object + """Demographic targeting: automatic (Advantage+), age range, gender.""" - budget: Optional[float] = None - """Budget amount in dollars.""" + bid_type: Optional[Literal["minimum_cost", "average_target", "maximum_target"]] = None + """Bid strategy.""" - budget_type: Optional[AdBudgetType] = None - """The budget type for an ad campaign or ad group.""" + budget_amount: Optional[float] = None + """Ad-set budget; null when the campaign owns budget (CBO).""" + + budget_type: Optional[Literal["daily", "lifetime"]] = None + """Whether the budget is daily or lifetime.""" click_through_rate: float - """Click-through rate as a fraction of impressions (clicks / impressions, 0–1).""" + """Clicks divided by impressions, between 0 and 1.""" + + clicks: float + """The number of clicks.""" + + conversion_event: Union[ + Literal[ + "purchase", + "add_to_cart", + "initiated_checkout", + "add_payment_info", + "complete_registration", + "lead", + "content_view", + "search", + "contact", + "customize_product", + "donate", + "find_location", + "schedule", + "start_trial", + "submit_application", + "subscribe", + ], + str, + None, + ] = None + """The pixel event optimized for. + + A standard event, or any custom pixel event name. + """ - clicks: int - """Total clicks on this ad group's ads in the stats window.""" + conversion_location: Optional[Literal["website"]] = None + """Where conversions happen.""" cost_per_click: float - """Cost per click in dollars (spend / clicks). 0 when there are no clicks.""" + """Spend divided by clicks; 0 when there are no clicks.""" cost_per_lead: Optional[float] = None - """Cost in dollars per Whop pixel-attributed lead (spend / leads). - - 0 when leads are tracked but none happened yet; null when leads are not a goal - and none were attributed. + """ + Spend divided by attributed leads; null when leads are not a goal and none are + attributed. """ cost_per_mille: float - """Cost per 1,000 impressions in dollars (spend / impressions × 1000). - - 0 when there are no impressions. - """ + """Spend per 1,000 impressions; 0 when there are no impressions.""" cost_per_purchase: Optional[float] = None - """Cost in dollars per Whop pixel-attributed purchase (spend / purchases). - - 0 when purchases are tracked but none happened yet; null when purchases are not - a goal and none were attributed. + """ + Spend divided by attributed purchases; null when purchases are not a goal and + none are attributed. """ - cost_per_result: Optional[float] = None - """Cost in dollars per optimization result (spend / results). + created_at: str + """When the ad group was created, ISO 8601.""" - 0 when a result is being optimized for but none happened yet; null when nothing - is being optimized for. - """ + desired_cost_per_result: Optional[float] = None + """Target/cap cost for average_target / maximum_target.""" - created_at: datetime - """When the ad group was created.""" + devices: object + """Device targeting: platforms and operating systems.""" + + ends_at: Optional[str] = None + """Schedule end, ISO 8601.""" frequency: Optional[float] = None - """ - Average number of times each person saw an ad (impressions / reach), as reported - by the platform. - """ + """Platform-reported impressions divided by reach.""" - impressions: int - """Total impressions (views) on this ad group's ads in the stats window.""" + frequency_cap: Optional[object] = None + """Impression cap; only valid for reach optimization.""" + + impressions: float + """The number of impressions.""" issues: List[Issue] - """ - Open platform issues affecting this ad group and its descendant ads, - deduplicated per object. Empty when there are none. - """ - leads: int - """Number of Whop pixel-attributed leads (last-click) in the stats window.""" + leads: float + """Whop pixel-attributed leads, last-click.""" + + minimum_daily_spend: Optional[float] = None + """Daily spend floor within the budget.""" + + optimization_goal: Optional[str] = None + """What the ad group optimizes for.""" - platform: AdCampaignPlatform - """The external ad platform this ad group is running on (e.g., meta, tiktok).""" + placements: List[object] purchase_value: float - """Total USD value of Whop pixel-attributed purchases in the stats window.""" + """USD value of pixel-attributed purchases.""" - purchases: int - """Number of Whop pixel-attributed purchases (last-click) in the stats window.""" + purchases: float + """Whop pixel-attributed purchases, last-click.""" - reach: int - """Unique users reached in the stats window (deduplicated by the platform).""" + reach: float + """The number of unique people who saw this.""" + + regions: object + """Geo targeting: include/exclude countries, cities, zips.""" return_on_ad_spend: float - """ - Return on ad spend as a ratio (purchaseValue / spend) — 2.5 means $2.50 of - attributed purchase value per $1 spent. 0 when there is no spend. - """ + """Purchase value divided by spend; 0 when there is no spend.""" spend: float - """Amount charged in dollars in the stats window.""" + """The amount charged, in spend_currency.""" + + spend_currency: Optional[str] = None + """The ISO 4217 currency code of all monetary metrics.""" - spend_currency: Optional[Currency] = None - """The available currencies on the platform""" + starts_at: Optional[str] = None + """Schedule start, ISO 8601.""" - status: AdGroupStatus - """Current operational status of the ad group.""" + status: Literal["active", "paused", "rejected"] + """Delivery status of the ad group.""" title: Optional[str] = None - """The ad group name shown in the Whop dashboard.""" + """The display title of the ad group.""" unique_click_through_rate: Optional[float] = None - """ - Unique click-through rate as a fraction of impressions (unique clicks / - impressions, 0–1). - """ + """Unique clicks divided by impressions, between 0 and 1.""" - unique_clicks: int - """Unique clicks (deduplicated by the platform) in the stats window.""" + unique_clicks: float + """The number of unique clicks.""" - updated_at: datetime - """When the ad group was last updated.""" + updated_at: str + """When the ad group was last updated, ISO 8601.""" 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 00000000..1fe4109f --- /dev/null +++ b/src/whop_sdk/types/ad_group_create_params.py @@ -0,0 +1,88 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Union +from typing_extensions import Literal, Required, TypedDict + +__all__ = ["AdGroupCreateParams"] + + +class AdGroupCreateParams(TypedDict, total=False): + ad_campaign_id: Required[str] + """The ad campaign to create the ad group in.""" + + audience: object + """Demographic targeting: { automatic, minimum_age, maximum_age, gender }.""" + + bid_type: Literal["minimum_cost", "average_target", "maximum_target"] + """Bid strategy.""" + + budget_amount: float + """Ad-set budget in dollars (ABO only; omit under CBO).""" + + budget_type: Literal["daily", "lifetime"] + """Whether the budget is daily or lifetime.""" + + conversion_event: Union[ + Literal[ + "purchase", + "add_to_cart", + "initiated_checkout", + "add_payment_info", + "complete_registration", + "lead", + "content_view", + "search", + "contact", + "customize_product", + "donate", + "find_location", + "schedule", + "start_trial", + "submit_application", + "subscribe", + ], + str, + None, + ] + """The pixel event optimized for. + + A standard event, or any custom pixel event name. + """ + + conversion_location: Literal["website"] + """Where conversions happen.""" + + desired_cost_per_result: float + """Target/cap cost for average_target / maximum_target.""" + + devices: object + """Device targeting: { platforms, operating_systems: [{ os, minimum_version }] }.""" + + ends_at: str + """Schedule end, ISO 8601.""" + + frequency_cap: object + """{ maximum_impressions, per_days } — only valid for reach optimization.""" + + minimum_daily_spend: float + """Daily spend floor within the budget.""" + + optimization_goal: str + """What the ad group optimizes for (e.g. conversions, link_clicks, reach).""" + + placements: object + """'automatic' (Advantage+) or a list of { platform, positions }.""" + + regions: object + """Geo targeting: { include / exclude: { countries, cities, zips } }.""" + + starts_at: str + """Schedule start, ISO 8601.""" + + status: Literal["active", "paused"] + """Initial status (default: active).""" + + title: str + """The display name of the ad group.""" diff --git a/src/whop_sdk/types/ad_group_list_params.py b/src/whop_sdk/types/ad_group_list_params.py index 9ddc929c..2478acfa 100644 --- a/src/whop_sdk/types/ad_group_list_params.py +++ b/src/whop_sdk/types/ad_group_list_params.py @@ -2,91 +2,23 @@ from __future__ import annotations -from typing import Union, Optional -from datetime import datetime -from typing_extensions import Literal, Annotated, TypedDict - -from .._types import SequenceNotStr -from .._utils import PropertyInfo -from .ad_group_status import AdGroupStatus -from .shared.direction import Direction +from typing_extensions import Literal, TypedDict __all__ = ["AdGroupListParams"] class AdGroupListParams(TypedDict, total=False): - ad_campaign_id: Optional[str] - """Filter by ad campaign. Provide exactly one of ad_campaign_id or company_id.""" - - ad_campaign_ids: Optional[SequenceNotStr[str]] - """Only return ad groups belonging to these ad campaigns (max 100). - - Can be combined with companyId or used on its own. - """ - - 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.""" - - company_id: Optional[str] - """Filter by company. Provide companyId or adCampaignIds.""" - - 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.""" - - direction: Optional[Direction] - """The direction of the sort.""" - - first: Optional[int] - """Returns the first _n_ elements from the list.""" - - last: Optional[int] - """Returns the last _n_ elements from the list.""" - - order: Optional[ - Literal[ - "created_at", - "spend", - "impressions", - "clicks", - "reach", - "unique_clicks", - "results", - "click_through_rate", - "cost_per_click", - "cost_per_mille", - "cost_per_result", - "frequency", - "return_on_ad_spend", - ] - ] - """The fields the ads dashboard lists (campaigns, ad sets) can be ordered by. - - Stat columns are computed over the provided stats date range. - """ - - query: Optional[str] - """Case-insensitive substring match against the ad group name or ID.""" + account_id: str + """Account whose ad groups to list. Defaults to the authenticated account.""" - stats_from: Annotated[Union[str, datetime, None], PropertyInfo(format="iso8601")] - """ - Inclusive start of the window for each ad group's metric fields (spend, - impressions, …). Omit both statsFrom and statsTo for all-time stats. - """ + ad_campaign_id: str + """Filter to ad groups in this campaign.""" - stats_to: Annotated[Union[str, datetime, None], PropertyInfo(format="iso8601")] - """Inclusive end of the window for each ad group's metric fields. + direction: Literal["asc", "desc"] + """The sort direction. Defaults to desc.""" - Omit both statsFrom and statsTo for all-time stats. - """ + order: Literal["created_at", "updated_at"] + """The field to sort by. Defaults to created_at.""" - status: Optional[AdGroupStatus] - """The status of an external ad group.""" + status: str + """Filter to a status (active, paused, in_review, rejected).""" diff --git a/src/whop_sdk/types/ad_group_list_response.py b/src/whop_sdk/types/ad_group_list_response.py index 4345765a..dc42b3d4 100644 --- a/src/whop_sdk/types/ad_group_list_response.py +++ b/src/whop_sdk/types/ad_group_list_response.py @@ -1,166 +1,14 @@ # 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 -from .ad_budget_type import AdBudgetType -from .ad_group_status import AdGroupStatus -from .shared.currency import Currency -from .ad_campaign_platform import AdCampaignPlatform +from .ad_group import AdGroup -__all__ = ["AdGroupListResponse", "AdCampaign", "Issue"] - - -class AdCampaign(BaseModel): - """The ad campaign this ad group belongs to.""" - - id: str - """The unique identifier for this ad campaign.""" - - -class Issue(BaseModel): - """A platform-reported issue on an ad object (rejection, policy flag, etc.).""" - - category: Optional[Literal["policy_rejection", "creative_media", "audience_targeting", "ad_volume_limit"]] = None - """Whop's canonical category that a raw platform issue is bucketed into.""" - - created_at: datetime - """When the issue was first reported.""" - - resolution_status: Literal["open", "resolved", "acknowledged"] - """Current resolution status.""" - - resource_id: Optional[str] = None - """The Whop ID of the ad object this issue is on (the ad, ad group, or campaign). - - Null when the issue isn't tied to a local object. - """ - - resource_type: str - """The kind of ad object this issue is on: `ad`, `ad_group`, or `ad_campaign`. - - Pairs with `resourceId`. - """ - - subtype: Optional[str] = None - """Finer-grained sub-bucket within the category (e.g. - - the specific Meta policy for a rejection). - """ +__all__ = ["AdGroupListResponse"] class AdGroupListResponse(BaseModel): - """An ad group belonging to an ad campaign.""" - - id: str - """The unique identifier for this ad group.""" - - ad_campaign: AdCampaign - """The ad campaign this ad group belongs to.""" - - budget: Optional[float] = None - """Budget amount in dollars.""" - - budget_type: Optional[AdBudgetType] = None - """The budget type for an ad campaign or ad group.""" - - click_through_rate: float - """Click-through rate as a fraction of impressions (clicks / impressions, 0–1).""" - - clicks: int - """Total clicks on this ad group's ads in the stats window.""" - - cost_per_click: float - """Cost per click in dollars (spend / clicks). 0 when there are no clicks.""" - - cost_per_lead: Optional[float] = None - """Cost in dollars per Whop pixel-attributed lead (spend / leads). - - 0 when leads are tracked but none happened yet; null when leads are not a goal - and none were attributed. - """ - - cost_per_mille: float - """Cost per 1,000 impressions in dollars (spend / impressions × 1000). - - 0 when there are no impressions. - """ - - cost_per_purchase: Optional[float] = None - """Cost in dollars per Whop pixel-attributed purchase (spend / purchases). - - 0 when purchases are tracked but none happened yet; null when purchases are not - a goal and none were attributed. - """ - - cost_per_result: Optional[float] = None - """Cost in dollars per optimization result (spend / results). - - 0 when a result is being optimized for but none happened yet; null when nothing - is being optimized for. - """ - - created_at: datetime - """When the ad group was created.""" - - frequency: Optional[float] = None - """ - Average number of times each person saw an ad (impressions / reach), as reported - by the platform. - """ - - impressions: int - """Total impressions (views) on this ad group's ads in the stats window.""" - - issues: List[Issue] - """ - Open platform issues affecting this ad group and its descendant ads, - deduplicated per object. Empty when there are none. - """ - - leads: int - """Number of Whop pixel-attributed leads (last-click) in the stats window.""" - - platform: AdCampaignPlatform - """The external ad platform this ad group is running on (e.g., meta, tiktok).""" - - purchase_value: float - """Total USD value of Whop pixel-attributed purchases in the stats window.""" - - purchases: int - """Number of Whop pixel-attributed purchases (last-click) in the stats window.""" - - reach: int - """Unique users reached in the stats window (deduplicated by the platform).""" - - return_on_ad_spend: float - """ - Return on ad spend as a ratio (purchaseValue / spend) — 2.5 means $2.50 of - attributed purchase value per $1 spent. 0 when there is no spend. - """ - - spend: float - """Amount charged in dollars in the stats window.""" - - spend_currency: Optional[Currency] = None - """The available currencies on the platform""" - - status: AdGroupStatus - """Current operational status of the ad group.""" - - title: Optional[str] = None - """The ad group name shown in the Whop dashboard.""" - - unique_click_through_rate: Optional[float] = None - """ - Unique click-through rate as a fraction of impressions (unique clicks / - impressions, 0–1). - """ - - unique_clicks: int - """Unique clicks (deduplicated by the platform) in the stats window.""" + data: Optional[List[AdGroup]] = None - updated_at: datetime - """When the ad group was last updated.""" + page_info: Optional[object] = None diff --git a/src/whop_sdk/types/ad_group_retrieve_params.py b/src/whop_sdk/types/ad_group_retrieve_params.py deleted file mode 100644 index 54c57f2c..00000000 --- a/src/whop_sdk/types/ad_group_retrieve_params.py +++ /dev/null @@ -1,25 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from __future__ import annotations - -from typing import Union -from datetime import datetime -from typing_extensions import Annotated, TypedDict - -from .._utils import PropertyInfo - -__all__ = ["AdGroupRetrieveParams"] - - -class AdGroupRetrieveParams(TypedDict, total=False): - stats_from: Annotated[Union[str, datetime, None], PropertyInfo(format="iso8601")] - """ - Inclusive start of the window for the ad group's metric fields (spend, - impressions, …). Omit both statsFrom and statsTo for all-time stats. - """ - - stats_to: Annotated[Union[str, datetime, None], PropertyInfo(format="iso8601")] - """Inclusive end of the window for the ad group's metric fields. - - Omit both statsFrom and statsTo for all-time stats. - """ diff --git a/src/whop_sdk/types/ad_group_status.py b/src/whop_sdk/types/ad_group_status.py deleted file mode 100644 index 625dbfda..00000000 --- a/src/whop_sdk/types/ad_group_status.py +++ /dev/null @@ -1,7 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from typing_extensions import Literal, TypeAlias - -__all__ = ["AdGroupStatus"] - -AdGroupStatus: TypeAlias = Literal["active", "paused", "inactive", "in_review", "rejected", "flagged"] diff --git a/src/whop_sdk/types/ad_group_update_params.py b/src/whop_sdk/types/ad_group_update_params.py index 71db1f9b..7a122b7d 100644 --- a/src/whop_sdk/types/ad_group_update_params.py +++ b/src/whop_sdk/types/ad_group_update_params.py @@ -2,19 +2,84 @@ from __future__ import annotations -from typing import Optional -from typing_extensions import TypedDict +from typing import Union +from typing_extensions import Literal, TypedDict __all__ = ["AdGroupUpdateParams"] class AdGroupUpdateParams(TypedDict, total=False): - budget: Optional[float] - """Budget amount in dollars. + audience: object + """Demographic targeting: { automatic, minimum_age, maximum_age, gender }.""" - The interpretation (daily or lifetime) follows the ad group's existing budget - type. + bid_type: Literal["minimum_cost", "average_target", "maximum_target"] + """Bid strategy.""" + + budget_amount: float + """Ad-set budget in dollars (ABO only; omit under CBO).""" + + budget_type: Literal["daily", "lifetime"] + """Whether the budget is daily or lifetime.""" + + conversion_event: Union[ + Literal[ + "purchase", + "add_to_cart", + "initiated_checkout", + "add_payment_info", + "complete_registration", + "lead", + "content_view", + "search", + "contact", + "customize_product", + "donate", + "find_location", + "schedule", + "start_trial", + "submit_application", + "subscribe", + ], + str, + None, + ] + """The pixel event optimized for. + + A standard event, or any custom pixel event name. """ - title: Optional[str] - """Human-readable ad group title. Max 255 characters.""" + conversion_location: Literal["website"] + """Where conversions happen.""" + + desired_cost_per_result: float + """Target/cap cost for average_target / maximum_target.""" + + devices: object + """Device targeting: { platforms, operating_systems: [{ os, minimum_version }] }.""" + + ends_at: str + """Schedule end, ISO 8601.""" + + frequency_cap: object + """{ maximum_impressions, per_days } — only valid for reach optimization.""" + + minimum_daily_spend: float + """Daily spend floor within the budget.""" + + optimization_goal: str + """What the ad group optimizes for (e.g. conversions, link_clicks, reach).""" + + placements: object + """'automatic' (Advantage+) or a list of { platform, positions }.""" + + regions: object + """Geo targeting: { include / exclude: { countries, cities, zips } }.""" + + starts_at: str + """Schedule start, ISO 8601.""" + + status: Literal["active", "paused"] + """Initial status (default: active).""" + + title: str + """The display name of the ad group.""" diff --git a/src/whop_sdk/types/ad_list_params.py b/src/whop_sdk/types/ad_list_params.py index 7fbe33a3..905944a3 100644 --- a/src/whop_sdk/types/ad_list_params.py +++ b/src/whop_sdk/types/ad_list_params.py @@ -2,116 +2,56 @@ from __future__ import annotations -from typing import Union, Optional -from datetime import datetime -from typing_extensions import Literal, Annotated, TypedDict - -from .._types import SequenceNotStr -from .._utils import PropertyInfo -from .shared.direction import Direction -from .external_ad_status import ExternalAdStatus +from typing_extensions import Literal, TypedDict __all__ = ["AdListParams"] class AdListParams(TypedDict, total=False): - ad_campaign_id: Optional[str] - """Filter by ad campaign. - - Provide exactly one of ad_group_id, ad_campaign_id, or company_id. - """ - - ad_campaign_ids: Optional[SequenceNotStr[str]] - """Only return ads belonging to these ad campaigns (max 100). - - Can be combined with companyId or used on its own. - """ - - ad_group_id: Optional[str] - """Filter by ad group. - - Provide exactly one of ad_group_id, ad_campaign_id, or company_id. - """ + account_id: str + """The account the ads belong to. - ad_group_ids: Optional[SequenceNotStr[str]] - """Only return ads belonging to these ad groups (max 100). - - Can be combined with companyId or used on its own. + Defaults to the account-scoped key's own account. """ - 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.""" + ad_campaign_id: str + """Only return ads in this ad campaign.""" - campaign_id: Optional[str] - """Filter by campaign.""" + ad_group_id: str + """Only return ads in this ad group.""" - company_id: Optional[str] - """Filter by company. + after: str + """Cursor to fetch the page after (from page_info.end_cursor).""" - Provide exactly one of ad_group_id, ad_campaign_id, or company_id. - """ + before: str + """Cursor to fetch the page before (from page_info.start_cursor).""" - created_after: Annotated[Union[str, datetime, None], PropertyInfo(format="iso8601")] + created_after: str """Only return ads created after this timestamp.""" - created_before: Annotated[Union[str, datetime, None], PropertyInfo(format="iso8601")] + created_before: str """Only return ads created before this timestamp.""" - direction: Optional[Direction] - """The direction of the sort.""" - - first: Optional[int] - """Returns the first _n_ elements from the list.""" - - last: Optional[int] - """Returns the last _n_ elements from the list.""" - - order: Optional[ - Literal[ - "created_at", - "spend", - "impressions", - "clicks", - "reach", - "unique_clicks", - "results", - "click_through_rate", - "cost_per_click", - "cost_per_mille", - "cost_per_result", - "frequency", - "return_on_ad_spend", - ] - ] - """The fields the ads dashboard lists (campaigns, ad sets) can be ordered by. - - Stat columns are computed over the provided stats date range. - """ + direction: Literal["asc", "desc"] + """The sort direction. Defaults to desc.""" - order_by: Optional[Literal["spend", "return_on_ad_spend", "roas"]] - """Columns that the listAds query can sort by. Deprecated — use AdStatOrder.""" + first: int + """The number of ads to return.""" - order_direction: Optional[Direction] - """The direction of the sort.""" + last: int + """The number of ads to return from the end of the range.""" - query: Optional[str] - """Case-insensitive substring match against the ad title or ID.""" + order: Literal["created_at", "updated_at"] + """The field to sort by. Defaults to created_at.""" - stats_from: Annotated[Union[str, datetime, None], PropertyInfo(format="iso8601")] - """ - Inclusive start of the window for each ad's metric fields (spend, impressions, - …) and for stats-column sorting. Omit both statsFrom and statsTo for all-time - stats. - """ + query: str + """Filter ads by a title or ID substring.""" - stats_to: Annotated[Union[str, datetime, None], PropertyInfo(format="iso8601")] - """ - Inclusive end of the window for each ad's metric fields and for stats-column - sorting. Omit both statsFrom and statsTo for all-time stats. - """ + stats_from: str + """Start of the stats window. Defaults to all-time.""" + + stats_to: str + """End of the stats window. Defaults to now.""" - status: Optional[ExternalAdStatus] - """The status of an external ad.""" + status: Literal["active", "paused", "in_review", "rejected"] + """Only return ads with this status.""" diff --git a/src/whop_sdk/types/ad_list_response.py b/src/whop_sdk/types/ad_list_response.py deleted file mode 100644 index 742a4b3d..00000000 --- a/src/whop_sdk/types/ad_list_response.py +++ /dev/null @@ -1,169 +0,0 @@ -# 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 -from .shared.currency import Currency -from .external_ad_status import ExternalAdStatus -from .ad_campaign_platform import AdCampaignPlatform - -__all__ = ["AdListResponse", "AdCampaign", "AdGroup", "Issue"] - - -class AdCampaign(BaseModel): - """The ad campaign this ad belongs to.""" - - id: str - """The unique identifier for this ad campaign.""" - - -class AdGroup(BaseModel): - """The parent ad group this ad belongs to.""" - - id: str - """The unique identifier for this ad group.""" - - -class Issue(BaseModel): - """A platform-reported issue on an ad object (rejection, policy flag, etc.).""" - - category: Optional[Literal["policy_rejection", "creative_media", "audience_targeting", "ad_volume_limit"]] = None - """Whop's canonical category that a raw platform issue is bucketed into.""" - - created_at: datetime - """When the issue was first reported.""" - - resolution_status: Literal["open", "resolved", "acknowledged"] - """Current resolution status.""" - - resource_id: Optional[str] = None - """The Whop ID of the ad object this issue is on (the ad, ad group, or campaign). - - Null when the issue isn't tied to a local object. - """ - - resource_type: str - """The kind of ad object this issue is on: `ad`, `ad_group`, or `ad_campaign`. - - Pairs with `resourceId`. - """ - - subtype: Optional[str] = None - """Finer-grained sub-bucket within the category (e.g. - - the specific Meta policy for a rejection). - """ - - -class AdListResponse(BaseModel): - """An ad belonging to an ad group.""" - - id: str - """The unique identifier for this ad.""" - - ad_campaign: AdCampaign - """The ad campaign this ad belongs to.""" - - ad_group: AdGroup - """The parent ad group this ad belongs to.""" - - click_through_rate: float - """Click-through rate as a fraction of impressions (clicks / impressions, 0–1).""" - - clicks: int - """Total clicks on this ad in the stats window.""" - - cost_per_click: float - """Cost per click in dollars (spend / clicks). 0 when there are no clicks.""" - - cost_per_lead: Optional[float] = None - """Cost in dollars per Whop pixel-attributed lead (spend / leads). - - 0 when leads are tracked but none happened yet; null when leads are not a goal - and none were attributed. - """ - - cost_per_mille: float - """Cost per 1,000 impressions in dollars (spend / impressions × 1000). - - 0 when there are no impressions. - """ - - cost_per_purchase: Optional[float] = None - """Cost in dollars per Whop pixel-attributed purchase (spend / purchases). - - 0 when purchases are tracked but none happened yet; null when purchases are not - a goal and none were attributed. - """ - - cost_per_result: Optional[float] = None - """Cost in dollars per optimization result (spend / results). - - 0 when a result is being optimized for but none happened yet; null when nothing - is being optimized for. - """ - - created_at: datetime - """When the ad was created.""" - - frequency: Optional[float] = None - """ - Average number of times each person saw an ad (impressions / reach), as reported - by the platform. - """ - - impressions: int - """Total impressions (views) on this ad in the stats window.""" - - issues: List[Issue] - """Open platform issues affecting this ad, deduplicated per object. - - Empty when there are none. - """ - - leads: int - """Number of Whop pixel-attributed leads (last-click) in the stats window.""" - - platform: AdCampaignPlatform - """The external ad platform this ad is running on (e.g., meta, tiktok).""" - - purchase_value: float - """Total USD value of Whop pixel-attributed purchases in the stats window.""" - - purchases: int - """Number of Whop pixel-attributed purchases (last-click) in the stats window.""" - - reach: int - """Unique users reached in the stats window (deduplicated by the platform).""" - - return_on_ad_spend: float - """ - Return on ad spend as a ratio (purchaseValue / spend) — 2.5 means $2.50 of - attributed purchase value per $1 spent. 0 when there is no spend. - """ - - spend: float - """Amount charged in dollars in the stats window.""" - - spend_currency: Optional[Currency] = None - """The available currencies on the platform""" - - status: ExternalAdStatus - """Current delivery status of the ad.""" - - title: Optional[str] = None - """The display title of the ad. Falls back to the creative set caption when unset.""" - - unique_click_through_rate: Optional[float] = None - """ - Unique click-through rate as a fraction of impressions (unique clicks / - impressions, 0–1). - """ - - unique_clicks: int - """Unique clicks (deduplicated by the platform) in the stats window.""" - - updated_at: datetime - """When the ad was last updated.""" diff --git a/src/whop_sdk/types/ad_retrieve_params.py b/src/whop_sdk/types/ad_retrieve_params.py index fc735f99..51c89edc 100644 --- a/src/whop_sdk/types/ad_retrieve_params.py +++ b/src/whop_sdk/types/ad_retrieve_params.py @@ -2,25 +2,14 @@ from __future__ import annotations -from typing import Union -from datetime import datetime -from typing_extensions import Annotated, TypedDict - -from .._utils import PropertyInfo +from typing_extensions import TypedDict __all__ = ["AdRetrieveParams"] class AdRetrieveParams(TypedDict, total=False): - stats_from: Annotated[Union[str, datetime, None], PropertyInfo(format="iso8601")] - """Inclusive start of the window for the ad's metric fields (spend, impressions, - …). - - Omit both statsFrom and statsTo for all-time stats. - """ - - stats_to: Annotated[Union[str, datetime, None], PropertyInfo(format="iso8601")] - """Inclusive end of the window for the ad's metric fields. + stats_from: str + """Start of the stats window.""" - Omit both statsFrom and statsTo for all-time stats. - """ + stats_to: str + """End of the stats window.""" diff --git a/src/whop_sdk/types/ad_update_params.py b/src/whop_sdk/types/ad_update_params.py new file mode 100644 index 00000000..1e24caf3 --- /dev/null +++ b/src/whop_sdk/types/ad_update_params.py @@ -0,0 +1,53 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Iterable +from typing_extensions import Literal, TypedDict + +from .._types import SequenceNotStr + +__all__ = ["AdUpdateParams", "Creative", "SocialAccount"] + + +class AdUpdateParams(TypedDict, total=False): + call_to_action: Literal["shop_now", "learn_more", "sign_up", "subscribe", "order_now", "get_offer", "see_details"] + """The call-to-action button shown on the ad.""" + + creatives: Iterable[Creative] + """The ad's creatives. + + Each entry is an uploaded file id with an optional format; omit format for the + original/uncropped asset. Replaces a live ad's creative on the platform. + """ + + descriptions: SequenceNotStr[str] + """The description variants shown on the ad.""" + + headlines: SequenceNotStr[str] + """The headline variants shown on the ad.""" + + primary_texts: SequenceNotStr[str] + """The primary text variants shown in the ad body.""" + + social_accounts: Iterable[SocialAccount] + """The social accounts the ad runs under.""" + + title: str + """The display name of the ad.""" + + url: str + """The URL the ad links to.""" + + url_parameters: object + """Query parameters appended to the destination URL, as a string-to-string map.""" + + +class Creative(TypedDict, total=False): + id: str + + format: Literal["square", "vertical", "horizontal"] + + +class SocialAccount(TypedDict, total=False): + id: str diff --git a/src/whop_sdk/types/external_ad_status.py b/src/whop_sdk/types/external_ad_status.py deleted file mode 100644 index 1fa97884..00000000 --- a/src/whop_sdk/types/external_ad_status.py +++ /dev/null @@ -1,7 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from typing_extensions import Literal, TypeAlias - -__all__ = ["ExternalAdStatus"] - -ExternalAdStatus: TypeAlias = Literal["active", "paused", "inactive", "in_review", "rejected", "flagged"] diff --git a/tests/api_resources/test_ad_campaigns.py b/tests/api_resources/test_ad_campaigns.py index 7a23db43..a083a15a 100644 --- a/tests/api_resources/test_ad_campaigns.py +++ b/tests/api_resources/test_ad_campaigns.py @@ -11,9 +11,7 @@ from tests.utils import assert_matches_type from whop_sdk.types import ( AdCampaign, - AdCampaignListResponse, ) -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") @@ -22,11 +20,66 @@ 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( + objective="awareness", + platform="meta", + title="title", + ) + assert_matches_type(AdCampaign, 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( + objective="awareness", + platform="meta", + title="title", + account_id="account_id", + budget_amount=0, + budget_optimization="ad_campaign", + budget_type="daily", + special_ad_categories=["housing"], + ) + assert_matches_type(AdCampaign, 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( + objective="awareness", + 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(AdCampaign, 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( + objective="awareness", + 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(AdCampaign, 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( - id="adcamp_xxxxxxxxxxx", + id="id", ) assert_matches_type(AdCampaign, ad_campaign, path=["response"]) @@ -34,9 +87,9 @@ def test_method_retrieve(self, client: Whop) -> None: @parametrize def test_method_retrieve_with_all_params(self, client: Whop) -> None: ad_campaign = client.ad_campaigns.retrieve( - id="adcamp_xxxxxxxxxxx", - stats_from=parse_datetime("2023-12-01T05:00:00.401Z"), - stats_to=parse_datetime("2023-12-01T05:00:00.401Z"), + id="id", + stats_from="stats_from", + stats_to="stats_to", ) assert_matches_type(AdCampaign, ad_campaign, path=["response"]) @@ -44,7 +97,7 @@ def test_method_retrieve_with_all_params(self, client: Whop) -> None: @parametrize def test_raw_response_retrieve(self, client: Whop) -> None: response = client.ad_campaigns.with_raw_response.retrieve( - id="adcamp_xxxxxxxxxxx", + id="id", ) assert response.is_closed is True @@ -56,7 +109,7 @@ def test_raw_response_retrieve(self, client: Whop) -> None: @parametrize def test_streaming_response_retrieve(self, client: Whop) -> None: with client.ad_campaigns.with_streaming_response.retrieve( - id="adcamp_xxxxxxxxxxx", + id="id", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -78,7 +131,7 @@ def test_path_params_retrieve(self, client: Whop) -> None: @parametrize def test_method_update(self, client: Whop) -> None: ad_campaign = client.ad_campaigns.update( - id="adcamp_xxxxxxxxxxx", + id="id", ) assert_matches_type(AdCampaign, ad_campaign, path=["response"]) @@ -86,9 +139,9 @@ def test_method_update(self, client: Whop) -> None: @parametrize def test_method_update_with_all_params(self, client: Whop) -> None: ad_campaign = client.ad_campaigns.update( - id="adcamp_xxxxxxxxxxx", - budget=6.9, - desired_cpr=6.9, + id="id", + budget_amount=0, + title="title", ) assert_matches_type(AdCampaign, ad_campaign, path=["response"]) @@ -96,7 +149,7 @@ def test_method_update_with_all_params(self, client: Whop) -> None: @parametrize def test_raw_response_update(self, client: Whop) -> None: response = client.ad_campaigns.with_raw_response.update( - id="adcamp_xxxxxxxxxxx", + id="id", ) assert response.is_closed is True @@ -108,7 +161,7 @@ def test_raw_response_update(self, client: Whop) -> None: @parametrize def test_streaming_response_update(self, client: Whop) -> None: with client.ad_campaigns.with_streaming_response.update( - id="adcamp_xxxxxxxxxxx", + id="id", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -130,27 +183,27 @@ def test_path_params_update(self, client: Whop) -> None: @parametrize def test_method_list(self, client: Whop) -> None: ad_campaign = client.ad_campaigns.list() - assert_matches_type(SyncCursorPage[AdCampaignListResponse], ad_campaign, path=["response"]) + assert_matches_type(SyncCursorPage[AdCampaign], 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( + account_id="account_id", after="after", before="before", - company_id="biz_xxxxxxxxxxxxxx", - created_after=parse_datetime("2023-12-01T05:00:00.401Z"), - created_before=parse_datetime("2023-12-01T05:00:00.401Z"), + created_after="created_after", + created_before="created_before", direction="asc", - first=42, - last=42, + first=100, + last=100, order="created_at", query="query", - stats_from=parse_datetime("2023-12-01T05:00:00.401Z"), - stats_to=parse_datetime("2023-12-01T05:00:00.401Z"), - status="active", + stats_from="stats_from", + stats_to="stats_to", + status="draft", ) - assert_matches_type(SyncCursorPage[AdCampaignListResponse], ad_campaign, path=["response"]) + assert_matches_type(SyncCursorPage[AdCampaign], ad_campaign, path=["response"]) @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize @@ -160,7 +213,7 @@ def test_raw_response_list(self, client: Whop) -> None: 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"]) + assert_matches_type(SyncCursorPage[AdCampaign], ad_campaign, path=["response"]) @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize @@ -170,7 +223,7 @@ def test_streaming_response_list(self, client: Whop) -> None: assert response.http_request.headers.get("X-Stainless-Lang") == "python" ad_campaign = response.parse() - assert_matches_type(SyncCursorPage[AdCampaignListResponse], ad_campaign, path=["response"]) + assert_matches_type(SyncCursorPage[AdCampaign], ad_campaign, path=["response"]) assert cast(Any, response.is_closed) is True @@ -178,7 +231,7 @@ def test_streaming_response_list(self, client: Whop) -> None: @parametrize def test_method_pause(self, client: Whop) -> None: ad_campaign = client.ad_campaigns.pause( - "adcamp_xxxxxxxxxxx", + "id", ) assert_matches_type(AdCampaign, ad_campaign, path=["response"]) @@ -186,7 +239,7 @@ def test_method_pause(self, client: Whop) -> None: @parametrize def test_raw_response_pause(self, client: Whop) -> None: response = client.ad_campaigns.with_raw_response.pause( - "adcamp_xxxxxxxxxxx", + "id", ) assert response.is_closed is True @@ -198,7 +251,7 @@ def test_raw_response_pause(self, client: Whop) -> None: @parametrize def test_streaming_response_pause(self, client: Whop) -> None: with client.ad_campaigns.with_streaming_response.pause( - "adcamp_xxxxxxxxxxx", + "id", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -220,7 +273,7 @@ def test_path_params_pause(self, client: Whop) -> None: @parametrize def test_method_unpause(self, client: Whop) -> None: ad_campaign = client.ad_campaigns.unpause( - "adcamp_xxxxxxxxxxx", + "id", ) assert_matches_type(AdCampaign, ad_campaign, path=["response"]) @@ -228,7 +281,7 @@ def test_method_unpause(self, client: Whop) -> None: @parametrize def test_raw_response_unpause(self, client: Whop) -> None: response = client.ad_campaigns.with_raw_response.unpause( - "adcamp_xxxxxxxxxxx", + "id", ) assert response.is_closed is True @@ -240,7 +293,7 @@ def test_raw_response_unpause(self, client: Whop) -> None: @parametrize def test_streaming_response_unpause(self, client: Whop) -> None: with client.ad_campaigns.with_streaming_response.unpause( - "adcamp_xxxxxxxxxxx", + "id", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -264,11 +317,66 @@ class TestAsyncAdCampaigns: "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( + objective="awareness", + platform="meta", + title="title", + ) + assert_matches_type(AdCampaign, 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( + objective="awareness", + platform="meta", + title="title", + account_id="account_id", + budget_amount=0, + budget_optimization="ad_campaign", + budget_type="daily", + special_ad_categories=["housing"], + ) + assert_matches_type(AdCampaign, 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( + objective="awareness", + 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(AdCampaign, 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( + objective="awareness", + 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(AdCampaign, 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( - id="adcamp_xxxxxxxxxxx", + id="id", ) assert_matches_type(AdCampaign, ad_campaign, path=["response"]) @@ -276,9 +384,9 @@ async def test_method_retrieve(self, async_client: AsyncWhop) -> None: @parametrize async def test_method_retrieve_with_all_params(self, async_client: AsyncWhop) -> None: ad_campaign = await async_client.ad_campaigns.retrieve( - id="adcamp_xxxxxxxxxxx", - stats_from=parse_datetime("2023-12-01T05:00:00.401Z"), - stats_to=parse_datetime("2023-12-01T05:00:00.401Z"), + id="id", + stats_from="stats_from", + stats_to="stats_to", ) assert_matches_type(AdCampaign, ad_campaign, path=["response"]) @@ -286,7 +394,7 @@ async def test_method_retrieve_with_all_params(self, async_client: AsyncWhop) -> @parametrize async def test_raw_response_retrieve(self, async_client: AsyncWhop) -> None: response = await async_client.ad_campaigns.with_raw_response.retrieve( - id="adcamp_xxxxxxxxxxx", + id="id", ) assert response.is_closed is True @@ -298,7 +406,7 @@ async def test_raw_response_retrieve(self, async_client: AsyncWhop) -> None: @parametrize async def test_streaming_response_retrieve(self, async_client: AsyncWhop) -> None: async with async_client.ad_campaigns.with_streaming_response.retrieve( - id="adcamp_xxxxxxxxxxx", + id="id", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -320,7 +428,7 @@ async def test_path_params_retrieve(self, async_client: AsyncWhop) -> None: @parametrize async def test_method_update(self, async_client: AsyncWhop) -> None: ad_campaign = await async_client.ad_campaigns.update( - id="adcamp_xxxxxxxxxxx", + id="id", ) assert_matches_type(AdCampaign, ad_campaign, path=["response"]) @@ -328,9 +436,9 @@ async def test_method_update(self, async_client: AsyncWhop) -> None: @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", - budget=6.9, - desired_cpr=6.9, + id="id", + budget_amount=0, + title="title", ) assert_matches_type(AdCampaign, ad_campaign, path=["response"]) @@ -338,7 +446,7 @@ async def test_method_update_with_all_params(self, async_client: AsyncWhop) -> N @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", + id="id", ) assert response.is_closed is True @@ -350,7 +458,7 @@ async def test_raw_response_update(self, async_client: AsyncWhop) -> None: @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", + id="id", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -372,27 +480,27 @@ async def test_path_params_update(self, async_client: AsyncWhop) -> None: @parametrize async def test_method_list(self, async_client: AsyncWhop) -> None: ad_campaign = await async_client.ad_campaigns.list() - assert_matches_type(AsyncCursorPage[AdCampaignListResponse], ad_campaign, path=["response"]) + assert_matches_type(AsyncCursorPage[AdCampaign], 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( + account_id="account_id", after="after", before="before", - company_id="biz_xxxxxxxxxxxxxx", - created_after=parse_datetime("2023-12-01T05:00:00.401Z"), - created_before=parse_datetime("2023-12-01T05:00:00.401Z"), + created_after="created_after", + created_before="created_before", direction="asc", - first=42, - last=42, + first=100, + last=100, order="created_at", query="query", - stats_from=parse_datetime("2023-12-01T05:00:00.401Z"), - stats_to=parse_datetime("2023-12-01T05:00:00.401Z"), - status="active", + stats_from="stats_from", + stats_to="stats_to", + status="draft", ) - assert_matches_type(AsyncCursorPage[AdCampaignListResponse], ad_campaign, path=["response"]) + assert_matches_type(AsyncCursorPage[AdCampaign], ad_campaign, path=["response"]) @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize @@ -402,7 +510,7 @@ async def test_raw_response_list(self, async_client: AsyncWhop) -> None: 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"]) + assert_matches_type(AsyncCursorPage[AdCampaign], ad_campaign, path=["response"]) @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize @@ -412,7 +520,7 @@ async def test_streaming_response_list(self, async_client: AsyncWhop) -> None: 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_matches_type(AsyncCursorPage[AdCampaign], ad_campaign, path=["response"]) assert cast(Any, response.is_closed) is True @@ -420,7 +528,7 @@ async def test_streaming_response_list(self, async_client: AsyncWhop) -> None: @parametrize async def test_method_pause(self, async_client: AsyncWhop) -> None: ad_campaign = await async_client.ad_campaigns.pause( - "adcamp_xxxxxxxxxxx", + "id", ) assert_matches_type(AdCampaign, ad_campaign, path=["response"]) @@ -428,7 +536,7 @@ async def test_method_pause(self, async_client: AsyncWhop) -> None: @parametrize async def test_raw_response_pause(self, async_client: AsyncWhop) -> None: response = await async_client.ad_campaigns.with_raw_response.pause( - "adcamp_xxxxxxxxxxx", + "id", ) assert response.is_closed is True @@ -440,7 +548,7 @@ async def test_raw_response_pause(self, async_client: AsyncWhop) -> None: @parametrize async def test_streaming_response_pause(self, async_client: AsyncWhop) -> None: async with async_client.ad_campaigns.with_streaming_response.pause( - "adcamp_xxxxxxxxxxx", + "id", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -462,7 +570,7 @@ async def test_path_params_pause(self, async_client: AsyncWhop) -> None: @parametrize async def test_method_unpause(self, async_client: AsyncWhop) -> None: ad_campaign = await async_client.ad_campaigns.unpause( - "adcamp_xxxxxxxxxxx", + "id", ) assert_matches_type(AdCampaign, ad_campaign, path=["response"]) @@ -470,7 +578,7 @@ async def test_method_unpause(self, async_client: AsyncWhop) -> None: @parametrize async def test_raw_response_unpause(self, async_client: AsyncWhop) -> None: response = await async_client.ad_campaigns.with_raw_response.unpause( - "adcamp_xxxxxxxxxxx", + "id", ) assert response.is_closed is True @@ -482,7 +590,7 @@ async def test_raw_response_unpause(self, async_client: AsyncWhop) -> None: @parametrize async def test_streaming_response_unpause(self, async_client: AsyncWhop) -> None: async with async_client.ad_campaigns.with_streaming_response.unpause( - "adcamp_xxxxxxxxxxx", + "id", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" diff --git a/tests/api_resources/test_ad_groups.py b/tests/api_resources/test_ad_groups.py index 275e8edf..6787cc94 100644 --- a/tests/api_resources/test_ad_groups.py +++ b/tests/api_resources/test_ad_groups.py @@ -14,8 +14,6 @@ AdGroupListResponse, AdGroupDeleteResponse, ) -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") @@ -25,19 +23,68 @@ class TestAdGroups: @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize - def test_method_retrieve(self, client: Whop) -> None: - ad_group = client.ad_groups.retrieve( - id="adgrp_xxxxxxxxxxxx", + def test_method_create(self, client: Whop) -> None: + ad_group = client.ad_groups.create( + ad_campaign_id="ad_campaign_id", ) assert_matches_type(AdGroup, ad_group, path=["response"]) @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize - def test_method_retrieve_with_all_params(self, client: Whop) -> None: + def test_method_create_with_all_params(self, client: Whop) -> None: + ad_group = client.ad_groups.create( + ad_campaign_id="ad_campaign_id", + audience={}, + bid_type="minimum_cost", + budget_amount=0, + budget_type="daily", + conversion_event="purchase", + conversion_location="website", + desired_cost_per_result=0, + devices={}, + ends_at="ends_at", + frequency_cap={}, + minimum_daily_spend=0, + optimization_goal="optimization_goal", + placements={}, + regions={}, + starts_at="starts_at", + status="active", + title="title", + ) + assert_matches_type(AdGroup, 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( + ad_campaign_id="ad_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(AdGroup, 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( + ad_campaign_id="ad_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(AdGroup, 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( - id="adgrp_xxxxxxxxxxxx", - stats_from=parse_datetime("2023-12-01T05:00:00.401Z"), - stats_to=parse_datetime("2023-12-01T05:00:00.401Z"), + "id", ) assert_matches_type(AdGroup, ad_group, path=["response"]) @@ -45,7 +92,7 @@ def test_method_retrieve_with_all_params(self, client: Whop) -> None: @parametrize def test_raw_response_retrieve(self, client: Whop) -> None: response = client.ad_groups.with_raw_response.retrieve( - id="adgrp_xxxxxxxxxxxx", + "id", ) assert response.is_closed is True @@ -57,7 +104,7 @@ def test_raw_response_retrieve(self, client: Whop) -> None: @parametrize def test_streaming_response_retrieve(self, client: Whop) -> None: with client.ad_groups.with_streaming_response.retrieve( - id="adgrp_xxxxxxxxxxxx", + "id", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -72,14 +119,14 @@ def test_streaming_response_retrieve(self, client: Whop) -> None: 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( - id="", + "", ) @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", + id="id", ) assert_matches_type(AdGroup, ad_group, path=["response"]) @@ -87,8 +134,23 @@ def test_method_update(self, client: Whop) -> None: @parametrize def test_method_update_with_all_params(self, client: Whop) -> None: ad_group = client.ad_groups.update( - id="adgrp_xxxxxxxxxxxx", - budget=6.9, + id="id", + audience={}, + bid_type="minimum_cost", + budget_amount=0, + budget_type="daily", + conversion_event="purchase", + conversion_location="website", + desired_cost_per_result=0, + devices={}, + ends_at="ends_at", + frequency_cap={}, + minimum_daily_spend=0, + optimization_goal="optimization_goal", + placements={}, + regions={}, + starts_at="starts_at", + status="active", title="title", ) assert_matches_type(AdGroup, ad_group, path=["response"]) @@ -97,7 +159,7 @@ def test_method_update_with_all_params(self, client: Whop) -> None: @parametrize def test_raw_response_update(self, client: Whop) -> None: response = client.ad_groups.with_raw_response.update( - id="adgrp_xxxxxxxxxxxx", + id="id", ) assert response.is_closed is True @@ -109,7 +171,7 @@ def test_raw_response_update(self, client: Whop) -> None: @parametrize def test_streaming_response_update(self, client: Whop) -> None: with client.ad_groups.with_streaming_response.update( - id="adgrp_xxxxxxxxxxxx", + id="id", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -131,30 +193,19 @@ def test_path_params_update(self, client: Whop) -> None: @parametrize def test_method_list(self, client: Whop) -> None: ad_group = client.ad_groups.list() - assert_matches_type(SyncCursorPage[AdGroupListResponse], ad_group, path=["response"]) + assert_matches_type(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( + account_id="account_id", ad_campaign_id="ad_campaign_id", - ad_campaign_ids=["string"], - 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"), direction="asc", - first=42, - last=42, order="created_at", - query="query", - stats_from=parse_datetime("2023-12-01T05:00:00.401Z"), - stats_to=parse_datetime("2023-12-01T05:00:00.401Z"), - status="active", + status="status", ) - assert_matches_type(SyncCursorPage[AdGroupListResponse], ad_group, path=["response"]) + assert_matches_type(AdGroupListResponse, ad_group, path=["response"]) @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize @@ -164,7 +215,7 @@ def test_raw_response_list(self, client: Whop) -> None: 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"]) + assert_matches_type(AdGroupListResponse, ad_group, path=["response"]) @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize @@ -174,7 +225,7 @@ def test_streaming_response_list(self, client: Whop) -> None: assert response.http_request.headers.get("X-Stainless-Lang") == "python" ad_group = response.parse() - assert_matches_type(SyncCursorPage[AdGroupListResponse], ad_group, path=["response"]) + assert_matches_type(AdGroupListResponse, ad_group, path=["response"]) assert cast(Any, response.is_closed) is True @@ -182,7 +233,7 @@ def test_streaming_response_list(self, client: Whop) -> None: @parametrize def test_method_delete(self, client: Whop) -> None: ad_group = client.ad_groups.delete( - "adgrp_xxxxxxxxxxxx", + "id", ) assert_matches_type(AdGroupDeleteResponse, ad_group, path=["response"]) @@ -190,7 +241,7 @@ def test_method_delete(self, client: Whop) -> None: @parametrize def test_raw_response_delete(self, client: Whop) -> None: response = client.ad_groups.with_raw_response.delete( - "adgrp_xxxxxxxxxxxx", + "id", ) assert response.is_closed is True @@ -202,7 +253,7 @@ def test_raw_response_delete(self, client: Whop) -> None: @parametrize def test_streaming_response_delete(self, client: Whop) -> None: with client.ad_groups.with_streaming_response.delete( - "adgrp_xxxxxxxxxxxx", + "id", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -224,7 +275,7 @@ def test_path_params_delete(self, client: Whop) -> None: @parametrize def test_method_pause(self, client: Whop) -> None: ad_group = client.ad_groups.pause( - "adgrp_xxxxxxxxxxxx", + "id", ) assert_matches_type(AdGroup, ad_group, path=["response"]) @@ -232,7 +283,7 @@ def test_method_pause(self, client: Whop) -> None: @parametrize def test_raw_response_pause(self, client: Whop) -> None: response = client.ad_groups.with_raw_response.pause( - "adgrp_xxxxxxxxxxxx", + "id", ) assert response.is_closed is True @@ -244,7 +295,7 @@ def test_raw_response_pause(self, client: Whop) -> None: @parametrize def test_streaming_response_pause(self, client: Whop) -> None: with client.ad_groups.with_streaming_response.pause( - "adgrp_xxxxxxxxxxxx", + "id", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -266,7 +317,7 @@ def test_path_params_pause(self, client: Whop) -> None: @parametrize def test_method_unpause(self, client: Whop) -> None: ad_group = client.ad_groups.unpause( - "adgrp_xxxxxxxxxxxx", + "id", ) assert_matches_type(AdGroup, ad_group, path=["response"]) @@ -274,7 +325,7 @@ def test_method_unpause(self, client: Whop) -> None: @parametrize def test_raw_response_unpause(self, client: Whop) -> None: response = client.ad_groups.with_raw_response.unpause( - "adgrp_xxxxxxxxxxxx", + "id", ) assert response.is_closed is True @@ -286,7 +337,7 @@ def test_raw_response_unpause(self, client: Whop) -> None: @parametrize def test_streaming_response_unpause(self, client: Whop) -> None: with client.ad_groups.with_streaming_response.unpause( - "adgrp_xxxxxxxxxxxx", + "id", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -312,19 +363,68 @@ class TestAsyncAdGroups: @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( - id="adgrp_xxxxxxxxxxxx", + async def test_method_create(self, async_client: AsyncWhop) -> None: + ad_group = await async_client.ad_groups.create( + ad_campaign_id="ad_campaign_id", ) assert_matches_type(AdGroup, ad_group, path=["response"]) @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize - async def test_method_retrieve_with_all_params(self, async_client: AsyncWhop) -> None: + async def test_method_create_with_all_params(self, async_client: AsyncWhop) -> None: + ad_group = await async_client.ad_groups.create( + ad_campaign_id="ad_campaign_id", + audience={}, + bid_type="minimum_cost", + budget_amount=0, + budget_type="daily", + conversion_event="purchase", + conversion_location="website", + desired_cost_per_result=0, + devices={}, + ends_at="ends_at", + frequency_cap={}, + minimum_daily_spend=0, + optimization_goal="optimization_goal", + placements={}, + regions={}, + starts_at="starts_at", + status="active", + title="title", + ) + assert_matches_type(AdGroup, 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( + ad_campaign_id="ad_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(AdGroup, 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( + ad_campaign_id="ad_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(AdGroup, 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( - id="adgrp_xxxxxxxxxxxx", - stats_from=parse_datetime("2023-12-01T05:00:00.401Z"), - stats_to=parse_datetime("2023-12-01T05:00:00.401Z"), + "id", ) assert_matches_type(AdGroup, ad_group, path=["response"]) @@ -332,7 +432,7 @@ async def test_method_retrieve_with_all_params(self, async_client: AsyncWhop) -> @parametrize async def test_raw_response_retrieve(self, async_client: AsyncWhop) -> None: response = await async_client.ad_groups.with_raw_response.retrieve( - id="adgrp_xxxxxxxxxxxx", + "id", ) assert response.is_closed is True @@ -344,7 +444,7 @@ async def test_raw_response_retrieve(self, async_client: AsyncWhop) -> None: @parametrize async def test_streaming_response_retrieve(self, async_client: AsyncWhop) -> None: async with async_client.ad_groups.with_streaming_response.retrieve( - id="adgrp_xxxxxxxxxxxx", + "id", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -359,14 +459,14 @@ async def test_streaming_response_retrieve(self, async_client: AsyncWhop) -> Non 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( - id="", + "", ) @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", + id="id", ) assert_matches_type(AdGroup, ad_group, path=["response"]) @@ -374,8 +474,23 @@ async def test_method_update(self, async_client: AsyncWhop) -> None: @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, + id="id", + audience={}, + bid_type="minimum_cost", + budget_amount=0, + budget_type="daily", + conversion_event="purchase", + conversion_location="website", + desired_cost_per_result=0, + devices={}, + ends_at="ends_at", + frequency_cap={}, + minimum_daily_spend=0, + optimization_goal="optimization_goal", + placements={}, + regions={}, + starts_at="starts_at", + status="active", title="title", ) assert_matches_type(AdGroup, ad_group, path=["response"]) @@ -384,7 +499,7 @@ async def test_method_update_with_all_params(self, async_client: AsyncWhop) -> N @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", + id="id", ) assert response.is_closed is True @@ -396,7 +511,7 @@ async def test_raw_response_update(self, async_client: AsyncWhop) -> None: @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", + id="id", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -418,30 +533,19 @@ async def test_path_params_update(self, async_client: AsyncWhop) -> None: @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"]) + assert_matches_type(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( + account_id="account_id", ad_campaign_id="ad_campaign_id", - ad_campaign_ids=["string"], - 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"), direction="asc", - first=42, - last=42, order="created_at", - query="query", - stats_from=parse_datetime("2023-12-01T05:00:00.401Z"), - stats_to=parse_datetime("2023-12-01T05:00:00.401Z"), - status="active", + status="status", ) - assert_matches_type(AsyncCursorPage[AdGroupListResponse], ad_group, path=["response"]) + assert_matches_type(AdGroupListResponse, ad_group, path=["response"]) @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize @@ -451,7 +555,7 @@ async def test_raw_response_list(self, async_client: AsyncWhop) -> None: 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"]) + assert_matches_type(AdGroupListResponse, ad_group, path=["response"]) @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize @@ -461,7 +565,7 @@ async def test_streaming_response_list(self, async_client: AsyncWhop) -> None: 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_matches_type(AdGroupListResponse, ad_group, path=["response"]) assert cast(Any, response.is_closed) is True @@ -469,7 +573,7 @@ async def test_streaming_response_list(self, async_client: AsyncWhop) -> None: @parametrize async def test_method_delete(self, async_client: AsyncWhop) -> None: ad_group = await async_client.ad_groups.delete( - "adgrp_xxxxxxxxxxxx", + "id", ) assert_matches_type(AdGroupDeleteResponse, ad_group, path=["response"]) @@ -477,7 +581,7 @@ async def test_method_delete(self, async_client: AsyncWhop) -> None: @parametrize async def test_raw_response_delete(self, async_client: AsyncWhop) -> None: response = await async_client.ad_groups.with_raw_response.delete( - "adgrp_xxxxxxxxxxxx", + "id", ) assert response.is_closed is True @@ -489,7 +593,7 @@ async def test_raw_response_delete(self, async_client: AsyncWhop) -> None: @parametrize async def test_streaming_response_delete(self, async_client: AsyncWhop) -> None: async with async_client.ad_groups.with_streaming_response.delete( - "adgrp_xxxxxxxxxxxx", + "id", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -511,7 +615,7 @@ async def test_path_params_delete(self, async_client: AsyncWhop) -> None: @parametrize async def test_method_pause(self, async_client: AsyncWhop) -> None: ad_group = await async_client.ad_groups.pause( - "adgrp_xxxxxxxxxxxx", + "id", ) assert_matches_type(AdGroup, ad_group, path=["response"]) @@ -519,7 +623,7 @@ async def test_method_pause(self, async_client: AsyncWhop) -> None: @parametrize async def test_raw_response_pause(self, async_client: AsyncWhop) -> None: response = await async_client.ad_groups.with_raw_response.pause( - "adgrp_xxxxxxxxxxxx", + "id", ) assert response.is_closed is True @@ -531,7 +635,7 @@ async def test_raw_response_pause(self, async_client: AsyncWhop) -> None: @parametrize async def test_streaming_response_pause(self, async_client: AsyncWhop) -> None: async with async_client.ad_groups.with_streaming_response.pause( - "adgrp_xxxxxxxxxxxx", + "id", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -553,7 +657,7 @@ async def test_path_params_pause(self, async_client: AsyncWhop) -> None: @parametrize async def test_method_unpause(self, async_client: AsyncWhop) -> None: ad_group = await async_client.ad_groups.unpause( - "adgrp_xxxxxxxxxxxx", + "id", ) assert_matches_type(AdGroup, ad_group, path=["response"]) @@ -561,7 +665,7 @@ async def test_method_unpause(self, async_client: AsyncWhop) -> None: @parametrize async def test_raw_response_unpause(self, async_client: AsyncWhop) -> None: response = await async_client.ad_groups.with_raw_response.unpause( - "adgrp_xxxxxxxxxxxx", + "id", ) assert response.is_closed is True @@ -573,7 +677,7 @@ async def test_raw_response_unpause(self, async_client: AsyncWhop) -> None: @parametrize async def test_streaming_response_unpause(self, async_client: AsyncWhop) -> None: async with async_client.ad_groups.with_streaming_response.unpause( - "adgrp_xxxxxxxxxxxx", + "id", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" diff --git a/tests/api_resources/test_ads.py b/tests/api_resources/test_ads.py index 4fc25ed4..7da7c43a 100644 --- a/tests/api_resources/test_ads.py +++ b/tests/api_resources/test_ads.py @@ -9,8 +9,7 @@ from whop_sdk import Whop, AsyncWhop from tests.utils import assert_matches_type -from whop_sdk.types import Ad, AdListResponse -from whop_sdk._utils import parse_datetime +from whop_sdk.types import Ad, AdDeleteResponse from whop_sdk.pagination import SyncCursorPage, AsyncCursorPage base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") @@ -19,11 +18,62 @@ 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() + assert_matches_type(Ad, 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={}, + ad_group_id="ad_group_id", + call_to_action="shop_now", + creatives=[ + { + "id": "id", + "format": "square", + } + ], + descriptions=["string"], + headlines=["string"], + primary_texts=["string"], + social_accounts=[{"id": "id"}], + title="title", + url="url", + url_parameters={}, + ) + assert_matches_type(Ad, 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() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + ad = response.parse() + assert_matches_type(Ad, 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() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + ad = response.parse() + assert_matches_type(Ad, 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( - id="ad_xxxxxxxxxxxxxxx", + id="id", ) assert_matches_type(Ad, ad, path=["response"]) @@ -31,9 +81,9 @@ def test_method_retrieve(self, client: Whop) -> None: @parametrize def test_method_retrieve_with_all_params(self, client: Whop) -> None: ad = client.ads.retrieve( - id="ad_xxxxxxxxxxxxxxx", - stats_from=parse_datetime("2023-12-01T05:00:00.401Z"), - stats_to=parse_datetime("2023-12-01T05:00:00.401Z"), + id="id", + stats_from="stats_from", + stats_to="stats_to", ) assert_matches_type(Ad, ad, path=["response"]) @@ -41,7 +91,7 @@ def test_method_retrieve_with_all_params(self, client: Whop) -> None: @parametrize def test_raw_response_retrieve(self, client: Whop) -> None: response = client.ads.with_raw_response.retrieve( - id="ad_xxxxxxxxxxxxxxx", + id="id", ) assert response.is_closed is True @@ -53,7 +103,7 @@ def test_raw_response_retrieve(self, client: Whop) -> None: @parametrize def test_streaming_response_retrieve(self, client: Whop) -> None: with client.ads.with_streaming_response.retrieve( - id="ad_xxxxxxxxxxxxxxx", + id="id", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -71,38 +121,97 @@ def test_path_params_retrieve(self, client: Whop) -> None: id="", ) + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_update(self, client: Whop) -> None: + ad = client.ads.update( + id="id", + ) + assert_matches_type(Ad, ad, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_update_with_all_params(self, client: Whop) -> None: + ad = client.ads.update( + id="id", + call_to_action="shop_now", + creatives=[ + { + "id": "id", + "format": "square", + } + ], + descriptions=["string"], + headlines=["string"], + primary_texts=["string"], + social_accounts=[{"id": "id"}], + title="title", + url="url", + url_parameters={}, + ) + assert_matches_type(Ad, ad, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_raw_response_update(self, client: Whop) -> None: + response = client.ads.with_raw_response.update( + id="id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + ad = response.parse() + assert_matches_type(Ad, ad, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_streaming_response_update(self, client: Whop) -> None: + with client.ads.with_streaming_response.update( + id="id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + ad = response.parse() + assert_matches_type(Ad, 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_update(self, client: Whop) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + client.ads.with_raw_response.update( + id="", + ) + @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"]) + assert_matches_type(SyncCursorPage[Ad], 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( + account_id="account_id", ad_campaign_id="ad_campaign_id", - ad_campaign_ids=["string"], ad_group_id="ad_group_id", - ad_group_ids=["string"], 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"), + created_after="created_after", + created_before="created_before", direction="asc", - first=42, - last=42, + first=100, + last=100, order="created_at", - order_by="spend", - order_direction="asc", query="query", - stats_from=parse_datetime("2023-12-01T05:00:00.401Z"), - stats_to=parse_datetime("2023-12-01T05:00:00.401Z"), + stats_from="stats_from", + stats_to="stats_to", status="active", ) - assert_matches_type(SyncCursorPage[AdListResponse], ad, path=["response"]) + assert_matches_type(SyncCursorPage[Ad], ad, path=["response"]) @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize @@ -112,7 +221,7 @@ def test_raw_response_list(self, client: Whop) -> None: 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"]) + assert_matches_type(SyncCursorPage[Ad], ad, path=["response"]) @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize @@ -122,15 +231,57 @@ def test_streaming_response_list(self, client: Whop) -> None: assert response.http_request.headers.get("X-Stainless-Lang") == "python" ad = response.parse() - assert_matches_type(SyncCursorPage[AdListResponse], ad, path=["response"]) + assert_matches_type(SyncCursorPage[Ad], ad, 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 = client.ads.delete( + "id", + ) + assert_matches_type(AdDeleteResponse, ad, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_raw_response_delete(self, client: Whop) -> None: + response = client.ads.with_raw_response.delete( + "id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + ad = response.parse() + assert_matches_type(AdDeleteResponse, ad, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_streaming_response_delete(self, client: Whop) -> None: + with client.ads.with_streaming_response.delete( + "id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + ad = response.parse() + assert_matches_type(AdDeleteResponse, 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_delete(self, client: Whop) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + client.ads.with_raw_response.delete( + "", + ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_method_pause(self, client: Whop) -> None: ad = client.ads.pause( - "ad_xxxxxxxxxxxxxxx", + "id", ) assert_matches_type(Ad, ad, path=["response"]) @@ -138,7 +289,7 @@ def test_method_pause(self, client: Whop) -> None: @parametrize def test_raw_response_pause(self, client: Whop) -> None: response = client.ads.with_raw_response.pause( - "ad_xxxxxxxxxxxxxxx", + "id", ) assert response.is_closed is True @@ -150,7 +301,7 @@ def test_raw_response_pause(self, client: Whop) -> None: @parametrize def test_streaming_response_pause(self, client: Whop) -> None: with client.ads.with_streaming_response.pause( - "ad_xxxxxxxxxxxxxxx", + "id", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -172,7 +323,7 @@ def test_path_params_pause(self, client: Whop) -> None: @parametrize def test_method_unpause(self, client: Whop) -> None: ad = client.ads.unpause( - "ad_xxxxxxxxxxxxxxx", + "id", ) assert_matches_type(Ad, ad, path=["response"]) @@ -180,7 +331,7 @@ def test_method_unpause(self, client: Whop) -> None: @parametrize def test_raw_response_unpause(self, client: Whop) -> None: response = client.ads.with_raw_response.unpause( - "ad_xxxxxxxxxxxxxxx", + "id", ) assert response.is_closed is True @@ -192,7 +343,7 @@ def test_raw_response_unpause(self, client: Whop) -> None: @parametrize def test_streaming_response_unpause(self, client: Whop) -> None: with client.ads.with_streaming_response.unpause( - "ad_xxxxxxxxxxxxxxx", + "id", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -216,11 +367,62 @@ class TestAsyncAds: "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() + assert_matches_type(Ad, 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={}, + ad_group_id="ad_group_id", + call_to_action="shop_now", + creatives=[ + { + "id": "id", + "format": "square", + } + ], + descriptions=["string"], + headlines=["string"], + primary_texts=["string"], + social_accounts=[{"id": "id"}], + title="title", + url="url", + url_parameters={}, + ) + assert_matches_type(Ad, 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() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + ad = await response.parse() + assert_matches_type(Ad, 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() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + ad = await response.parse() + assert_matches_type(Ad, 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( - id="ad_xxxxxxxxxxxxxxx", + id="id", ) assert_matches_type(Ad, ad, path=["response"]) @@ -228,9 +430,9 @@ async def test_method_retrieve(self, async_client: AsyncWhop) -> None: @parametrize async def test_method_retrieve_with_all_params(self, async_client: AsyncWhop) -> None: ad = await async_client.ads.retrieve( - id="ad_xxxxxxxxxxxxxxx", - stats_from=parse_datetime("2023-12-01T05:00:00.401Z"), - stats_to=parse_datetime("2023-12-01T05:00:00.401Z"), + id="id", + stats_from="stats_from", + stats_to="stats_to", ) assert_matches_type(Ad, ad, path=["response"]) @@ -238,7 +440,7 @@ async def test_method_retrieve_with_all_params(self, async_client: AsyncWhop) -> @parametrize async def test_raw_response_retrieve(self, async_client: AsyncWhop) -> None: response = await async_client.ads.with_raw_response.retrieve( - id="ad_xxxxxxxxxxxxxxx", + id="id", ) assert response.is_closed is True @@ -250,7 +452,7 @@ async def test_raw_response_retrieve(self, async_client: AsyncWhop) -> None: @parametrize async def test_streaming_response_retrieve(self, async_client: AsyncWhop) -> None: async with async_client.ads.with_streaming_response.retrieve( - id="ad_xxxxxxxxxxxxxxx", + id="id", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -268,38 +470,97 @@ async def test_path_params_retrieve(self, async_client: AsyncWhop) -> None: id="", ) + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_update(self, async_client: AsyncWhop) -> None: + ad = await async_client.ads.update( + id="id", + ) + assert_matches_type(Ad, ad, 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 = await async_client.ads.update( + id="id", + call_to_action="shop_now", + creatives=[ + { + "id": "id", + "format": "square", + } + ], + descriptions=["string"], + headlines=["string"], + primary_texts=["string"], + social_accounts=[{"id": "id"}], + title="title", + url="url", + url_parameters={}, + ) + assert_matches_type(Ad, ad, 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.ads.with_raw_response.update( + id="id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + ad = await response.parse() + assert_matches_type(Ad, ad, 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.ads.with_streaming_response.update( + id="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(Ad, 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_update(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.update( + id="", + ) + @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"]) + assert_matches_type(AsyncCursorPage[Ad], 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( + account_id="account_id", ad_campaign_id="ad_campaign_id", - ad_campaign_ids=["string"], ad_group_id="ad_group_id", - ad_group_ids=["string"], 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"), + created_after="created_after", + created_before="created_before", direction="asc", - first=42, - last=42, + first=100, + last=100, order="created_at", - order_by="spend", - order_direction="asc", query="query", - stats_from=parse_datetime("2023-12-01T05:00:00.401Z"), - stats_to=parse_datetime("2023-12-01T05:00:00.401Z"), + stats_from="stats_from", + stats_to="stats_to", status="active", ) - assert_matches_type(AsyncCursorPage[AdListResponse], ad, path=["response"]) + assert_matches_type(AsyncCursorPage[Ad], ad, path=["response"]) @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize @@ -309,7 +570,7 @@ async def test_raw_response_list(self, async_client: AsyncWhop) -> None: 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"]) + assert_matches_type(AsyncCursorPage[Ad], ad, path=["response"]) @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize @@ -319,15 +580,57 @@ async def test_streaming_response_list(self, async_client: AsyncWhop) -> None: assert response.http_request.headers.get("X-Stainless-Lang") == "python" ad = await response.parse() - assert_matches_type(AsyncCursorPage[AdListResponse], ad, path=["response"]) + assert_matches_type(AsyncCursorPage[Ad], 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_delete(self, async_client: AsyncWhop) -> None: + ad = await async_client.ads.delete( + "id", + ) + assert_matches_type(AdDeleteResponse, ad, 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.ads.with_raw_response.delete( + "id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + ad = await response.parse() + assert_matches_type(AdDeleteResponse, ad, 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.ads.with_streaming_response.delete( + "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(AdDeleteResponse, 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_delete(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.delete( + "", + ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_method_pause(self, async_client: AsyncWhop) -> None: ad = await async_client.ads.pause( - "ad_xxxxxxxxxxxxxxx", + "id", ) assert_matches_type(Ad, ad, path=["response"]) @@ -335,7 +638,7 @@ async def test_method_pause(self, async_client: AsyncWhop) -> None: @parametrize async def test_raw_response_pause(self, async_client: AsyncWhop) -> None: response = await async_client.ads.with_raw_response.pause( - "ad_xxxxxxxxxxxxxxx", + "id", ) assert response.is_closed is True @@ -347,7 +650,7 @@ async def test_raw_response_pause(self, async_client: AsyncWhop) -> None: @parametrize async def test_streaming_response_pause(self, async_client: AsyncWhop) -> None: async with async_client.ads.with_streaming_response.pause( - "ad_xxxxxxxxxxxxxxx", + "id", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -369,7 +672,7 @@ async def test_path_params_pause(self, async_client: AsyncWhop) -> None: @parametrize async def test_method_unpause(self, async_client: AsyncWhop) -> None: ad = await async_client.ads.unpause( - "ad_xxxxxxxxxxxxxxx", + "id", ) assert_matches_type(Ad, ad, path=["response"]) @@ -377,7 +680,7 @@ async def test_method_unpause(self, async_client: AsyncWhop) -> None: @parametrize async def test_raw_response_unpause(self, async_client: AsyncWhop) -> None: response = await async_client.ads.with_raw_response.unpause( - "ad_xxxxxxxxxxxxxxx", + "id", ) assert response.is_closed is True @@ -389,7 +692,7 @@ async def test_raw_response_unpause(self, async_client: AsyncWhop) -> None: @parametrize async def test_streaming_response_unpause(self, async_client: AsyncWhop) -> None: async with async_client.ads.with_streaming_response.unpause( - "ad_xxxxxxxxxxxxxxx", + "id", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" From b5bb8e30a4ee8cf966b98555eee0ec5dceca7bc0 Mon Sep 17 00:00:00 2001 From: stlc-bot Date: Wed, 24 Jun 2026 20:47:21 +0000 Subject: [PATCH 079/109] Fetch a social account's Facebook/Instagram posts over the REST API Stainless-Generated-From: 440a7a825a3a88493adb1c5200be40ab0cc4cf74 --- .stats.yml | 2 +- api.md | 9 +- src/whop_sdk/resources/social_accounts.py | 140 +++++++++++++++++- src/whop_sdk/types/__init__.py | 3 + src/whop_sdk/types/social_account_post.py | 74 +++++++++ .../types/social_account_posts_params.py | 21 +++ .../types/social_account_posts_response.py | 20 +++ tests/api_resources/test_social_accounts.py | 117 +++++++++++++++ 8 files changed, 383 insertions(+), 3 deletions(-) create mode 100644 src/whop_sdk/types/social_account_post.py create mode 100644 src/whop_sdk/types/social_account_posts_params.py create mode 100644 src/whop_sdk/types/social_account_posts_response.py diff --git a/.stats.yml b/.stats.yml index dcab66f6..090fb0bc 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1 +1 @@ -configured_endpoints: 250 +configured_endpoints: 251 diff --git a/api.md b/api.md index 95ef2e78..c0ff5d27 100644 --- a/api.md +++ b/api.md @@ -130,7 +130,13 @@ Methods: Types: ```python -from whop_sdk.types import SocialAccount, SocialAccountCreateResponse, SocialAccountDeleteResponse +from whop_sdk.types import ( + SocialAccount, + SocialAccountPost, + SocialAccountCreateResponse, + SocialAccountDeleteResponse, + SocialAccountPostsResponse, +) ``` Methods: @@ -138,6 +144,7 @@ Methods: - client.social_accounts.create(\*\*params) -> SocialAccountCreateResponse - client.social_accounts.list(\*\*params) -> SyncCursorPage[SocialAccount] - client.social_accounts.delete(id, \*\*params) -> SocialAccountDeleteResponse +- client.social_accounts.posts(id, \*\*params) -> SocialAccountPostsResponse # Companies diff --git a/src/whop_sdk/resources/social_accounts.py b/src/whop_sdk/resources/social_accounts.py index 166c80fe..c7052b5a 100644 --- a/src/whop_sdk/resources/social_accounts.py +++ b/src/whop_sdk/resources/social_accounts.py @@ -7,7 +7,12 @@ import httpx -from ..types import social_account_list_params, social_account_create_params, social_account_delete_params +from ..types import ( + social_account_list_params, + social_account_posts_params, + social_account_create_params, + social_account_delete_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 @@ -21,6 +26,7 @@ from ..pagination import SyncCursorPage, AsyncCursorPage from .._base_client import AsyncPaginator, make_request_options from ..types.social_account import SocialAccount +from ..types.social_account_posts_response import SocialAccountPostsResponse from ..types.social_account_create_response import SocialAccountCreateResponse from ..types.social_account_delete_response import SocialAccountDeleteResponse @@ -238,6 +244,66 @@ def delete( cast_to=SocialAccountDeleteResponse, ) + def posts( + self, + id: str, + *, + account_id: str, + after: str | Omit = omit, + first: int | Omit = omit, + post_id: 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, + ) -> SocialAccountPostsResponse: + """Lists the existing posts of a connected social account. + + Supported for Facebook + pages and Instagram accounts. Pass post*id to return only that single post. The + owning account is passed as account_id (a biz* identifier). + + Args: + account_id: The Account (a biz\\__ identifier) the social account is connected to. + + after: Cursor to fetch the page after (from page_info.end_cursor). + + first: The number of posts to return. + + post_id: Return only the single post with this platform id, instead of the full list. + + 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("/social_accounts/{id}/posts", id=id), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "account_id": account_id, + "after": after, + "first": first, + "post_id": post_id, + }, + social_account_posts_params.SocialAccountPostsParams, + ), + ), + cast_to=SocialAccountPostsResponse, + ) + class AsyncSocialAccountsResource(AsyncAPIResource): @cached_property @@ -450,6 +516,66 @@ async def delete( cast_to=SocialAccountDeleteResponse, ) + async def posts( + self, + id: str, + *, + account_id: str, + after: str | Omit = omit, + first: int | Omit = omit, + post_id: 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, + ) -> SocialAccountPostsResponse: + """Lists the existing posts of a connected social account. + + Supported for Facebook + pages and Instagram accounts. Pass post*id to return only that single post. The + owning account is passed as account_id (a biz* identifier). + + Args: + account_id: The Account (a biz\\__ identifier) the social account is connected to. + + after: Cursor to fetch the page after (from page_info.end_cursor). + + first: The number of posts to return. + + post_id: Return only the single post with this platform id, instead of the full list. + + 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("/social_accounts/{id}/posts", id=id), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=await async_maybe_transform( + { + "account_id": account_id, + "after": after, + "first": first, + "post_id": post_id, + }, + social_account_posts_params.SocialAccountPostsParams, + ), + ), + cast_to=SocialAccountPostsResponse, + ) + class SocialAccountsResourceWithRawResponse: def __init__(self, social_accounts: SocialAccountsResource) -> None: @@ -464,6 +590,9 @@ def __init__(self, social_accounts: SocialAccountsResource) -> None: self.delete = to_raw_response_wrapper( social_accounts.delete, ) + self.posts = to_raw_response_wrapper( + social_accounts.posts, + ) class AsyncSocialAccountsResourceWithRawResponse: @@ -479,6 +608,9 @@ def __init__(self, social_accounts: AsyncSocialAccountsResource) -> None: self.delete = async_to_raw_response_wrapper( social_accounts.delete, ) + self.posts = async_to_raw_response_wrapper( + social_accounts.posts, + ) class SocialAccountsResourceWithStreamingResponse: @@ -494,6 +626,9 @@ def __init__(self, social_accounts: SocialAccountsResource) -> None: self.delete = to_streamed_response_wrapper( social_accounts.delete, ) + self.posts = to_streamed_response_wrapper( + social_accounts.posts, + ) class AsyncSocialAccountsResourceWithStreamingResponse: @@ -509,3 +644,6 @@ def __init__(self, social_accounts: AsyncSocialAccountsResource) -> None: self.delete = async_to_streamed_response_wrapper( social_accounts.delete, ) + self.posts = async_to_streamed_response_wrapper( + social_accounts.posts, + ) diff --git a/src/whop_sdk/types/__init__.py b/src/whop_sdk/types/__init__.py index b5fcc2f8..e344364e 100644 --- a/src/whop_sdk/types/__init__.py +++ b/src/whop_sdk/types/__init__.py @@ -158,6 +158,7 @@ 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 .social_account_post import SocialAccountPost as SocialAccountPost from .tax_identifier_type import TaxIdentifierType as TaxIdentifierType from .topup_create_params import TopupCreateParams as TopupCreateParams from .verification_status import VerificationStatus as VerificationStatus @@ -344,6 +345,7 @@ from .payout_destination_category import PayoutDestinationCategory as PayoutDestinationCategory from .payout_method_list_response import PayoutMethodListResponse as PayoutMethodListResponse from .plan_calculate_tax_response import PlanCalculateTaxResponse as PlanCalculateTaxResponse +from .social_account_posts_params import SocialAccountPostsParams as SocialAccountPostsParams 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 @@ -376,6 +378,7 @@ from .payment_created_webhook_event import PaymentCreatedWebhookEvent as PaymentCreatedWebhookEvent from .payment_pending_webhook_event import PaymentPendingWebhookEvent as PaymentPendingWebhookEvent from .resolution_center_case_status import ResolutionCenterCaseStatus as ResolutionCenterCaseStatus +from .social_account_posts_response import SocialAccountPostsResponse as SocialAccountPostsResponse from .support_channel_create_params import SupportChannelCreateParams as SupportChannelCreateParams from .support_channel_list_response import SupportChannelListResponse as SupportChannelListResponse from .company_token_transaction_type import CompanyTokenTransactionType as CompanyTokenTransactionType diff --git a/src/whop_sdk/types/social_account_post.py b/src/whop_sdk/types/social_account_post.py new file mode 100644 index 00000000..4e3d6df6 --- /dev/null +++ b/src/whop_sdk/types/social_account_post.py @@ -0,0 +1,74 @@ +# 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 + +__all__ = ["SocialAccountPost"] + + +class SocialAccountPost(BaseModel): + id: str + """The platform's own identifier for the post or media. + + Use it to reference the post on an ad. + """ + + call_to_action: 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", + "see_details", + "view_instagram_profile", + ] + ] = None + """ + The post's call-to-action button, for example shop_now (Facebook only; null for + Instagram). + """ + + destination_url: Optional[str] = None + """ + The URL the post's call-to-action drives to (Facebook only; null for Instagram). + """ + + media_url: Optional[str] = None + """ + The URL of the post's media — the image for image posts, the playable video file + for video posts. Meta signs these and they expire after roughly 24 hours, so + don't store them. + """ + + thumbnail_url: Optional[str] = None + """ + Poster image for video posts; null for image posts, where media_url is already + the image. Signed and short-lived like media_url. + """ diff --git a/src/whop_sdk/types/social_account_posts_params.py b/src/whop_sdk/types/social_account_posts_params.py new file mode 100644 index 00000000..1da9ce1d --- /dev/null +++ b/src/whop_sdk/types/social_account_posts_params.py @@ -0,0 +1,21 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Required, TypedDict + +__all__ = ["SocialAccountPostsParams"] + + +class SocialAccountPostsParams(TypedDict, total=False): + account_id: Required[str] + """The Account (a biz\\__ identifier) the social account is connected to.""" + + after: str + """Cursor to fetch the page after (from page_info.end_cursor).""" + + first: int + """The number of posts to return.""" + + post_id: str + """Return only the single post with this platform id, instead of the full list.""" diff --git a/src/whop_sdk/types/social_account_posts_response.py b/src/whop_sdk/types/social_account_posts_response.py new file mode 100644 index 00000000..59fa3a0e --- /dev/null +++ b/src/whop_sdk/types/social_account_posts_response.py @@ -0,0 +1,20 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Optional + +from .._models import BaseModel +from .social_account_post import SocialAccountPost + +__all__ = ["SocialAccountPostsResponse", "PageInfo"] + + +class PageInfo(BaseModel): + end_cursor: Optional[str] = None + + has_next_page: bool + + +class SocialAccountPostsResponse(BaseModel): + data: List[SocialAccountPost] + + page_info: PageInfo diff --git a/tests/api_resources/test_social_accounts.py b/tests/api_resources/test_social_accounts.py index b83a3df3..5d776302 100644 --- a/tests/api_resources/test_social_accounts.py +++ b/tests/api_resources/test_social_accounts.py @@ -11,6 +11,7 @@ from tests.utils import assert_matches_type from whop_sdk.types import ( SocialAccount, + SocialAccountPostsResponse, SocialAccountCreateResponse, SocialAccountDeleteResponse, ) @@ -166,6 +167,64 @@ def test_path_params_delete(self, client: Whop) -> None: id="", ) + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_posts(self, client: Whop) -> None: + social_account = client.social_accounts.posts( + id="id", + account_id="account_id", + ) + assert_matches_type(SocialAccountPostsResponse, social_account, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_posts_with_all_params(self, client: Whop) -> None: + social_account = client.social_accounts.posts( + id="id", + account_id="account_id", + after="after", + first=100, + post_id="post_id", + ) + assert_matches_type(SocialAccountPostsResponse, social_account, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_raw_response_posts(self, client: Whop) -> None: + response = client.social_accounts.with_raw_response.posts( + id="id", + account_id="account_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + social_account = response.parse() + assert_matches_type(SocialAccountPostsResponse, social_account, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_streaming_response_posts(self, client: Whop) -> None: + with client.social_accounts.with_streaming_response.posts( + id="id", + account_id="account_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + social_account = response.parse() + assert_matches_type(SocialAccountPostsResponse, social_account, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_path_params_posts(self, client: Whop) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + client.social_accounts.with_raw_response.posts( + id="", + account_id="account_id", + ) + class TestAsyncSocialAccounts: parametrize = pytest.mark.parametrize( @@ -315,3 +374,61 @@ async def test_path_params_delete(self, async_client: AsyncWhop) -> None: await async_client.social_accounts.with_raw_response.delete( id="", ) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_posts(self, async_client: AsyncWhop) -> None: + social_account = await async_client.social_accounts.posts( + id="id", + account_id="account_id", + ) + assert_matches_type(SocialAccountPostsResponse, social_account, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_posts_with_all_params(self, async_client: AsyncWhop) -> None: + social_account = await async_client.social_accounts.posts( + id="id", + account_id="account_id", + after="after", + first=100, + post_id="post_id", + ) + assert_matches_type(SocialAccountPostsResponse, social_account, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_raw_response_posts(self, async_client: AsyncWhop) -> None: + response = await async_client.social_accounts.with_raw_response.posts( + id="id", + account_id="account_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + social_account = await response.parse() + assert_matches_type(SocialAccountPostsResponse, social_account, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_streaming_response_posts(self, async_client: AsyncWhop) -> None: + async with async_client.social_accounts.with_streaming_response.posts( + id="id", + account_id="account_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + social_account = await response.parse() + assert_matches_type(SocialAccountPostsResponse, social_account, 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_posts(self, async_client: AsyncWhop) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + await async_client.social_accounts.with_raw_response.posts( + id="", + account_id="account_id", + ) From fdf91d89b87e222683f63a45e32c9e653994ebc0 Mon Sep 17 00:00:00 2001 From: stlc-bot Date: Fri, 26 Jun 2026 19:36:33 +0000 Subject: [PATCH 080/109] Guard Zerion deposit notify on nil ledger account + add aura card brand Stainless-Generated-From: fd807d6392f9b0dadd3dbd2b26570cd2292fd6a4 --- src/whop_sdk/types/card_brands.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/whop_sdk/types/card_brands.py b/src/whop_sdk/types/card_brands.py index bd4345a7..322a3221 100644 --- a/src/whop_sdk/types/card_brands.py +++ b/src/whop_sdk/types/card_brands.py @@ -44,5 +44,6 @@ "hipercard", "jcblankapay", "cmi", + "aura", "unknown", ] From 8f2d68b7ce48eef36f3fa47a0dcd9c1ea1dd2813 Mon Sep 17 00:00:00 2001 From: stlc-bot Date: Fri, 26 Jun 2026 22:49:25 +0000 Subject: [PATCH 081/109] feat(backend): sortable business referrals + clearer earnings sort keys Stainless-Generated-From: 696ab2dba3a4784734b7fc0e8ecc1151843ec729 --- src/whop_sdk/resources/accounts.py | 36 ++--- src/whop_sdk/resources/deposits.py | 12 +- src/whop_sdk/resources/financial_activity.py | 40 +++--- src/whop_sdk/resources/payouts.py | 4 +- src/whop_sdk/resources/plans.py | 68 +++++----- .../referrals/businesses/businesses.py | 20 ++- .../referrals/businesses/earnings.py | 4 +- src/whop_sdk/resources/swaps.py | 86 +++++++++--- src/whop_sdk/resources/transfers.py | 4 +- src/whop_sdk/resources/verifications.py | 114 ++++++++-------- src/whop_sdk/types/account.py | 127 +++++++++--------- src/whop_sdk/types/account_update_params.py | 18 +-- src/whop_sdk/types/card_create_response.py | 15 ++- src/whop_sdk/types/card_list_response.py | 15 ++- src/whop_sdk/types/card_retrieve_response.py | 15 ++- src/whop_sdk/types/company_list_response.py | 4 +- src/whop_sdk/types/course.py | 2 +- src/whop_sdk/types/course_list_response.py | 2 +- src/whop_sdk/types/deposit_create_params.py | 9 +- src/whop_sdk/types/deposit_create_response.py | 20 ++- src/whop_sdk/types/deposit_list_response.py | 9 ++ src/whop_sdk/types/dispute.py | 8 +- .../types/experience_list_response.py | 4 +- .../types/financial_activity_list_params.py | 8 +- .../types/financial_activity_list_response.py | 54 +++++++- .../types/forum_post_list_response.py | 2 +- ...identity_profile_approved_webhook_event.py | 9 +- ...tity_profile_needs_action_webhook_event.py | 9 +- ...identity_profile_rejected_webhook_event.py | 9 +- .../identity_profile_updated_webhook_event.py | 9 +- ...r_account_funds_available_webhook_event.py | 4 +- .../types/ledger_account_retrieve_response.py | 4 +- src/whop_sdk/types/lesson.py | 6 +- .../types/membership_list_response.py | 8 +- src/whop_sdk/types/payment_list_response.py | 14 +- src/whop_sdk/types/payout_list_params.py | 2 +- src/whop_sdk/types/payout_list_response.py | 11 ++ .../types/plan_calculate_tax_params.py | 8 +- src/whop_sdk/types/plan_create_params.py | 24 ++-- src/whop_sdk/types/plan_list_response.py | 83 +++++------- src/whop_sdk/types/plan_update_params.py | 18 +-- .../business_list_earnings_params.py | 2 +- .../business_list_earnings_response.py | 8 +- .../types/referrals/business_list_params.py | 6 + .../types/referrals/business_list_response.py | 15 ++- .../referrals/business_retrieve_response.py | 15 ++- .../businesses/earning_list_params.py | 2 +- .../businesses/earning_list_response.py | 8 +- .../types/refund_created_webhook_event.py | 8 +- .../types/refund_retrieve_response.py | 8 +- .../types/refund_updated_webhook_event.py | 8 +- src/whop_sdk/types/review_list_response.py | 2 +- .../types/review_retrieve_response.py | 6 +- .../types/shared/checkout_configuration.py | 22 +-- src/whop_sdk/types/shared/company.py | 4 +- src/whop_sdk/types/shared/experience.py | 10 +- src/whop_sdk/types/shared/forum_post.py | 2 +- src/whop_sdk/types/shared/membership.py | 8 +- src/whop_sdk/types/shared/payment.py | 14 +- src/whop_sdk/types/shared/plan.py | 119 ++++++++-------- src/whop_sdk/types/shared/product.py | 55 ++++---- .../types/shared/product_list_item.py | 24 ++-- src/whop_sdk/types/swap_create_params.py | 15 ++- .../types/swap_create_quote_params.py | 18 ++- .../types/swap_create_quote_response.py | 13 ++ src/whop_sdk/types/swap_create_response.py | 8 +- src/whop_sdk/types/swap_list_response.py | 6 + src/whop_sdk/types/swap_retrieve_response.py | 5 + src/whop_sdk/types/transfer_create_params.py | 2 +- .../types/transfer_create_response.py | 23 ++++ src/whop_sdk/types/transfer_list_response.py | 9 ++ .../types/transfer_retrieve_response.py | 23 ++++ src/whop_sdk/types/user.py | 38 +++--- .../types/verification_create_params.py | 44 +++--- .../types/verification_create_response.py | 41 ++++-- .../types/verification_delete_response.py | 2 + .../types/verification_list_response.py | 42 ++++-- .../types/verification_retrieve_response.py | 41 ++++-- .../types/verification_update_params.py | 26 ++-- .../types/verification_update_response.py | 41 ++++-- .../referrals/test_businesses.py | 4 + 81 files changed, 1010 insertions(+), 655 deletions(-) diff --git a/src/whop_sdk/resources/accounts.py b/src/whop_sdk/resources/accounts.py index 4f8189cb..a72741cd 100644 --- a/src/whop_sdk/resources/accounts.py +++ b/src/whop_sdk/resources/accounts.py @@ -177,21 +177,21 @@ def update( banner_image: Attachment input for the account banner image. - business_type: The high-level business category for the account. + business_type: High-level business category for the account. - country: The country the account is located in. + country: Country where the account is located. - description: A promotional description for the account. + description: Account promotional description. - featured_affiliate_product_id: The ID of the product to feature for affiliates. Pass null to clear. + featured_affiliate_product_id: The ID of the product to feature for affiliates. Pass `null` to clear. - home_preferences: Preferences for the public business home page. + home_preferences: Public account home page preferences. - industry_group: The industry group the account belongs to. + industry_group: Account industry group. - industry_type: The specific industry vertical the account operates in. + industry_type: Specific industry vertical for the account. - invoice_prefix: The prefix to use for account invoices. + invoice_prefix: Prefix used for account invoices. logo: Attachment input for the account logo. @@ -222,7 +222,7 @@ def update( social_links: The full list of social links to display for the account. - store_page_config: Store page display configuration for the account. + store_page_config: Account store page display configuration. target_audience: The target audience for this account. @@ -516,21 +516,21 @@ async def update( banner_image: Attachment input for the account banner image. - business_type: The high-level business category for the account. + business_type: High-level business category for the account. - country: The country the account is located in. + country: Country where the account is located. - description: A promotional description for the account. + description: Account promotional description. - featured_affiliate_product_id: The ID of the product to feature for affiliates. Pass null to clear. + featured_affiliate_product_id: The ID of the product to feature for affiliates. Pass `null` to clear. - home_preferences: Preferences for the public business home page. + home_preferences: Public account home page preferences. - industry_group: The industry group the account belongs to. + industry_group: Account industry group. - industry_type: The specific industry vertical the account operates in. + industry_type: Specific industry vertical for the account. - invoice_prefix: The prefix to use for account invoices. + invoice_prefix: Prefix used for account invoices. logo: Attachment input for the account logo. @@ -561,7 +561,7 @@ async def update( social_links: The full list of social links to display for the account. - store_page_config: Store page display configuration for the account. + store_page_config: Account store page display configuration. target_audience: The target audience for this account. diff --git a/src/whop_sdk/resources/deposits.py b/src/whop_sdk/resources/deposits.py index 82ceb67d..760571df 100644 --- a/src/whop_sdk/resources/deposits.py +++ b/src/whop_sdk/resources/deposits.py @@ -67,11 +67,11 @@ def create( destination: Destination account ID or wallet address. Object form is supported for compatibility. - amount: Optional amount to deposit. + amount: Amount to prefill on hosted deposit page. - metadata: Arbitrary metadata echoed in the response. + metadata: Metadata to include with the deposit response. - network: Optional destination network override. + network: Destination network override. extra_headers: Send extra headers @@ -181,11 +181,11 @@ async def create( destination: Destination account ID or wallet address. Object form is supported for compatibility. - amount: Optional amount to deposit. + amount: Amount to prefill on hosted deposit page. - metadata: Arbitrary metadata echoed in the response. + metadata: Metadata to include with the deposit response. - network: Optional destination network override. + network: Destination network override. extra_headers: Send extra headers diff --git a/src/whop_sdk/resources/financial_activity.py b/src/whop_sdk/resources/financial_activity.py index 90a4e652..1dda7a0b 100644 --- a/src/whop_sdk/resources/financial_activity.py +++ b/src/whop_sdk/resources/financial_activity.py @@ -64,31 +64,31 @@ def list( extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> FinancialActivityListResponse: - """Lists financial activity rows for a ledger account. - - Rows are derived from ledger - lines and include typed resource and source objects that clients can use for - presentation and navigation. The ledger's owner is passed as exactly one of - account*id (a biz* identifier) or user*id (a user* identifier). + """ + Returns a paginated activity feed for one account or user, derived from ledger + lines with typed resource and source objects for presentation. Pass exactly one + of `account_id` (a `biz_` identifier) or `user_id` (a `user_` identifier). + Filter by line type, currency, posted timestamp, or settlement date to reconcile + a specific window. Args: account_id: The owning account ID (a biz\\__ identifier). Provide this or user_id. - available_after: Only include rows whose funds became withdrawable on or after this YYYY-MM-DD + available_after: Only include rows whose funds became withdrawable on or after this `YYYY-MM-DD` settlement date (UTC), distinct from posted_at. Requires currency. - available_before: Only include rows whose funds became withdrawable on or before this YYYY-MM-DD + available_before: Only include rows whose funds became withdrawable on or before this `YYYY-MM-DD` settlement date (UTC). Set equal to available_after for a single day. Requires currency. - currency: Optional currency code filter, for example usd. + currency: Optional currency code filter, for example `usd`. cursor: Cursor returned by the previous page. limit: Maximum number of rows to return. line_types: Optional ledger line categories to include. Some categories (for example - onchain_deposit, which covers inbound crypto deposits such as MoonPay onramps) + `onchain_deposit`, which covers inbound crypto deposits such as MoonPay onramps) are only returned when explicitly requested here. posted_after: Only include rows posted after this ISO 8601 timestamp. @@ -172,31 +172,31 @@ async def list( extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> FinancialActivityListResponse: - """Lists financial activity rows for a ledger account. - - Rows are derived from ledger - lines and include typed resource and source objects that clients can use for - presentation and navigation. The ledger's owner is passed as exactly one of - account*id (a biz* identifier) or user*id (a user* identifier). + """ + Returns a paginated activity feed for one account or user, derived from ledger + lines with typed resource and source objects for presentation. Pass exactly one + of `account_id` (a `biz_` identifier) or `user_id` (a `user_` identifier). + Filter by line type, currency, posted timestamp, or settlement date to reconcile + a specific window. Args: account_id: The owning account ID (a biz\\__ identifier). Provide this or user_id. - available_after: Only include rows whose funds became withdrawable on or after this YYYY-MM-DD + available_after: Only include rows whose funds became withdrawable on or after this `YYYY-MM-DD` settlement date (UTC), distinct from posted_at. Requires currency. - available_before: Only include rows whose funds became withdrawable on or before this YYYY-MM-DD + available_before: Only include rows whose funds became withdrawable on or before this `YYYY-MM-DD` settlement date (UTC). Set equal to available_after for a single day. Requires currency. - currency: Optional currency code filter, for example usd. + currency: Optional currency code filter, for example `usd`. cursor: Cursor returned by the previous page. limit: Maximum number of rows to return. line_types: Optional ledger line categories to include. Some categories (for example - onchain_deposit, which covers inbound crypto deposits such as MoonPay onramps) + `onchain_deposit`, which covers inbound crypto deposits such as MoonPay onramps) are only returned when explicitly requested here. posted_after: Only include rows posted after this ISO 8601 timestamp. diff --git a/src/whop_sdk/resources/payouts.py b/src/whop_sdk/resources/payouts.py index 4394b6ea..90ec1ab1 100644 --- a/src/whop_sdk/resources/payouts.py +++ b/src/whop_sdk/resources/payouts.py @@ -73,7 +73,7 @@ def list( before: Cursor to fetch the page before (from page_info.start_cursor). - currency: Optional currency code filter, for example usd. + currency: Optional currency code filter, for example `usd`. first: Number of payouts to return from the start of the window. @@ -165,7 +165,7 @@ def list( before: Cursor to fetch the page before (from page_info.start_cursor). - currency: Optional currency code filter, for example usd. + currency: Optional currency code filter, for example `usd`. first: Number of payouts to return from the start of the window. diff --git a/src/whop_sdk/resources/plans.py b/src/whop_sdk/resources/plans.py index 59c15215..16a12e9b 100644 --- a/src/whop_sdk/resources/plans.py +++ b/src/whop_sdk/resources/plans.py @@ -95,8 +95,7 @@ def create( 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. + billing_period: Recurring billing interval in days, such as 30 for monthly or 365 for annual. checkout_styling: Checkout styling overrides for this plan. @@ -107,14 +106,13 @@ def create( description: A text description of the plan displayed to customers on the product page. - expiration_days: The number of days until the membership expires and access is revoked. + expiration_days: Access duration in days before the membership expires. image: An image displayed on the product page to represent this plan. - initial_price: The amount charged on the first purchase, in the plan's currency (e.g., 10.43 - for $10.43). + initial_price: Initial amount charged in the plan's currency, e.g. 10.43 for $10.43. - internal_notes: Private notes visible only to the business owner. Not shown to customers. + internal_notes: Private notes visible only to the account owner. Not shown to customers. legacy_payment_method_controls: Whether this plan uses legacy payment method controls. @@ -127,25 +125,25 @@ def create( payment_method_configuration: Explicit payment method configuration for the plan. When not provided, the account's defaults apply. - plan_type: The billing type of the plan, such as one_time or renewal. + plan_type: Plan billing type, such as `one_time` or `renewal`. product_id: The unique identifier of the product to attach this plan to. - release_method: The method used to sell this plan (e.g., buy_now, waitlist). + release_method: Sales method for this plan, such as `buy_now` or `waitlist`. renewal_price: The amount charged each billing period for recurring plans, in the plan's currency. - split_pay_required_payments: The number of installment payments required before the subscription pauses. + split_pay_required_payments: Installment payments required before the subscription pauses. stock: The maximum number of units available for purchase. Ignored when unlimited_stock is true. - three_ds_level: The 3D Secure behavior for this plan. Send null to inherit the account default. + three_ds_level: 3D Secure behavior for this plan. Send `null` to inherit the account default. title: The display name of the plan shown to customers on the product page. - trial_period_days: The number of free trial days before the first charge on a recurring plan. + trial_period_days: Free trial duration before the first recurring charge. unlimited_stock: Whether the plan has unlimited stock. When true, the stock field is ignored. @@ -273,8 +271,7 @@ def update( 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. + billing_period: Recurring billing interval in days, such as 30 for monthly or 365 for annual. checkout_styling: Checkout styling overrides for this plan. @@ -285,14 +282,13 @@ def update( description: A text description of the plan displayed to customers on the product page. - expiration_days: The number of days until the membership expires and access is revoked. + expiration_days: Access duration in days before the membership expires. image: An image displayed on the product page to represent this plan. - initial_price: The amount charged on the first purchase, in the plan's currency (e.g., 10.43 - for $10.43). + initial_price: Initial amount charged in the plan's currency, e.g. 10.43 for $10.43. - internal_notes: Private notes visible only to the business owner. Not shown to customers. + internal_notes: Private notes visible only to the account owner. Not shown to customers. legacy_payment_method_controls: Whether this plan uses legacy payment method controls. @@ -317,11 +313,11 @@ def update( strike_through_renewal_price: A comparison price displayed with a strikethrough for the renewal price. - three_ds_level: The 3D Secure behavior for this plan. Send null to inherit the account default. + three_ds_level: 3D Secure behavior for this plan. Send `null` to inherit the account default. title: The display name of the plan shown to customers on the product page. - trial_period_days: The number of free trial days before the first charge on a recurring plan. + trial_period_days: Free trial duration before the first recurring charge. unlimited_stock: Whether the plan has unlimited stock. When true, the stock field is ignored. @@ -621,8 +617,7 @@ async def create( 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. + billing_period: Recurring billing interval in days, such as 30 for monthly or 365 for annual. checkout_styling: Checkout styling overrides for this plan. @@ -633,14 +628,13 @@ async def create( description: A text description of the plan displayed to customers on the product page. - expiration_days: The number of days until the membership expires and access is revoked. + expiration_days: Access duration in days before the membership expires. image: An image displayed on the product page to represent this plan. - initial_price: The amount charged on the first purchase, in the plan's currency (e.g., 10.43 - for $10.43). + initial_price: Initial amount charged in the plan's currency, e.g. 10.43 for $10.43. - internal_notes: Private notes visible only to the business owner. Not shown to customers. + internal_notes: Private notes visible only to the account owner. Not shown to customers. legacy_payment_method_controls: Whether this plan uses legacy payment method controls. @@ -653,25 +647,25 @@ async def create( payment_method_configuration: Explicit payment method configuration for the plan. When not provided, the account's defaults apply. - plan_type: The billing type of the plan, such as one_time or renewal. + plan_type: Plan billing type, such as `one_time` or `renewal`. product_id: The unique identifier of the product to attach this plan to. - release_method: The method used to sell this plan (e.g., buy_now, waitlist). + release_method: Sales method for this plan, such as `buy_now` or `waitlist`. renewal_price: The amount charged each billing period for recurring plans, in the plan's currency. - split_pay_required_payments: The number of installment payments required before the subscription pauses. + split_pay_required_payments: Installment payments required before the subscription pauses. stock: The maximum number of units available for purchase. Ignored when unlimited_stock is true. - three_ds_level: The 3D Secure behavior for this plan. Send null to inherit the account default. + three_ds_level: 3D Secure behavior for this plan. Send `null` to inherit the account default. title: The display name of the plan shown to customers on the product page. - trial_period_days: The number of free trial days before the first charge on a recurring plan. + trial_period_days: Free trial duration before the first recurring charge. unlimited_stock: Whether the plan has unlimited stock. When true, the stock field is ignored. @@ -799,8 +793,7 @@ async def update( 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. + billing_period: Recurring billing interval in days, such as 30 for monthly or 365 for annual. checkout_styling: Checkout styling overrides for this plan. @@ -811,14 +804,13 @@ async def update( description: A text description of the plan displayed to customers on the product page. - expiration_days: The number of days until the membership expires and access is revoked. + expiration_days: Access duration in days before the membership expires. image: An image displayed on the product page to represent this plan. - initial_price: The amount charged on the first purchase, in the plan's currency (e.g., 10.43 - for $10.43). + initial_price: Initial amount charged in the plan's currency, e.g. 10.43 for $10.43. - internal_notes: Private notes visible only to the business owner. Not shown to customers. + internal_notes: Private notes visible only to the account owner. Not shown to customers. legacy_payment_method_controls: Whether this plan uses legacy payment method controls. @@ -843,11 +835,11 @@ async def update( strike_through_renewal_price: A comparison price displayed with a strikethrough for the renewal price. - three_ds_level: The 3D Secure behavior for this plan. Send null to inherit the account default. + three_ds_level: 3D Secure behavior for this plan. Send `null` to inherit the account default. title: The display name of the plan shown to customers on the product page. - trial_period_days: The number of free trial days before the first charge on a recurring plan. + trial_period_days: Free trial duration before the first recurring charge. unlimited_stock: Whether the plan has unlimited stock. When true, the stock field is ignored. diff --git a/src/whop_sdk/resources/referrals/businesses/businesses.py b/src/whop_sdk/resources/referrals/businesses/businesses.py index fbe717b0..14ecbeed 100644 --- a/src/whop_sdk/resources/referrals/businesses/businesses.py +++ b/src/whop_sdk/resources/referrals/businesses/businesses.py @@ -99,6 +99,8 @@ def list( first: int | Omit = omit, has_earnings: bool | Omit = omit, last: int | Omit = omit, + order: Literal["asc", "desc"] | Omit = omit, + sort: Literal["created_at", "referral_started_at", "referral_expires_at", "payout_percentage"] | Omit = omit, status: Literal["active", "removed"] | 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. @@ -123,6 +125,10 @@ def list( last: Number of business referrals to return from the end of the window. + order: Sort direction. + + sort: Field to sort business referrals by. + status: Filter by referral status. extra_headers: Send extra headers @@ -148,6 +154,8 @@ def list( "first": first, "has_earnings": has_earnings, "last": last, + "order": order, + "sort": sort, "status": status, }, business_list_params.BusinessListParams, @@ -165,7 +173,7 @@ def list_earnings( include: Literal["receipt_fees"] | Omit = omit, last: int | Omit = omit, order: Literal["asc", "desc"] | Omit = omit, - sort: Literal["created_at", "amount", "payout_at"] | Omit = omit, + sort: Literal["created_at", "commission_amount", "transaction_amount", "payout_at"] | Omit = omit, status: Literal["awaiting_settlement", "pending", "completed", "canceled", "reversed"] | 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. @@ -287,6 +295,8 @@ def list( first: int | Omit = omit, has_earnings: bool | Omit = omit, last: int | Omit = omit, + order: Literal["asc", "desc"] | Omit = omit, + sort: Literal["created_at", "referral_started_at", "referral_expires_at", "payout_percentage"] | Omit = omit, status: Literal["active", "removed"] | 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. @@ -311,6 +321,10 @@ def list( last: Number of business referrals to return from the end of the window. + order: Sort direction. + + sort: Field to sort business referrals by. + status: Filter by referral status. extra_headers: Send extra headers @@ -336,6 +350,8 @@ def list( "first": first, "has_earnings": has_earnings, "last": last, + "order": order, + "sort": sort, "status": status, }, business_list_params.BusinessListParams, @@ -353,7 +369,7 @@ def list_earnings( include: Literal["receipt_fees"] | Omit = omit, last: int | Omit = omit, order: Literal["asc", "desc"] | Omit = omit, - sort: Literal["created_at", "amount", "payout_at"] | Omit = omit, + sort: Literal["created_at", "commission_amount", "transaction_amount", "payout_at"] | Omit = omit, status: Literal["awaiting_settlement", "pending", "completed", "canceled", "reversed"] | 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. diff --git a/src/whop_sdk/resources/referrals/businesses/earnings.py b/src/whop_sdk/resources/referrals/businesses/earnings.py index 61c264bb..d853b430 100644 --- a/src/whop_sdk/resources/referrals/businesses/earnings.py +++ b/src/whop_sdk/resources/referrals/businesses/earnings.py @@ -54,7 +54,7 @@ def list( include: Literal["receipt_fees"] | Omit = omit, last: int | Omit = omit, order: Literal["asc", "desc"] | Omit = omit, - sort: Literal["created_at", "amount", "payout_at"] | Omit = omit, + sort: Literal["created_at", "commission_amount", "transaction_amount", "payout_at"] | Omit = omit, status: Literal["awaiting_settlement", "pending", "completed", "canceled", "reversed"] | 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. @@ -143,7 +143,7 @@ def list( include: Literal["receipt_fees"] | Omit = omit, last: int | Omit = omit, order: Literal["asc", "desc"] | Omit = omit, - sort: Literal["created_at", "amount", "payout_at"] | Omit = omit, + sort: Literal["created_at", "commission_amount", "transaction_amount", "payout_at"] | Omit = omit, status: Literal["awaiting_settlement", "pending", "completed", "canceled", "reversed"] | 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. diff --git a/src/whop_sdk/resources/swaps.py b/src/whop_sdk/resources/swaps.py index 858cdb6c..77fbcc9d 100644 --- a/src/whop_sdk/resources/swaps.py +++ b/src/whop_sdk/resources/swaps.py @@ -65,17 +65,25 @@ def create( ) -> SwapCreateResponse: """Executes a swap from the account's wallet. - Runs asynchronously — poll GET + Runs asynchronously; poll GET /swaps/{id} for status. Args: account_id: Business or user account ID (biz*\\** / user*\\**). - amount: Input token amount. + amount: Source token amount. - from_token: Source token, by contract address or ticker symbol (e.g. "USDT"). + from_token: Source token contract address or ticker symbol, such as "USDT". - to_token: Destination token, by contract address or ticker symbol (e.g. "XAUT"). + to_token: Destination token contract address or ticker symbol, such as "XAUT". + + from_chain: Source chain name or chain ID. Defaults to the source token's chain when + omitted. + + slippage_bps: Maximum slippage tolerance in basis points. + + to_chain: Destination chain name or chain ID. Defaults to the destination token's chain + when omitted. extra_headers: Send extra headers @@ -149,9 +157,10 @@ def list( extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> SwapListResponse: - """ - Lists the account's swaps — currently its in-flight or most recent swap, so zero - or one rows. + """Lists the account's swaps. + + Currently returns the in-flight or most recent swap, + so zero or one rows. Args: account_id: Business or user account ID (biz*\\** / user*\\**). @@ -200,11 +209,25 @@ def create_quote( No funds move and nothing is persisted. Args: - amount: Input token amount. + amount: Source token amount. + + from_token: Source token contract address or ticker symbol, such as "USDT". + + to_token: Destination token contract address or ticker symbol, such as "XAUT". + + from_address: Source wallet address used for the quote. + + from_chain: Source chain name or chain ID. Defaults to the source token's chain when + omitted. + + metadata: Metadata to include with the quote response. + + slippage_bps: Maximum slippage tolerance in basis points. - from_token: Source token, by contract address or ticker symbol (e.g. "USDT"). + to_address: Destination wallet address used for the quote. - to_token: Destination token, by contract address or ticker symbol (e.g. "XAUT"). + to_chain: Destination chain name or chain ID. Defaults to the destination token's chain + when omitted. extra_headers: Send extra headers @@ -276,17 +299,25 @@ async def create( ) -> SwapCreateResponse: """Executes a swap from the account's wallet. - Runs asynchronously — poll GET + Runs asynchronously; poll GET /swaps/{id} for status. Args: account_id: Business or user account ID (biz*\\** / user*\\**). - amount: Input token amount. + amount: Source token amount. - from_token: Source token, by contract address or ticker symbol (e.g. "USDT"). + from_token: Source token contract address or ticker symbol, such as "USDT". - to_token: Destination token, by contract address or ticker symbol (e.g. "XAUT"). + to_token: Destination token contract address or ticker symbol, such as "XAUT". + + from_chain: Source chain name or chain ID. Defaults to the source token's chain when + omitted. + + slippage_bps: Maximum slippage tolerance in basis points. + + to_chain: Destination chain name or chain ID. Defaults to the destination token's chain + when omitted. extra_headers: Send extra headers @@ -360,9 +391,10 @@ async def list( extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> SwapListResponse: - """ - Lists the account's swaps — currently its in-flight or most recent swap, so zero - or one rows. + """Lists the account's swaps. + + Currently returns the in-flight or most recent swap, + so zero or one rows. Args: account_id: Business or user account ID (biz*\\** / user*\\**). @@ -411,11 +443,25 @@ async def create_quote( No funds move and nothing is persisted. Args: - amount: Input token amount. + amount: Source token amount. + + from_token: Source token contract address or ticker symbol, such as "USDT". + + to_token: Destination token contract address or ticker symbol, such as "XAUT". + + from_address: Source wallet address used for the quote. + + from_chain: Source chain name or chain ID. Defaults to the source token's chain when + omitted. + + metadata: Metadata to include with the quote response. + + slippage_bps: Maximum slippage tolerance in basis points. - from_token: Source token, by contract address or ticker symbol (e.g. "USDT"). + to_address: Destination wallet address used for the quote. - to_token: Destination token, by contract address or ticker symbol (e.g. "XAUT"). + to_chain: Destination chain name or chain ID. Defaults to the destination token's chain + when omitted. extra_headers: Send extra headers diff --git a/src/whop_sdk/resources/transfers.py b/src/whop_sdk/resources/transfers.py index 4472c431..896acdd8 100644 --- a/src/whop_sdk/resources/transfers.py +++ b/src/whop_sdk/resources/transfers.py @@ -82,7 +82,7 @@ def create( origin_id: The account sending the funds. A user ID (user_xxx), account ID (biz_xxx), or ledger account ID (ldgr_xxx). - currency: The currency, such as usd. Required for ledger transfers. + currency: Currency, such as `usd`. Required for ledger transfers. destination_id: The recipient. Required for ledger and wallet*send (a user*/biz*/ldgr* ID, or — for sends — an email). Omit for claim_link. @@ -306,7 +306,7 @@ async def create( origin_id: The account sending the funds. A user ID (user_xxx), account ID (biz_xxx), or ledger account ID (ldgr_xxx). - currency: The currency, such as usd. Required for ledger transfers. + currency: Currency, such as `usd`. Required for ledger transfers. destination_id: The recipient. Required for ledger and wallet*send (a user*/biz*/ldgr* ID, or — for sends — an email). Omit for claim_link. diff --git a/src/whop_sdk/resources/verifications.py b/src/whop_sdk/resources/verifications.py index 3403ff34..708b7ad3 100644 --- a/src/whop_sdk/resources/verifications.py +++ b/src/whop_sdk/resources/verifications.py @@ -80,40 +80,37 @@ def create( Args: account_id: The account ID to verify (biz\\__ tag). - address: Optional pre-fill claim. Address (line1, city, state, postal_code). + address: Address to prefill in provider session. - business_name: Optional pre-fill claim for businesses. + business_name: Legal business name to prefill in provider session. - business_structure: Optional. Business structure (e.g. llc, corporation). + business_structure: Business entity structure, such as `llc` or `corporation`. - business_website: Optional. Business website URL. Accepted for both individual and business - verifications on company accounts; persisted to the account's metadata and used - to provision the payout account on approval. Whop store pages are rejected. + business_website: Business website URL for account verifications. Stored on the account and used + when provisioning the payout account. Whop store pages are rejected. - country: Optional pre-fill claim. Country code; for businesses, the country of + country: Country code for provider session. For businesses, use the country of incorporation. - date_of_birth: Optional pre-fill claim. Seeds the Sumsub session; attested values come from - Sumsub on approval. + date_of_birth: Date of birth to prefill in provider session. Approved values come from the + provider. - first_name: Optional pre-fill claim. Seeds the Sumsub session; attested values come from - Sumsub on approval. + first_name: First name to prefill in provider session. Approved values come from the + provider. - kind: The verification type. Defaults to individual. + kind: Verification profile type. Defaults to `individual`. - last_name: Optional pre-fill claim. Seeds the Sumsub session; attested values come from - Sumsub on approval. + last_name: Last name to prefill in provider session. Approved values come from the + provider. - phone: Optional pre-fill claim — phone number. + phone: Phone number to prefill in provider session. - place_of_incorporation: Optional. Place of incorporation (state/region); maps to the business address - state. + place_of_incorporation: State or region of incorporation for business verification. restart: Whether to restart an in-flight verification. - tax_identification_number: Optional. Tax identification number — SSN for individuals, EIN for businesses. - Tokenized in transit, never stored raw; stored on the profile so the payout - account, provisioned on approval, doesn't raise a tax-id RFI. + tax_identification_number: Tax ID for verification, such as an SSN for individuals or EIN for businesses. + Tokenized in transit and stored only on the profile. extra_headers: Send extra headers @@ -211,24 +208,24 @@ def update( RFIs. Args: - business_address: The business address. + business_address: Business address to submit for verification. - business_name: The business name. + business_name: Legal business name to submit for verification. - business_structure: The business structure. + business_structure: Business entity structure to submit for verification. - country: The country code. + country: Country code to submit for verification. - date_of_birth: The date of birth. + date_of_birth: Date of birth to submit for individual verification. - first_name: The first name on the verification. + first_name: First name to submit for individual verification. - last_name: The last name on the verification. + last_name: Last name to submit for individual verification. - personal_address: The personal address. + personal_address: Personal address to submit for individual verification. - rfis: RFI responses. Each entry must include id and a value, address, or files - payload. + rfis: Responses to outstanding RFIs. Each entry must include an RFI ID and a value, + address, or files payload. extra_headers: Send extra headers @@ -385,40 +382,37 @@ async def create( Args: account_id: The account ID to verify (biz\\__ tag). - address: Optional pre-fill claim. Address (line1, city, state, postal_code). + address: Address to prefill in provider session. - business_name: Optional pre-fill claim for businesses. + business_name: Legal business name to prefill in provider session. - business_structure: Optional. Business structure (e.g. llc, corporation). + business_structure: Business entity structure, such as `llc` or `corporation`. - business_website: Optional. Business website URL. Accepted for both individual and business - verifications on company accounts; persisted to the account's metadata and used - to provision the payout account on approval. Whop store pages are rejected. + business_website: Business website URL for account verifications. Stored on the account and used + when provisioning the payout account. Whop store pages are rejected. - country: Optional pre-fill claim. Country code; for businesses, the country of + country: Country code for provider session. For businesses, use the country of incorporation. - date_of_birth: Optional pre-fill claim. Seeds the Sumsub session; attested values come from - Sumsub on approval. + date_of_birth: Date of birth to prefill in provider session. Approved values come from the + provider. - first_name: Optional pre-fill claim. Seeds the Sumsub session; attested values come from - Sumsub on approval. + first_name: First name to prefill in provider session. Approved values come from the + provider. - kind: The verification type. Defaults to individual. + kind: Verification profile type. Defaults to `individual`. - last_name: Optional pre-fill claim. Seeds the Sumsub session; attested values come from - Sumsub on approval. + last_name: Last name to prefill in provider session. Approved values come from the + provider. - phone: Optional pre-fill claim — phone number. + phone: Phone number to prefill in provider session. - place_of_incorporation: Optional. Place of incorporation (state/region); maps to the business address - state. + place_of_incorporation: State or region of incorporation for business verification. restart: Whether to restart an in-flight verification. - tax_identification_number: Optional. Tax identification number — SSN for individuals, EIN for businesses. - Tokenized in transit, never stored raw; stored on the profile so the payout - account, provisioned on approval, doesn't raise a tax-id RFI. + tax_identification_number: Tax ID for verification, such as an SSN for individuals or EIN for businesses. + Tokenized in transit and stored only on the profile. extra_headers: Send extra headers @@ -518,24 +512,24 @@ async def update( RFIs. Args: - business_address: The business address. + business_address: Business address to submit for verification. - business_name: The business name. + business_name: Legal business name to submit for verification. - business_structure: The business structure. + business_structure: Business entity structure to submit for verification. - country: The country code. + country: Country code to submit for verification. - date_of_birth: The date of birth. + date_of_birth: Date of birth to submit for individual verification. - first_name: The first name on the verification. + first_name: First name to submit for individual verification. - last_name: The last name on the verification. + last_name: Last name to submit for individual verification. - personal_address: The personal address. + personal_address: Personal address to submit for individual verification. - rfis: RFI responses. Each entry must include id and a value, address, or files - payload. + rfis: Responses to outstanding RFIs. Each entry must include an RFI ID and a value, + address, or files payload. extra_headers: Send extra headers diff --git a/src/whop_sdk/types/account.py b/src/whop_sdk/types/account.py index 1c837082..54334f8e 100644 --- a/src/whop_sdk/types/account.py +++ b/src/whop_sdk/types/account.py @@ -10,40 +10,37 @@ class Balance(BaseModel): - """The account's holdings (crypto and fiat), each with its USD value. - - Empty when total_usd is null (not computed) - """ + """Account holdings, each with USD value. Empty when `total_usd` is `null`.""" balance: str - """The total amount held in native units, as a decimal string""" + """Total amount held in native units, as a decimal string.""" breakdown: object """ - The holding split into available, pending, and reserve amounts (native-unit - decimal strings). On-chain crypto is entirely available; good_funds and fiat - cash can have pending/reserve portions + Balance split into available, pending, and reserve amounts, as native-unit + decimal strings. On-chain crypto is entirely available; good_funds and fiat cash + can have pending or reserve portions. """ icon_url: Optional[str] = None - """The URL of the holding's icon, when available""" + """Holding icon URL.""" name: str """The holding's display name""" price_usd: Optional[float] = None - """The USD price per unit, or null when no exchange rate is available""" + """USD price per unit, or `null` when no exchange rate is available.""" symbol: str - """The holding's display symbol, e.g. USDT, cbBTC, or EUR""" + """Holding display symbol, such as `USDT`, `cbBTC`, or `EUR`.""" value_usd: Optional[str] = None - """The total USD value of the holding, or null when no exchange rate is available""" + """Holding USD value, or `null` when no exchange rate is available.""" class Capabilities(BaseModel): """ - Each payment rail's status: active, inactive, or pending (pending means onboarding or review is in progress) + Payment rails enabled for this account, each `active`, `inactive`, or `pending` (onboarding or review in progress). Computed only on `retrieve` and `me` for callers with `company:balance:read` scope; `null` otherwise. """ accept_bank_payments: Literal["active", "inactive", "pending"] @@ -82,7 +79,7 @@ class Capabilities(BaseModel): class RecommendedAction(BaseModel): """ - Optional actions that unlock capabilities or grow the account, same shape as required_actions + Optional actions that unlock capabilities or grow the account, same shape as `required_actions`. Computed only on `retrieve` and `me`; `null` otherwise. """ action: Literal["apply_for_financing", "migrate_from_stripe", "accept_first_payment", "join_whop_university"] @@ -103,7 +100,7 @@ class RecommendedAction(BaseModel): """Supporting copy, or empty""" icon_url: Optional[str] = None - """Illustration icon URL, or null""" + """Illustration icon URL, or `null`""" status: Literal["optional"] """Always optional — never blocking""" @@ -114,7 +111,7 @@ class RecommendedAction(BaseModel): class RequiredAction(BaseModel): """ - Actions the account owner must take to unblock capabilities like payouts and card spend, ordered by display priority + Actions the account owner must take to unblock capabilities like payouts and card spend, ordered by display priority. Computed only on `retrieve` and `me` for callers with `company:balance:read` scope; `null` otherwise. """ action: Literal["deposit_funds", "submit_information_request", "verify_identity", "connect_fulfillment_tracker"] @@ -145,10 +142,10 @@ class RequiredAction(BaseModel): class Wallet(BaseModel): - """The account's primary crypto wallet, or null if none has been provisioned""" + """Account primary crypto wallet, or `null` if none has been provisioned.""" id: str - """The ID of the wallet, which will look like wallet\\__******\\********""" + """Wallet ID, prefixed `wallet_`.""" address: str """The on-chain address of the wallet""" @@ -159,129 +156,133 @@ class Wallet(BaseModel): class Account(BaseModel): id: str - """The ID of the account, which will look like biz\\__******\\********""" + """Account ID, prefixed `biz_`.""" balances: List[Balance] banner_image_url: Optional[str] = None - """The URL of the account banner image""" + """Account banner image URL.""" business_type: Optional[str] = None - """The high-level business category for the account""" + """High-level business category for the account.""" capabilities: Optional[Capabilities] = None """ - Each payment rail's status: active, inactive, or pending (pending means - onboarding or review is in progress) + Payment rails enabled for this account, each `active`, `inactive`, or `pending` + (onboarding or review in progress). Computed only on `retrieve` and `me` for + callers with `company:balance:read` scope; `null` otherwise. """ country: Optional[str] = None - """The country the account is located in""" + """Country where the account is located.""" created_at: str - """When the account was created, as an ISO 8601 timestamp""" + """When the account was created, as an ISO 8601 timestamp.""" description: Optional[str] = None - """A promotional description for the account""" + """Account promotional description.""" email: Optional[str] = None - """The email address of the account owner""" + """Account owner email address.""" home_preferences: List[str] industry_group: Optional[str] = None - """The industry group the account belongs to""" + """Account industry group.""" industry_type: Optional[str] = None - """The specific industry vertical the account operates in""" + """Specific industry vertical for the account.""" invoice_prefix: Optional[str] = None - """The prefix used for account invoices""" + """Prefix used for account invoices.""" logo_url: Optional[str] = None - """The URL of the account logo image""" + """Account logo image URL.""" metadata: object - """Arbitrary key/value metadata supplied when the account was created""" + """Arbitrary key/value metadata supplied at account creation.""" onboarding_type: Optional[str] = None - """The type of onboarding the account has completed""" + """Type of onboarding the account has completed.""" opengraph_image_url: Optional[str] = None - """The URL of the account Open Graph image""" + """Account Open Graph image URL.""" opengraph_image_variant: Optional[str] = None - """The account Open Graph image variant""" + """Account Open Graph image variant.""" other_business_description: Optional[str] = None - """The description of the business type when business_type is other""" + """Business type details when business_type is `other`.""" other_industry_description: Optional[str] = None - """The description of the industry type when industry_type is other""" + """Industry details when industry_type is `other`.""" parent_account_id: Optional[str] = None - """The parent account ID for connected accounts""" + """Parent account ID for connected accounts.""" recommended_actions: Optional[List[RecommendedAction]] = None require_2fa: bool - """ - Whether the account requires authorized users to have two-factor authentication - enabled - """ + """Whether authorized users must enable two-factor authentication.""" required_actions: Optional[List[RequiredAction]] = None route: str - """The account's public route identifier""" + """Account public route identifier.""" send_customer_emails: bool - """Whether Whop sends transactional emails to customers on behalf of this account""" + """Whether Whop sends transactional emails to customers on behalf of this account.""" show_joined_whops: bool - """Whether the account appears in joined whops on other accounts""" + """Whether the account appears in joined whops on other accounts.""" show_reviews_dtc: bool - """Whether reviews are displayed on direct-to-consumer product pages""" + """Whether reviews are displayed on direct-to-consumer product pages.""" show_user_directory: bool - """Whether the account shows users in the user directory""" + """Whether the account shows users in the user directory.""" social_links: List[AccountSocialLink] status: Optional[str] = None - """Whether the account can operate on Whop — active or suspended""" + """Whether the account can operate on Whop: `active` or `suspended`. + + Computed only on `retrieve` and `me`; `null` otherwise. + """ store_page_config: object - """Store page display configuration for the account""" + """Account store page display configuration.""" target_audience: Optional[str] = None - """The target audience for this account""" + """Target audience for this account.""" title: str - """The display name of the account""" + """Account display name.""" total_earned_usd: Optional[float] = None - """Lifetime sales for the account, normalized to USD""" + """Account lifetime sales, normalized to USD. + + Computed only on `retrieve` and `me` for callers with `stats:read` scope; `null` + otherwise. + """ total_usd: Optional[str] = None - """Total USD value across all balances with a known exchange rate. + """Total USD value across balances with known exchange rates. - Only computed on single-account reads (retrieve and me); null (with an empty - balances array) on list responses, on writes, when the caller's token lacks the - balance-read permission, and when the balance source is unavailable + Computed only on single-account reads (`retrieve` and `me`); `null` on list + responses, writes, missing balance-read permission, or unavailable balance + source. """ use_logo_as_opengraph_image_fallback: bool - """Whether the account uses its logo as the fallback Open Graph image""" + """Whether the account uses its logo as the fallback Open Graph image.""" verification: object - """The account's identity-verification status. - - `individual` is KYC, `business` is KYB; each is null when that profile has not - been created, otherwise { status } where status is one of not_started, pending, - approved, rejected + """ + Account identity verification status for the `individual` (KYC) and `business` + (KYB) profiles. Each is `null` until created, otherwise a `status` of + `not_started`, `pending`, `approved`, or `rejected`. """ wallet: Optional[Wallet] = None - """The account's primary crypto wallet, or null if none has been provisioned""" + """Account primary crypto wallet, or `null` if none has been provisioned.""" diff --git a/src/whop_sdk/types/account_update_params.py b/src/whop_sdk/types/account_update_params.py index f5014c18..b32d332e 100644 --- a/src/whop_sdk/types/account_update_params.py +++ b/src/whop_sdk/types/account_update_params.py @@ -24,28 +24,28 @@ class AccountUpdateParams(TypedDict, total=False): """Attachment input for the account banner image.""" business_type: Optional[str] - """The high-level business category for the account.""" + """High-level business category for the account.""" country: Optional[str] - """The country the account is located in.""" + """Country where the account is located.""" description: Optional[str] - """A promotional description for the account.""" + """Account promotional description.""" featured_affiliate_product_id: Optional[str] - """The ID of the product to feature for affiliates. Pass null to clear.""" + """The ID of the product to feature for affiliates. Pass `null` to clear.""" home_preferences: SequenceNotStr[str] - """Preferences for the public business home page.""" + """Public account home page preferences.""" industry_group: Optional[str] - """The industry group the account belongs to.""" + """Account industry group.""" industry_type: Optional[str] - """The specific industry vertical the account operates in.""" + """Specific industry vertical for the account.""" invoice_prefix: Optional[str] - """The prefix to use for account invoices.""" + """Prefix used for account invoices.""" logo: Optional[Dict[str, object]] """Attachment input for the account logo.""" @@ -93,7 +93,7 @@ class AccountUpdateParams(TypedDict, total=False): """The full list of social links to display for the account.""" store_page_config: Optional[Dict[str, object]] - """Store page display configuration for the account.""" + """Account store page display configuration.""" target_audience: Optional[str] """The target audience for this account.""" diff --git a/src/whop_sdk/types/card_create_response.py b/src/whop_sdk/types/card_create_response.py index fe5e37dd..0afd6612 100644 --- a/src/whop_sdk/types/card_create_response.py +++ b/src/whop_sdk/types/card_create_response.py @@ -13,16 +13,22 @@ class Billing(BaseModel): """The billing address.""" city: Optional[str] = None + """Billing city.""" country_code: Optional[str] = None + """Billing country code.""" line1: Optional[str] = None + """Street address line 1.""" line2: Optional[str] = None + """Street address line 2.""" postal_code: Optional[str] = None + """Billing postal code.""" region: Optional[str] = None + """Billing region or state.""" class Limit(BaseModel): @@ -32,7 +38,7 @@ class Limit(BaseModel): """The limit amount in dollars.""" frequency: str - """The limit window, for example per24HourPeriod or perAuthorization.""" + """Limit window, for example `per24HourPeriod` or `perAuthorization`.""" class Secrets(BaseModel): @@ -59,20 +65,25 @@ class CardCreateResponse(BaseModel): """The billing address.""" canceled_at: Optional[datetime] = None + """When the card was canceled.""" created_at: Optional[datetime] = None + """When the card was created.""" expiration_month: Optional[str] = None + """Card expiration month.""" expiration_year: Optional[str] = None + """Card expiration year.""" last4: Optional[str] = None - """The last 4 digits of the card number. Null for pending invitation cards.""" + """Last four digits of the card number. `null` for pending invitation cards.""" limit: Optional[Limit] = None """The spending limit configuration.""" name: Optional[str] = None + """Card display name.""" object: Literal["card"] diff --git a/src/whop_sdk/types/card_list_response.py b/src/whop_sdk/types/card_list_response.py index 7393af4e..d6c0530d 100644 --- a/src/whop_sdk/types/card_list_response.py +++ b/src/whop_sdk/types/card_list_response.py @@ -13,16 +13,22 @@ class DataBilling(BaseModel): """The billing address.""" city: Optional[str] = None + """Billing city.""" country_code: Optional[str] = None + """Billing country code.""" line1: Optional[str] = None + """Street address line 1.""" line2: Optional[str] = None + """Street address line 2.""" postal_code: Optional[str] = None + """Billing postal code.""" region: Optional[str] = None + """Billing region or state.""" class DataLimit(BaseModel): @@ -32,7 +38,7 @@ class DataLimit(BaseModel): """The limit amount in dollars.""" frequency: str - """The limit window, for example per24HourPeriod or perAuthorization.""" + """Limit window, for example `per24HourPeriod` or `perAuthorization`.""" class DataSecrets(BaseModel): @@ -59,20 +65,25 @@ class Data(BaseModel): """The billing address.""" canceled_at: Optional[datetime] = None + """When the card was canceled.""" created_at: Optional[datetime] = None + """When the card was created.""" expiration_month: Optional[str] = None + """Card expiration month.""" expiration_year: Optional[str] = None + """Card expiration year.""" last4: Optional[str] = None - """The last 4 digits of the card number. Null for pending invitation cards.""" + """Last four digits of the card number. `null` for pending invitation cards.""" limit: Optional[DataLimit] = None """The spending limit configuration.""" name: Optional[str] = None + """Card display name.""" object: Literal["card"] diff --git a/src/whop_sdk/types/card_retrieve_response.py b/src/whop_sdk/types/card_retrieve_response.py index 937e2aa0..8cf6786f 100644 --- a/src/whop_sdk/types/card_retrieve_response.py +++ b/src/whop_sdk/types/card_retrieve_response.py @@ -13,16 +13,22 @@ class Billing(BaseModel): """The billing address.""" city: Optional[str] = None + """Billing city.""" country_code: Optional[str] = None + """Billing country code.""" line1: Optional[str] = None + """Street address line 1.""" line2: Optional[str] = None + """Street address line 2.""" postal_code: Optional[str] = None + """Billing postal code.""" region: Optional[str] = None + """Billing region or state.""" class Limit(BaseModel): @@ -32,7 +38,7 @@ class Limit(BaseModel): """The limit amount in dollars.""" frequency: str - """The limit window, for example per24HourPeriod or perAuthorization.""" + """Limit window, for example `per24HourPeriod` or `perAuthorization`.""" class Secrets(BaseModel): @@ -59,20 +65,25 @@ class CardRetrieveResponse(BaseModel): """The billing address.""" canceled_at: Optional[datetime] = None + """When the card was canceled.""" created_at: Optional[datetime] = None + """When the card was created.""" expiration_month: Optional[str] = None + """Card expiration month.""" expiration_year: Optional[str] = None + """Card expiration year.""" last4: Optional[str] = None - """The last 4 digits of the card number. Null for pending invitation cards.""" + """Last four digits of the card number. `null` for pending invitation cards.""" limit: Optional[Limit] = None """The spending limit configuration.""" name: Optional[str] = None + """Card display name.""" object: Literal["card"] diff --git a/src/whop_sdk/types/company_list_response.py b/src/whop_sdk/types/company_list_response.py index 52d0916e..351ebd62 100644 --- a/src/whop_sdk/types/company_list_response.py +++ b/src/whop_sdk/types/company_list_response.py @@ -74,9 +74,7 @@ class CompanyListResponse(BaseModel): """ route: str - """ - The URL slug for the company's store page (e.g., 'pickaxe' in whop.com/pickaxe). - """ + """URL slug for the account's store page, e.g. `pickaxe` in whop.com/pickaxe.""" send_customer_emails: bool """ diff --git a/src/whop_sdk/types/course.py b/src/whop_sdk/types/course.py index 514fd985..38ea821d 100644 --- a/src/whop_sdk/types/course.py +++ b/src/whop_sdk/types/course.py @@ -111,7 +111,7 @@ class Thumbnail(BaseModel): """ content_type: Optional[str] = None - """The MIME type of the uploaded file (e.g., image/jpeg, video/mp4, audio/mpeg).""" + """Uploaded file MIME type, such as image/jpeg, video/mp4, or audio/mpeg.""" filename: Optional[str] = None """The original filename of the uploaded attachment, including its file extension.""" diff --git a/src/whop_sdk/types/course_list_response.py b/src/whop_sdk/types/course_list_response.py index 29c44d56..7178c72b 100644 --- a/src/whop_sdk/types/course_list_response.py +++ b/src/whop_sdk/types/course_list_response.py @@ -27,7 +27,7 @@ class Thumbnail(BaseModel): """ content_type: Optional[str] = None - """The MIME type of the uploaded file (e.g., image/jpeg, video/mp4, audio/mpeg).""" + """Uploaded file MIME type, such as image/jpeg, video/mp4, or audio/mpeg.""" filename: Optional[str] = None """The original filename of the uploaded attachment, including its file extension.""" diff --git a/src/whop_sdk/types/deposit_create_params.py b/src/whop_sdk/types/deposit_create_params.py index 164d1b72..2487a3a8 100644 --- a/src/whop_sdk/types/deposit_create_params.py +++ b/src/whop_sdk/types/deposit_create_params.py @@ -16,21 +16,24 @@ class DepositCreateParams(TypedDict, total=False): """ amount: float - """Optional amount to deposit.""" + """Amount to prefill on hosted deposit page.""" metadata: Dict[str, object] - """Arbitrary metadata echoed in the response.""" + """Metadata to include with the deposit response.""" network: Optional[str] - """Optional destination network override.""" + """Destination network override.""" class DestinationUnionMember1(TypedDict, total=False): account_id: str + """Destination account ID.""" address: str + """Destination wallet address.""" network: str + """Destination wallet network.""" Destination: TypeAlias = Union[str, DestinationUnionMember1] diff --git a/src/whop_sdk/types/deposit_create_response.py b/src/whop_sdk/types/deposit_create_response.py index d079f82f..65ccb5eb 100644 --- a/src/whop_sdk/types/deposit_create_response.py +++ b/src/whop_sdk/types/deposit_create_response.py @@ -10,19 +10,25 @@ class MethodsBankCurrency(BaseModel): account_number: Optional[str] = None + """Bank account number for deposits in this currency.""" currency: str + """Currency supported by these bank instructions.""" deposit_bank_name: Optional[str] = None + """Receiving bank name.""" deposit_beneficiary_name: Optional[str] = None + """Beneficiary name to use for transfer.""" deposit_reference: Optional[str] = None + """Reference to include with bank transfer.""" rails: List[str] - """Active deposit rails for this currency, e.g. ach, wire, sepa.""" + """Active deposit rails for this currency, such as `ach`, `wire`, or `sepa`.""" routing_number: Optional[str] = None + """Bank routing number for deposits in this currency.""" class MethodsBank(BaseModel): @@ -32,17 +38,25 @@ class MethodsBank(BaseModel): """ currencies: List[MethodsBankCurrency] + """Bank transfer currencies available for this deposit.""" class MethodsCrypto(BaseModel): + """Crypto wallet addresses available for this deposit.""" + evm: str + """EVM-compatible deposit address.""" solana: str + """Solana deposit address.""" wallet: str + """Primary wallet address for destination account.""" class Methods(BaseModel): + """Available deposit methods for destination.""" + bank: Optional[MethodsBank] = None """Bank deposit details. @@ -50,6 +64,7 @@ class Methods(BaseModel): """ crypto: MethodsCrypto + """Crypto wallet addresses available for this deposit.""" class DepositCreateResponse(BaseModel): @@ -60,9 +75,12 @@ class DepositCreateResponse(BaseModel): """URL of the hosted deposit page. Only present for business destinations.""" metadata: Dict[str, object] + """Metadata from the request.""" methods: Methods + """Available deposit methods for destination.""" object: Literal["deposit"] amount: Optional[str] = None + """Requested deposit amount.""" diff --git a/src/whop_sdk/types/deposit_list_response.py b/src/whop_sdk/types/deposit_list_response.py index d72522c2..a6a3cf90 100644 --- a/src/whop_sdk/types/deposit_list_response.py +++ b/src/whop_sdk/types/deposit_list_response.py @@ -11,23 +11,32 @@ class Bank(BaseModel): id: str + """Bank deposit transaction ID.""" created_at: datetime + """When the bank deposit transaction was created.""" destination_amount: Optional[str] = None + """Amount credited to the account balance.""" destination_currency: Optional[str] = None + """Currency credited to the account balance.""" source_amount: str + """Amount sent by the depositor.""" source_currency: str + """Currency sent by the depositor.""" status: str + """Current bank deposit status.""" class DepositListResponse(BaseModel): account_id: str + """Account ID that owns these deposit transactions.""" bank: List[Bank] + """Bank deposit transactions for this account.""" object: Literal["deposits"] diff --git a/src/whop_sdk/types/dispute.py b/src/whop_sdk/types/dispute.py index b093b0c2..c34af93f 100644 --- a/src/whop_sdk/types/dispute.py +++ b/src/whop_sdk/types/dispute.py @@ -44,7 +44,7 @@ class CancellationPolicyAttachment(BaseModel): """ content_type: Optional[str] = None - """The MIME type of the uploaded file (e.g., image/jpeg, video/mp4, audio/mpeg).""" + """Uploaded file MIME type, such as image/jpeg, video/mp4, or audio/mpeg.""" filename: Optional[str] = None """The original filename of the uploaded attachment, including its file extension.""" @@ -82,7 +82,7 @@ class CustomerCommunicationAttachment(BaseModel): """ content_type: Optional[str] = None - """The MIME type of the uploaded file (e.g., image/jpeg, video/mp4, audio/mpeg).""" + """Uploaded file MIME type, such as image/jpeg, video/mp4, or audio/mpeg.""" filename: Optional[str] = None """The original filename of the uploaded attachment, including its file extension.""" @@ -231,7 +231,7 @@ class RefundPolicyAttachment(BaseModel): """ content_type: Optional[str] = None - """The MIME type of the uploaded file (e.g., image/jpeg, video/mp4, audio/mpeg).""" + """Uploaded file MIME type, such as image/jpeg, video/mp4, or audio/mpeg.""" filename: Optional[str] = None """The original filename of the uploaded attachment, including its file extension.""" @@ -260,7 +260,7 @@ class UncategorizedAttachment(BaseModel): """ content_type: Optional[str] = None - """The MIME type of the uploaded file (e.g., image/jpeg, video/mp4, audio/mpeg).""" + """Uploaded file MIME type, such as image/jpeg, video/mp4, or audio/mpeg.""" filename: Optional[str] = None """The original filename of the uploaded attachment, including its file extension.""" diff --git a/src/whop_sdk/types/experience_list_response.py b/src/whop_sdk/types/experience_list_response.py index 3380a752..9b0cf573 100644 --- a/src/whop_sdk/types/experience_list_response.py +++ b/src/whop_sdk/types/experience_list_response.py @@ -47,9 +47,7 @@ class Company(BaseModel): """The unique identifier for the company.""" route: str - """ - The URL slug for the company's store page (e.g., 'pickaxe' in whop.com/pickaxe). - """ + """URL slug for the account's store page, e.g. `pickaxe` in whop.com/pickaxe.""" title: str """The display name of the company shown to customers.""" diff --git a/src/whop_sdk/types/financial_activity_list_params.py b/src/whop_sdk/types/financial_activity_list_params.py index 623ab738..33123eae 100644 --- a/src/whop_sdk/types/financial_activity_list_params.py +++ b/src/whop_sdk/types/financial_activity_list_params.py @@ -18,19 +18,19 @@ class FinancialActivityListParams(TypedDict, total=False): available_after: Annotated[Union[str, date], PropertyInfo(format="iso8601")] """ - Only include rows whose funds became withdrawable on or after this YYYY-MM-DD + Only include rows whose funds became withdrawable on or after this `YYYY-MM-DD` settlement date (UTC), distinct from posted_at. Requires currency. """ available_before: Annotated[Union[str, date], PropertyInfo(format="iso8601")] """ - Only include rows whose funds became withdrawable on or before this YYYY-MM-DD + Only include rows whose funds became withdrawable on or before this `YYYY-MM-DD` settlement date (UTC). Set equal to available_after for a single day. Requires currency. """ currency: str - """Optional currency code filter, for example usd.""" + """Optional currency code filter, for example `usd`.""" cursor: str """Cursor returned by the previous page.""" @@ -41,7 +41,7 @@ class FinancialActivityListParams(TypedDict, total=False): line_types: SequenceNotStr[str] """Optional ledger line categories to include. - Some categories (for example onchain_deposit, which covers inbound crypto + Some categories (for example `onchain_deposit`, which covers inbound crypto deposits such as MoonPay onramps) are only returned when explicitly requested here. """ diff --git a/src/whop_sdk/types/financial_activity_list_response.py b/src/whop_sdk/types/financial_activity_list_response.py index 7b21b302..a2c3248e 100644 --- a/src/whop_sdk/types/financial_activity_list_response.py +++ b/src/whop_sdk/types/financial_activity_list_response.py @@ -32,58 +32,77 @@ class DataCurrency(BaseModel): + """Currency for this ledger activity.""" + code: str + """Currency code.""" precision: str - """Precision factor for the currency, for example 100000000 for USD.""" + """Precision factor for the currency, for example `100000000` for USD.""" class DataResourceUnionMember0(BaseModel): id: str + """Account ID.""" logo_url: Optional[str] = None + """Account logo URL.""" object: Literal["account"] route: Optional[str] = None + """Account route.""" title: Optional[str] = None + """Account display name.""" class DataResourceUnionMember1(BaseModel): id: str + """User ID.""" name: Optional[str] = None + """User display name.""" object: Literal["user"] profile_picture_url: Optional[str] = None + """User profile image URL.""" username: Optional[str] = None + """User's username.""" class DataResourceUnionMember2OwnerUnionMember0(BaseModel): id: str + """Account ID.""" logo_url: Optional[str] = None + """Account logo URL.""" object: Literal["account"] route: Optional[str] = None + """Account route.""" title: Optional[str] = None + """Account display name.""" class DataResourceUnionMember2OwnerUnionMember1(BaseModel): id: str + """User ID.""" name: Optional[str] = None + """User display name.""" object: Literal["user"] profile_picture_url: Optional[str] = None + """User profile image URL.""" username: Optional[str] = None + """User's username.""" DataResourceUnionMember2Owner: TypeAlias = Union[ @@ -93,6 +112,7 @@ class DataResourceUnionMember2OwnerUnionMember1(BaseModel): class DataResourceUnionMember2(BaseModel): id: str + """Ledger account ID.""" object: Literal["ledger_account"] @@ -101,58 +121,77 @@ class DataResourceUnionMember2(BaseModel): class DataResourceUnionMember3Bank(BaseModel): account_name: Optional[str] = None + """Bank account holder name.""" account_type: Optional[str] = None + """Bank account type.""" bank_name: Optional[str] = None + """Bank name.""" last4: Optional[str] = None + """Last four digits of the bank account.""" class DataResourceUnionMember3Card(BaseModel): brand: Optional[str] = None + """Card brand.""" exp_month: Optional[int] = None + """Card expiration month.""" exp_year: Optional[int] = None + """Card expiration year.""" last4: Optional[str] = None + """Last four digits of the card.""" class DataResourceUnionMember3(BaseModel): id: str + """Payment method ID.""" bank: Optional[DataResourceUnionMember3Bank] = None card: Optional[DataResourceUnionMember3Card] = None email_identifier: Optional[str] = None + """Email identifier for email-based payment methods.""" gateway_type: Optional[str] = None + """Payment gateway type.""" object: Literal["payment_method"] payment_method_type: Optional[str] = None + """Payment method type.""" class DataResourceUnionMember4(BaseModel): id: str + """Payout method ID.""" account_reference: Optional[str] = None + """Masked account reference.""" destination_currency_code: Optional[str] = None + """Destination currency code.""" institution_name: Optional[str] = None + """Payout institution name.""" nickname: Optional[str] = None + """Payout method nickname.""" object: Literal["payout_method"] provider: Optional[str] = None + """Payout provider.""" class DataResourceUnionMember5(BaseModel): id: str + """Card transaction ID.""" authorized_at: Optional[datetime] = None """ISO 8601 timestamp the transaction was authorized.""" @@ -180,10 +219,13 @@ class DataResourceUnionMember5(BaseModel): """ISO 4217 currency code of the merchant-charged amount in local_amount.""" merchant_category: Optional[str] = None + """Merchant category.""" merchant_icon_url: Optional[str] = None + """Merchant icon URL.""" merchant_name: Optional[str] = None + """Merchant display name.""" object: Literal["card_transaction"] @@ -191,6 +233,7 @@ class DataResourceUnionMember5(BaseModel): """ISO 8601 timestamp the transaction was settled by the card network.""" status: Optional[str] = None + """Current card transaction status.""" usd_amount: Optional[str] = None """The processor-settled USD amount as a decimal string. @@ -219,6 +262,8 @@ class DataSourcePayoutDestination(BaseModel): class DataSource(BaseModel): + """Source of this ledger activity.""" + id: str object: str @@ -307,6 +352,7 @@ def __getattr__(self, attr: str) -> builtins.object: ... class Data(BaseModel): id: str + """Ledger activity ID.""" amount: str """Signed amount in the currency's smallest precision units.""" @@ -322,18 +368,24 @@ class Data(BaseModel): """ created_at: Optional[datetime] = None + """When the activity record was created.""" currency: DataCurrency + """Currency for this ledger activity.""" line_type: str + """Type of ledger activity.""" object: Literal["ledger_activity"] posted_at: datetime + """When the activity posted to the ledger.""" resource: Optional[DataResource] = None + """Resource associated with this ledger activity.""" source: Optional[DataSource] = None + """Source of this ledger activity.""" class PageInfo(BaseModel): diff --git a/src/whop_sdk/types/forum_post_list_response.py b/src/whop_sdk/types/forum_post_list_response.py index 4cbd7083..6c3fd981 100644 --- a/src/whop_sdk/types/forum_post_list_response.py +++ b/src/whop_sdk/types/forum_post_list_response.py @@ -22,7 +22,7 @@ class Attachment(BaseModel): """ content_type: Optional[str] = None - """The MIME type of the uploaded file (e.g., image/jpeg, video/mp4, audio/mpeg).""" + """Uploaded file MIME type, such as image/jpeg, video/mp4, or audio/mpeg.""" filename: Optional[str] = None """The original filename of the uploaded attachment, including its file extension.""" diff --git a/src/whop_sdk/types/identity_profile_approved_webhook_event.py b/src/whop_sdk/types/identity_profile_approved_webhook_event.py index 7a737ae0..f03229cc 100644 --- a/src/whop_sdk/types/identity_profile_approved_webhook_event.py +++ b/src/whop_sdk/types/identity_profile_approved_webhook_event.py @@ -135,11 +135,10 @@ class Data(BaseModel): """ country: Optional[str] = None - """ISO 3166-1 alpha-2 country code (e.g. - - `US`, `GB`). For individuals this is the country of citizenship or residence - reported by the identity provider; for businesses this is the country of - incorporation. + """ + ISO 3166-1 alpha-2 country code reported by the identity provider, such as `US` + or `GB`. For individuals this is the country of citizenship or residence; for + businesses, the country of incorporation. """ created_at: datetime diff --git a/src/whop_sdk/types/identity_profile_needs_action_webhook_event.py b/src/whop_sdk/types/identity_profile_needs_action_webhook_event.py index 7f67727b..47bdd4ac 100644 --- a/src/whop_sdk/types/identity_profile_needs_action_webhook_event.py +++ b/src/whop_sdk/types/identity_profile_needs_action_webhook_event.py @@ -135,11 +135,10 @@ class Data(BaseModel): """ country: Optional[str] = None - """ISO 3166-1 alpha-2 country code (e.g. - - `US`, `GB`). For individuals this is the country of citizenship or residence - reported by the identity provider; for businesses this is the country of - incorporation. + """ + ISO 3166-1 alpha-2 country code reported by the identity provider, such as `US` + or `GB`. For individuals this is the country of citizenship or residence; for + businesses, the country of incorporation. """ created_at: datetime diff --git a/src/whop_sdk/types/identity_profile_rejected_webhook_event.py b/src/whop_sdk/types/identity_profile_rejected_webhook_event.py index 9de64813..473ef563 100644 --- a/src/whop_sdk/types/identity_profile_rejected_webhook_event.py +++ b/src/whop_sdk/types/identity_profile_rejected_webhook_event.py @@ -135,11 +135,10 @@ class Data(BaseModel): """ country: Optional[str] = None - """ISO 3166-1 alpha-2 country code (e.g. - - `US`, `GB`). For individuals this is the country of citizenship or residence - reported by the identity provider; for businesses this is the country of - incorporation. + """ + ISO 3166-1 alpha-2 country code reported by the identity provider, such as `US` + or `GB`. For individuals this is the country of citizenship or residence; for + businesses, the country of incorporation. """ created_at: datetime diff --git a/src/whop_sdk/types/identity_profile_updated_webhook_event.py b/src/whop_sdk/types/identity_profile_updated_webhook_event.py index cea15a83..03cc5454 100644 --- a/src/whop_sdk/types/identity_profile_updated_webhook_event.py +++ b/src/whop_sdk/types/identity_profile_updated_webhook_event.py @@ -135,11 +135,10 @@ class Data(BaseModel): """ country: Optional[str] = None - """ISO 3166-1 alpha-2 country code (e.g. - - `US`, `GB`). For individuals this is the country of citizenship or residence - reported by the identity provider; for businesses this is the country of - incorporation. + """ + ISO 3166-1 alpha-2 country code reported by the identity provider, such as `US` + or `GB`. For individuals this is the country of citizenship or residence; for + businesses, the country of incorporation. """ created_at: datetime diff --git a/src/whop_sdk/types/ledger_account_funds_available_webhook_event.py b/src/whop_sdk/types/ledger_account_funds_available_webhook_event.py index 4deabdc1..7ceef586 100644 --- a/src/whop_sdk/types/ledger_account_funds_available_webhook_event.py +++ b/src/whop_sdk/types/ledger_account_funds_available_webhook_event.py @@ -71,9 +71,7 @@ class DataOwnerCompany(BaseModel): """The unique identifier for the company.""" route: str - """ - The URL slug for the company's store page (e.g., 'pickaxe' in whop.com/pickaxe). - """ + """URL slug for the account's store page, e.g. `pickaxe` in whop.com/pickaxe.""" title: str """The display name of the company shown to customers.""" diff --git a/src/whop_sdk/types/ledger_account_retrieve_response.py b/src/whop_sdk/types/ledger_account_retrieve_response.py index fcaeeed0..86b90d83 100644 --- a/src/whop_sdk/types/ledger_account_retrieve_response.py +++ b/src/whop_sdk/types/ledger_account_retrieve_response.py @@ -69,9 +69,7 @@ class OwnerCompany(BaseModel): """The unique identifier for the company.""" route: str - """ - The URL slug for the company's store page (e.g., 'pickaxe' in whop.com/pickaxe). - """ + """URL slug for the account's store page, e.g. `pickaxe` in whop.com/pickaxe.""" title: str """The display name of the company shown to customers.""" diff --git a/src/whop_sdk/types/lesson.py b/src/whop_sdk/types/lesson.py index a962bd3e..121e8205 100644 --- a/src/whop_sdk/types/lesson.py +++ b/src/whop_sdk/types/lesson.py @@ -36,7 +36,7 @@ class AssessmentQuestionImage(BaseModel): """ content_type: Optional[str] = None - """The MIME type of the uploaded file (e.g., image/jpeg, video/mp4, audio/mpeg).""" + """Uploaded file MIME type, such as image/jpeg, video/mp4, or audio/mpeg.""" filename: Optional[str] = None """The original filename of the uploaded attachment, including its file extension.""" @@ -113,7 +113,7 @@ class Attachment(BaseModel): """ content_type: Optional[str] = None - """The MIME type of the uploaded file (e.g., image/jpeg, video/mp4, audio/mpeg).""" + """Uploaded file MIME type, such as image/jpeg, video/mp4, or audio/mpeg.""" filename: Optional[str] = None """The original filename of the uploaded attachment, including its file extension.""" @@ -142,7 +142,7 @@ class MainPdf(BaseModel): """ content_type: Optional[str] = None - """The MIME type of the uploaded file (e.g., image/jpeg, video/mp4, audio/mpeg).""" + """Uploaded file MIME type, such as image/jpeg, video/mp4, or audio/mpeg.""" filename: Optional[str] = None """The original filename of the uploaded attachment, including its file extension.""" diff --git a/src/whop_sdk/types/membership_list_response.py b/src/whop_sdk/types/membership_list_response.py index 2910aad0..77034358 100644 --- a/src/whop_sdk/types/membership_list_response.py +++ b/src/whop_sdk/types/membership_list_response.py @@ -52,10 +52,10 @@ class Product(BaseModel): """The unique identifier for the product.""" metadata: Optional[Dict[str, object]] = None - """Custom key-value pairs stored on the product. - - Included in webhook payloads for payment and membership events. Max 50 keys, 100 - chars per key, 500 chars per string value. + """ + Custom key-value pairs stored on the product and included in payment and + membership webhook payloads. Max 50 keys, 100 characters per key, 500 characters + per string value. """ title: str diff --git a/src/whop_sdk/types/payment_list_response.py b/src/whop_sdk/types/payment_list_response.py index 25f09d10..edddf651 100644 --- a/src/whop_sdk/types/payment_list_response.py +++ b/src/whop_sdk/types/payment_list_response.py @@ -179,16 +179,16 @@ class Product(BaseModel): """The unique identifier for the product.""" metadata: Optional[Dict[str, object]] = None - """Custom key-value pairs stored on the product. - - Included in webhook payloads for payment and membership events. Max 50 keys, 100 - chars per key, 500 chars per string value. + """ + Custom key-value pairs stored on the product and included in payment and + membership webhook payloads. Max 50 keys, 100 characters per key, 500 characters + per string value. """ route: str - """ - The URL slug used in the product's public link (e.g., 'my-product' in - whop.com/company/my-product). + """URL slug in the product's public link, e.g. + + `pickaxe-analytics` in whop.com/company/pickaxe-analytics. """ title: str diff --git a/src/whop_sdk/types/payout_list_params.py b/src/whop_sdk/types/payout_list_params.py index 72274108..e3b0e4e7 100644 --- a/src/whop_sdk/types/payout_list_params.py +++ b/src/whop_sdk/types/payout_list_params.py @@ -18,7 +18,7 @@ class PayoutListParams(TypedDict, total=False): """Cursor to fetch the page before (from page_info.start_cursor).""" currency: str - """Optional currency code filter, for example usd.""" + """Optional currency code filter, for example `usd`.""" first: int """Number of payouts to return from the start of the window.""" diff --git a/src/whop_sdk/types/payout_list_response.py b/src/whop_sdk/types/payout_list_response.py index 2e662c5b..3d25fb3a 100644 --- a/src/whop_sdk/types/payout_list_response.py +++ b/src/whop_sdk/types/payout_list_response.py @@ -10,9 +10,13 @@ class PayoutTokenPayoutDestination(BaseModel): + """Payout destination display details.""" + icon_url: Optional[str] = None + """Payout destination icon URL.""" payer_name: Optional[str] = None + """Payout destination display name.""" class PayoutToken(BaseModel): @@ -22,19 +26,24 @@ class PayoutToken(BaseModel): """ nickname: Optional[str] = None + """Saved payout method nickname.""" payout_destination: Optional[PayoutTokenPayoutDestination] = None + """Payout destination display details.""" class PayoutListResponse(BaseModel): id: str + """Payout ID.""" amount: float """The payout amount in whole currency units.""" created_at: datetime + """When the payout was created.""" currency: str + """Payout currency.""" estimated_arrival: Optional[datetime] = None """Estimated time the funds become available in the destination account.""" @@ -54,5 +63,7 @@ class PayoutListResponse(BaseModel): """ speed: Literal["standard", "instant"] + """Payout delivery speed.""" status: Literal["requested", "awaiting_payment", "in_transit", "completed", "failed", "canceled", "denied"] + """Current payout status.""" diff --git a/src/whop_sdk/types/plan_calculate_tax_params.py b/src/whop_sdk/types/plan_calculate_tax_params.py index d1b31405..1e9f5eb5 100644 --- a/src/whop_sdk/types/plan_calculate_tax_params.py +++ b/src/whop_sdk/types/plan_calculate_tax_params.py @@ -29,7 +29,7 @@ class Address(TypedDict, total=False): """The buyer's billing address. Provide this or ip_address.""" country: Required[str] - """The two-letter ISO 3166-1 country code, for example US, DE, or GB.""" + """The two-letter ISO 3166-1 country code, for example `US`, `DE`, or `GB`.""" city: Optional[str] """The city name.""" @@ -44,12 +44,12 @@ class Address(TypedDict, total=False): """The postal or ZIP code.""" state: Optional[str] - """The state, province, or region code, for example CA.""" + """The state, province, or region code, for example `CA`.""" class TaxID(TypedDict, total=False): type: str - """The tax ID type, for example eu_vat.""" + """Tax ID type, for example `eu_vat`.""" value: str - """The tax ID number, for example DE123456789.""" + """Tax ID number, for example `DE123456789`.""" diff --git a/src/whop_sdk/types/plan_create_params.py b/src/whop_sdk/types/plan_create_params.py index 6e742c65..86d54694 100644 --- a/src/whop_sdk/types/plan_create_params.py +++ b/src/whop_sdk/types/plan_create_params.py @@ -21,10 +21,7 @@ class PlanCreateParams(TypedDict, total=False): """Whether this plan accepts local currency payments via adaptive pricing.""" billing_period: Optional[int] - """The number of days between recurring charges. - - For example, 30 for monthly or 365 for yearly. - """ + """Recurring billing interval in days, such as 30 for monthly or 365 for annual.""" checkout_styling: Optional[object] """Checkout styling overrides for this plan.""" @@ -42,19 +39,16 @@ class PlanCreateParams(TypedDict, total=False): """A text description of the plan displayed to customers on the product page.""" expiration_days: Optional[int] - """The number of days until the membership expires and access is revoked.""" + """Access duration in days before the membership expires.""" image: Optional[Image] """An image displayed on the product page to represent this plan.""" initial_price: Optional[float] - """ - The amount charged on the first purchase, in the plan's currency (e.g., 10.43 - for $10.43). - """ + """Initial amount charged in the plan's currency, e.g. 10.43 for $10.43.""" internal_notes: Optional[str] - """Private notes visible only to the business owner. Not shown to customers.""" + """Private notes visible only to the account owner. Not shown to customers.""" legacy_payment_method_controls: Optional[bool] """Whether this plan uses legacy payment method controls.""" @@ -76,13 +70,13 @@ class PlanCreateParams(TypedDict, total=False): """ plan_type: str - """The billing type of the plan, such as one_time or renewal.""" + """Plan billing type, such as `one_time` or `renewal`.""" product_id: str """The unique identifier of the product to attach this plan to.""" release_method: str - """The method used to sell this plan (e.g., buy_now, waitlist).""" + """Sales method for this plan, such as `buy_now` or `waitlist`.""" renewal_price: Optional[float] """ @@ -91,7 +85,7 @@ class PlanCreateParams(TypedDict, total=False): """ split_pay_required_payments: Optional[int] - """The number of installment payments required before the subscription pauses.""" + """Installment payments required before the subscription pauses.""" stock: Optional[int] """The maximum number of units available for purchase. @@ -100,13 +94,13 @@ class PlanCreateParams(TypedDict, total=False): """ three_ds_level: Literal["mandate_challenge", "frictionless"] - """The 3D Secure behavior for this plan. Send null to inherit the account default.""" + """3D Secure behavior for this plan. Send `null` to inherit the account default.""" title: Optional[str] """The display name of the plan shown to customers on the product page.""" trial_period_days: Optional[int] - """The number of free trial days before the first charge on a recurring plan.""" + """Free trial duration before the first recurring charge.""" unlimited_stock: Optional[bool] """Whether the plan has unlimited stock. When true, the stock field is ignored.""" diff --git a/src/whop_sdk/types/plan_list_response.py b/src/whop_sdk/types/plan_list_response.py index ac7af37a..28c91fa1 100644 --- a/src/whop_sdk/types/plan_list_response.py +++ b/src/whop_sdk/types/plan_list_response.py @@ -9,106 +9,95 @@ class PlanListResponse(BaseModel): id: str - """The ID of the plan, which will look like plan\\__******\\********""" + """Plan ID, prefixed `plan_`.""" account: Optional[object] = None - """The account that sells this plan, an object with an id and title. - - Null for standalone invoice plans - """ + """Account that sells this plan; `null` for standalone invoice plans.""" adaptive_pricing_enabled: bool - """Whether this plan accepts local currency payments via adaptive pricing""" + """Whether this plan accepts local currency payments via adaptive pricing.""" billing_period: Optional[float] = None - """The number of days between recurring charges. Null for one-time plans""" + """Recurring billing interval in days, such as 30 for monthly or 365 for annual. + + `null` for one-time plans. + """ created_at: str - """When the plan was created, as an ISO 8601 timestamp""" + """When the plan was created, as an ISO 8601 timestamp.""" currency: str - """The three-letter ISO currency code all prices on this plan are denominated in""" + """Three-letter ISO currency code for this plan's prices.""" description: Optional[str] = None - """A text description of the plan visible to customers""" + """Customer-visible plan description.""" expiration_days: Optional[float] = None - """The number of days until the membership expires, for expiration-based plans""" + """Access duration in days for expiration-based plans.""" initial_price: float - """The initial purchase price in the plan's currency""" + """Initial purchase price in plan currency.""" internal_notes: Optional[str] = None - """Private notes visible only to authorized team members""" + """Private notes visible only to authorized team members.""" invoice: Optional[object] = None - """The invoice this plan was generated for, an object with an id. - - Null unless the plan was created for an invoice - """ + """Invoice this plan was generated for; `null` unless created for an invoice.""" member_count: Optional[float] = None - """The number of active memberships on this plan. - - Only visible to authorized team members - """ + """Active memberships through this plan, when visible to the requester.""" metadata: Optional[object] = None - """Custom key-value pairs stored on the plan""" + """Custom key-value pairs stored on the plan.""" payment_method_configuration: Optional[object] = None """ - The explicit payment method configuration for the plan, an object with enabled, - disabled and include_platform_defaults. Null if the plan uses default settings + Payment method configuration (`enabled`, `disabled`, + `include_platform_defaults`); `null` when plan uses default settings. """ plan_type: str """ - The billing model for this plan: 'renewal' for recurring subscriptions or - 'one_time' for single payments + Billing model for this plan: `renewal` (recurring) or `one_time` (single + payment). """ product: Optional[object] = None - """The product this plan belongs to, an object with an id and title. - - Null for standalone plans - """ + """Product this plan belongs to; `null` for standalone plans.""" purchase_url: str - """The full URL where customers can purchase this plan directly""" + """URL where customers can purchase this plan directly.""" release_method: str - """The method used to sell this plan, e.g. 'buy_now' or 'waitlist'""" + """Sales method for this plan, such as `buy_now` or `waitlist`.""" renewal_price: float - """The recurring price charged every billing period in the plan's currency""" + """Recurring price charged every billing period.""" split_pay_required_payments: Optional[float] = None - """The number of installment payments required before the subscription pauses""" + """Installment payments required before the subscription pauses.""" stock: Optional[float] = None - """The number of units available for purchase. - - Only visible to authorized team members - """ + """Units available for purchase, when visible to the requester.""" three_ds_level: Optional[str] = None - """The 3D Secure behavior for this plan. - - Null means the plan inherits the account default - """ + """3D Secure behavior for this plan; `null` inherits account default.""" title: Optional[str] = None - """The display name of the plan shown to customers""" + """Plan display name shown to customers.""" trial_period_days: Optional[float] = None - """The number of free trial days before the first charge on a recurring plan""" + """Free trial days before the first renewal charge. + + `null` if no trial is configured or the user has already used a trial for this + plan. + """ unlimited_stock: bool - """Whether the plan has unlimited stock""" + """Whether the plan has unlimited stock.""" updated_at: str - """When the plan was last updated, as an ISO 8601 timestamp""" + """When the plan was last updated, as an ISO 8601 timestamp.""" visibility: str - """Whether the plan is visible to customers or hidden from public view""" + """Whether the plan is visible to customers or hidden from public view.""" diff --git a/src/whop_sdk/types/plan_update_params.py b/src/whop_sdk/types/plan_update_params.py index 335ffdf3..bf5882af 100644 --- a/src/whop_sdk/types/plan_update_params.py +++ b/src/whop_sdk/types/plan_update_params.py @@ -15,10 +15,7 @@ class PlanUpdateParams(TypedDict, total=False): """Whether this plan accepts local currency payments via adaptive pricing.""" billing_period: Optional[int] - """The number of days between recurring charges. - - For example, 30 for monthly or 365 for yearly. - """ + """Recurring billing interval in days, such as 30 for monthly or 365 for annual.""" checkout_styling: Optional[object] """Checkout styling overrides for this plan.""" @@ -36,19 +33,16 @@ class PlanUpdateParams(TypedDict, total=False): """A text description of the plan displayed to customers on the product page.""" expiration_days: Optional[int] - """The number of days until the membership expires and access is revoked.""" + """Access duration in days before the membership expires.""" image: Optional[Image] """An image displayed on the product page to represent this plan.""" initial_price: Optional[float] - """ - The amount charged on the first purchase, in the plan's currency (e.g., 10.43 - for $10.43). - """ + """Initial amount charged in the plan's currency, e.g. 10.43 for $10.43.""" internal_notes: Optional[str] - """Private notes visible only to the business owner. Not shown to customers.""" + """Private notes visible only to the account owner. Not shown to customers.""" legacy_payment_method_controls: Optional[bool] """Whether this plan uses legacy payment method controls.""" @@ -91,13 +85,13 @@ class PlanUpdateParams(TypedDict, total=False): """A comparison price displayed with a strikethrough for the renewal price.""" three_ds_level: Literal["mandate_challenge", "frictionless"] - """The 3D Secure behavior for this plan. Send null to inherit the account default.""" + """3D Secure behavior for this plan. Send `null` to inherit the account default.""" title: Optional[str] """The display name of the plan shown to customers on the product page.""" trial_period_days: Optional[int] - """The number of free trial days before the first charge on a recurring plan.""" + """Free trial duration before the first recurring charge.""" unlimited_stock: Optional[bool] """Whether the plan has unlimited stock. When true, the stock field is ignored.""" diff --git a/src/whop_sdk/types/referrals/business_list_earnings_params.py b/src/whop_sdk/types/referrals/business_list_earnings_params.py index 5fc91692..b762aaef 100644 --- a/src/whop_sdk/types/referrals/business_list_earnings_params.py +++ b/src/whop_sdk/types/referrals/business_list_earnings_params.py @@ -25,7 +25,7 @@ class BusinessListEarningsParams(TypedDict, total=False): order: Literal["asc", "desc"] """Sort direction.""" - sort: Literal["created_at", "amount", "payout_at"] + sort: Literal["created_at", "commission_amount", "transaction_amount", "payout_at"] """Field to sort earnings by.""" status: Literal["awaiting_settlement", "pending", "completed", "canceled", "reversed"] diff --git a/src/whop_sdk/types/referrals/business_list_earnings_response.py b/src/whop_sdk/types/referrals/business_list_earnings_response.py index 92c38d02..4c414287 100644 --- a/src/whop_sdk/types/referrals/business_list_earnings_response.py +++ b/src/whop_sdk/types/referrals/business_list_earnings_response.py @@ -25,14 +25,19 @@ class AccessPass(BaseModel): class Account(BaseModel): + """Referred account.""" + id: str - """The referred business (a biz\\__ identifier).""" + """Referred account ID.""" logo_url: Optional[str] = None + """Referred account logo URL.""" route: str + """Referred account route.""" title: str + """Referred account display name.""" class ReceiptAlternativePaymentMethod(BaseModel): @@ -87,6 +92,7 @@ class BusinessListEarningsResponse(BaseModel): access_pass: Optional[AccessPass] = None account: Optional[Account] = None + """Referred account.""" cancelation_reason: Optional[str] = None """Why the earning was canceled or reversed, if applicable.""" diff --git a/src/whop_sdk/types/referrals/business_list_params.py b/src/whop_sdk/types/referrals/business_list_params.py index 574004a1..07b65a33 100644 --- a/src/whop_sdk/types/referrals/business_list_params.py +++ b/src/whop_sdk/types/referrals/business_list_params.py @@ -26,5 +26,11 @@ class BusinessListParams(TypedDict, total=False): last: int """Number of business referrals to return from the end of the window.""" + order: Literal["asc", "desc"] + """Sort direction.""" + + sort: Literal["created_at", "referral_started_at", "referral_expires_at", "payout_percentage"] + """Field to sort business referrals by.""" + status: Literal["active", "removed"] """Filter by referral status.""" diff --git a/src/whop_sdk/types/referrals/business_list_response.py b/src/whop_sdk/types/referrals/business_list_response.py index 9d0fe682..a71d85ef 100644 --- a/src/whop_sdk/types/referrals/business_list_response.py +++ b/src/whop_sdk/types/referrals/business_list_response.py @@ -10,14 +10,19 @@ class Account(BaseModel): + """Referred account.""" + id: str - """The referred business (a biz\\__ identifier).""" + """Referred account ID.""" logo_url: Optional[str] = None + """Referred account logo URL.""" route: str + """Referred account route.""" title: str + """Referred account display name.""" class EarningsUsd(BaseModel): @@ -47,22 +52,28 @@ class VolumeUsd(BaseModel): class BusinessListResponse(BaseModel): id: str + """Business referral ID.""" account: Optional[Account] = None + """Referred account.""" created_at: datetime + """When the business referral was created.""" earnings_usd: EarningsUsd object: Literal["business_referral"] payout_percentage: float - """The referrer's share of Whop's gross profit, as a fraction (0.3 = 30%).""" + """Referrer's share of Whop gross profit, as a fraction (0.3 = 30%).""" referral_expires_at: Optional[datetime] = None + """When the referral expires.""" referral_started_at: Optional[datetime] = None + """When the referral became active.""" status: Literal["active", "removed"] + """Current referral status.""" volume_usd: VolumeUsd diff --git a/src/whop_sdk/types/referrals/business_retrieve_response.py b/src/whop_sdk/types/referrals/business_retrieve_response.py index 2b0846a8..33f98228 100644 --- a/src/whop_sdk/types/referrals/business_retrieve_response.py +++ b/src/whop_sdk/types/referrals/business_retrieve_response.py @@ -10,14 +10,19 @@ class Account(BaseModel): + """Referred account.""" + id: str - """The referred business (a biz\\__ identifier).""" + """Referred account ID.""" logo_url: Optional[str] = None + """Referred account logo URL.""" route: str + """Referred account route.""" title: str + """Referred account display name.""" class EarningsUsd(BaseModel): @@ -47,22 +52,28 @@ class VolumeUsd(BaseModel): class BusinessRetrieveResponse(BaseModel): id: str + """Business referral ID.""" account: Optional[Account] = None + """Referred account.""" created_at: datetime + """When the business referral was created.""" earnings_usd: EarningsUsd object: Literal["business_referral"] payout_percentage: float - """The referrer's share of Whop's gross profit, as a fraction (0.3 = 30%).""" + """Referrer's share of Whop gross profit, as a fraction (0.3 = 30%).""" referral_expires_at: Optional[datetime] = None + """When the referral expires.""" referral_started_at: Optional[datetime] = None + """When the referral became active.""" status: Literal["active", "removed"] + """Current referral status.""" volume_usd: VolumeUsd diff --git a/src/whop_sdk/types/referrals/businesses/earning_list_params.py b/src/whop_sdk/types/referrals/businesses/earning_list_params.py index 161ef59f..89fae383 100644 --- a/src/whop_sdk/types/referrals/businesses/earning_list_params.py +++ b/src/whop_sdk/types/referrals/businesses/earning_list_params.py @@ -25,7 +25,7 @@ class EarningListParams(TypedDict, total=False): order: Literal["asc", "desc"] """Sort direction.""" - sort: Literal["created_at", "amount", "payout_at"] + sort: Literal["created_at", "commission_amount", "transaction_amount", "payout_at"] """Field to sort earnings by.""" status: Literal["awaiting_settlement", "pending", "completed", "canceled", "reversed"] diff --git a/src/whop_sdk/types/referrals/businesses/earning_list_response.py b/src/whop_sdk/types/referrals/businesses/earning_list_response.py index e82df872..8f551bdd 100644 --- a/src/whop_sdk/types/referrals/businesses/earning_list_response.py +++ b/src/whop_sdk/types/referrals/businesses/earning_list_response.py @@ -25,14 +25,19 @@ class AccessPass(BaseModel): class Account(BaseModel): + """Referred account.""" + id: str - """The referred business (a biz\\__ identifier).""" + """Referred account ID.""" logo_url: Optional[str] = None + """Referred account logo URL.""" route: str + """Referred account route.""" title: str + """Referred account display name.""" class ReceiptAlternativePaymentMethod(BaseModel): @@ -87,6 +92,7 @@ class EarningListResponse(BaseModel): access_pass: Optional[AccessPass] = None account: Optional[Account] = None + """Referred account.""" cancelation_reason: Optional[str] = None """Why the earning was canceled or reversed, if applicable.""" diff --git a/src/whop_sdk/types/refund_created_webhook_event.py b/src/whop_sdk/types/refund_created_webhook_event.py index 199467d7..6ab91ee2 100644 --- a/src/whop_sdk/types/refund_created_webhook_event.py +++ b/src/whop_sdk/types/refund_created_webhook_event.py @@ -69,10 +69,10 @@ class DataPaymentProduct(BaseModel): """The unique identifier for the product.""" metadata: Optional[Dict[str, object]] = None - """Custom key-value pairs stored on the product. - - Included in webhook payloads for payment and membership events. Max 50 keys, 100 - chars per key, 500 chars per string value. + """ + Custom key-value pairs stored on the product and included in payment and + membership webhook payloads. Max 50 keys, 100 characters per key, 500 characters + per string value. """ diff --git a/src/whop_sdk/types/refund_retrieve_response.py b/src/whop_sdk/types/refund_retrieve_response.py index e09cbf69..94976ead 100644 --- a/src/whop_sdk/types/refund_retrieve_response.py +++ b/src/whop_sdk/types/refund_retrieve_response.py @@ -67,10 +67,10 @@ class PaymentProduct(BaseModel): """The unique identifier for the product.""" metadata: Optional[Dict[str, object]] = None - """Custom key-value pairs stored on the product. - - Included in webhook payloads for payment and membership events. Max 50 keys, 100 - chars per key, 500 chars per string value. + """ + Custom key-value pairs stored on the product and included in payment and + membership webhook payloads. Max 50 keys, 100 characters per key, 500 characters + per string value. """ diff --git a/src/whop_sdk/types/refund_updated_webhook_event.py b/src/whop_sdk/types/refund_updated_webhook_event.py index d73a908e..27591ce0 100644 --- a/src/whop_sdk/types/refund_updated_webhook_event.py +++ b/src/whop_sdk/types/refund_updated_webhook_event.py @@ -69,10 +69,10 @@ class DataPaymentProduct(BaseModel): """The unique identifier for the product.""" metadata: Optional[Dict[str, object]] = None - """Custom key-value pairs stored on the product. - - Included in webhook payloads for payment and membership events. Max 50 keys, 100 - chars per key, 500 chars per string value. + """ + Custom key-value pairs stored on the product and included in payment and + membership webhook payloads. Max 50 keys, 100 characters per key, 500 characters + per string value. """ diff --git a/src/whop_sdk/types/review_list_response.py b/src/whop_sdk/types/review_list_response.py index 5a670355..8627ff8d 100644 --- a/src/whop_sdk/types/review_list_response.py +++ b/src/whop_sdk/types/review_list_response.py @@ -23,7 +23,7 @@ class Attachment(BaseModel): """ content_type: Optional[str] = None - """The MIME type of the uploaded file (e.g., image/jpeg, video/mp4, audio/mpeg).""" + """Uploaded file MIME type, such as image/jpeg, video/mp4, or audio/mpeg.""" filename: Optional[str] = None """The original filename of the uploaded attachment, including its file extension.""" diff --git a/src/whop_sdk/types/review_retrieve_response.py b/src/whop_sdk/types/review_retrieve_response.py index bd1157f7..3f6b93fa 100644 --- a/src/whop_sdk/types/review_retrieve_response.py +++ b/src/whop_sdk/types/review_retrieve_response.py @@ -23,7 +23,7 @@ class Attachment(BaseModel): """ content_type: Optional[str] = None - """The MIME type of the uploaded file (e.g., image/jpeg, video/mp4, audio/mpeg).""" + """Uploaded file MIME type, such as image/jpeg, video/mp4, or audio/mpeg.""" filename: Optional[str] = None """The original filename of the uploaded attachment, including its file extension.""" @@ -42,9 +42,7 @@ class Company(BaseModel): """The unique identifier for the company.""" route: str - """ - The URL slug for the company's store page (e.g., 'pickaxe' in whop.com/pickaxe). - """ + """URL slug for the account's store page, e.g. `pickaxe` in whop.com/pickaxe.""" title: str """The display name of the company shown to customers.""" diff --git a/src/whop_sdk/types/shared/checkout_configuration.py b/src/whop_sdk/types/shared/checkout_configuration.py index 8784af7e..6aadc59d 100644 --- a/src/whop_sdk/types/shared/checkout_configuration.py +++ b/src/whop_sdk/types/shared/checkout_configuration.py @@ -55,9 +55,9 @@ class Plan(BaseModel): """ billing_period: Optional[int] = None - """The number of days between each recurring charge. - - Null for one-time plans. For example, 30 for monthly or 365 for annual billing. + """ + Number of days between recurring charges, such as 30 for monthly or 365 for + annual. `null` for one-time plans. """ currency: Currency @@ -67,9 +67,9 @@ class Plan(BaseModel): """ expiration_days: Optional[int] = None - """The number of days until the membership expires (for expiration-based plans). - - For example, 365 for a one-year access pass. + """ + Access duration in days for expiration-based plans, such as 365 for a one-year + pass. """ initial_price: float @@ -87,8 +87,8 @@ class Plan(BaseModel): release_method: ReleaseMethod """ - The method used to sell this plan: 'buy_now' for immediate purchase or - 'waitlist' for waitlist-based access. + Sales method for this plan: `buy_now` for immediate purchase or `waitlist` for + waitlist-based access. """ renewal_price: float @@ -101,10 +101,10 @@ class Plan(BaseModel): """The 3D Secure behavior for a plan.""" trial_period_days: Optional[int] = None - """The number of free trial days before the first charge on a renewal plan. + """Free trial days before first renewal charge. - Null if no trial is configured or the current user has already used a trial for - this plan. + `null` if no trial is configured or the user has already used a trial for this + plan. """ visibility: Visibility diff --git a/src/whop_sdk/types/shared/company.py b/src/whop_sdk/types/shared/company.py index 764ef992..a95e9086 100644 --- a/src/whop_sdk/types/shared/company.py +++ b/src/whop_sdk/types/shared/company.py @@ -113,9 +113,7 @@ class Company(BaseModel): """ route: str - """ - The URL slug for the company's store page (e.g., 'pickaxe' in whop.com/pickaxe). - """ + """URL slug for the account's store page, e.g. `pickaxe` in whop.com/pickaxe.""" send_customer_emails: bool """ diff --git a/src/whop_sdk/types/shared/experience.py b/src/whop_sdk/types/shared/experience.py index c6241f91..50a1951f 100644 --- a/src/whop_sdk/types/shared/experience.py +++ b/src/whop_sdk/types/shared/experience.py @@ -47,9 +47,7 @@ class Company(BaseModel): """The unique identifier for the company.""" route: str - """ - The URL slug for the company's store page (e.g., 'pickaxe' in whop.com/pickaxe). - """ + """URL slug for the account's store page, e.g. `pickaxe` in whop.com/pickaxe.""" title: str """The display name of the company shown to customers.""" @@ -78,9 +76,9 @@ class Product(BaseModel): """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). + """URL slug in the product's public link, e.g. + + `pickaxe-analytics` in whop.com/company/pickaxe-analytics. """ title: str diff --git a/src/whop_sdk/types/shared/forum_post.py b/src/whop_sdk/types/shared/forum_post.py index 7a6136d7..1bcccfe7 100644 --- a/src/whop_sdk/types/shared/forum_post.py +++ b/src/whop_sdk/types/shared/forum_post.py @@ -22,7 +22,7 @@ class Attachment(BaseModel): """ content_type: Optional[str] = None - """The MIME type of the uploaded file (e.g., image/jpeg, video/mp4, audio/mpeg).""" + """Uploaded file MIME type, such as image/jpeg, video/mp4, or audio/mpeg.""" filename: Optional[str] = None """The original filename of the uploaded attachment, including its file extension.""" diff --git a/src/whop_sdk/types/shared/membership.py b/src/whop_sdk/types/shared/membership.py index c37fd757..41057e14 100644 --- a/src/whop_sdk/types/shared/membership.py +++ b/src/whop_sdk/types/shared/membership.py @@ -65,10 +65,10 @@ class Product(BaseModel): """The unique identifier for the product.""" metadata: Optional[Dict[str, object]] = None - """Custom key-value pairs stored on the product. - - Included in webhook payloads for payment and membership events. Max 50 keys, 100 - chars per key, 500 chars per string value. + """ + Custom key-value pairs stored on the product and included in payment and + membership webhook payloads. Max 50 keys, 100 characters per key, 500 characters + per string value. """ title: str diff --git a/src/whop_sdk/types/shared/payment.py b/src/whop_sdk/types/shared/payment.py index 551e9bf4..c75635a5 100644 --- a/src/whop_sdk/types/shared/payment.py +++ b/src/whop_sdk/types/shared/payment.py @@ -272,16 +272,16 @@ class Product(BaseModel): """The unique identifier for the product.""" metadata: Optional[Dict[str, object]] = None - """Custom key-value pairs stored on the product. - - Included in webhook payloads for payment and membership events. Max 50 keys, 100 - chars per key, 500 chars per string value. + """ + Custom key-value pairs stored on the product and included in payment and + membership webhook payloads. Max 50 keys, 100 characters per key, 500 characters + per string value. """ route: str - """ - The URL slug used in the product's public link (e.g., 'my-product' in - whop.com/company/my-product). + """URL slug in the product's public link, e.g. + + `pickaxe-analytics` in whop.com/company/pickaxe-analytics. """ title: str diff --git a/src/whop_sdk/types/shared/plan.py b/src/whop_sdk/types/shared/plan.py index e708fec9..7fa371ff 100644 --- a/src/whop_sdk/types/shared/plan.py +++ b/src/whop_sdk/types/shared/plan.py @@ -5,30 +5,52 @@ from ..._models import BaseModel -__all__ = ["Plan"] +__all__ = ["Plan", "CustomField"] + + +class CustomField(BaseModel): + """Custom input fields collected on the checkout form.""" + + id: str + """Custom field ID.""" + + field_type: Literal["text"] + """Custom field input type.""" + + name: str + """Field label shown to customer at checkout.""" + + order: float + """Field position on checkout form.""" + + placeholder: Optional[str] = None + """Placeholder text shown in empty field.""" + + required: bool + """Whether the customer must complete this field to check out.""" class Plan(BaseModel): id: str - """The ID of the plan, which will look like plan\\__******\\********""" + """Plan ID, prefixed `plan_`.""" account: Optional[object] = None - """The account that sells this plan, an object with an id and title. - - Null for standalone invoice plans - """ + """Account that sells this plan; `null` for standalone invoice plans.""" adaptive_pricing_enabled: bool - """Whether this plan accepts local currency payments via adaptive pricing""" + """Whether this plan accepts local currency payments via adaptive pricing.""" billing_period: Optional[float] = None - """The number of days between recurring charges. Null for one-time plans""" + """Recurring billing interval in days, such as 30 for monthly or 365 for annual. + + `null` for one-time plans. + """ collect_tax: bool - """Whether tax is collected on purchases of this plan""" + """Whether tax is collected on purchases of this plan.""" created_at: str - """When the plan was created, as an ISO 8601 timestamp""" + """When the plan was created, as an ISO 8601 timestamp.""" currency: Literal[ "usd", @@ -122,97 +144,82 @@ class Plan(BaseModel): "whop_usd", "xau", ] - """The three-letter ISO currency code all prices on this plan are denominated in""" + """Three-letter ISO currency code for this plan's prices.""" - custom_fields: List[object] - """ - Custom input fields displayed on the checkout form, objects with id, field_type, - name, order, placeholder and required - """ + custom_fields: List[CustomField] description: Optional[str] = None - """A text description of the plan visible to customers""" + """Customer-visible plan description.""" expiration_days: Optional[float] = None - """The number of days until the membership expires, for expiration-based plans""" + """Access duration in days for expiration-based plans.""" initial_price: float - """The initial purchase price in the plan's currency""" + """Initial purchase price in plan currency.""" internal_notes: Optional[str] = None - """Private notes visible only to authorized team members""" + """Private notes visible only to authorized team members.""" invoice: Optional[object] = None - """The invoice this plan was generated for, an object with an id. - - Null unless the plan was created for an invoice - """ + """Invoice this plan was generated for; `null` unless created for an invoice.""" member_count: Optional[float] = None - """The number of active memberships on this plan. - - Only visible to authorized team members - """ + """Active memberships through this plan, when visible to the requester.""" metadata: Optional[object] = None - """Custom key-value pairs stored on the plan""" + """Custom key-value pairs stored on the plan.""" payment_method_configuration: Optional[object] = None """ - The explicit payment method configuration for the plan, an object with enabled, - disabled and include_platform_defaults. Null if the plan uses default settings + Payment method configuration (`enabled`, `disabled`, + `include_platform_defaults`); `null` when plan uses default settings. """ plan_type: Literal["renewal", "one_time"] """ - The billing model for this plan: 'renewal' for recurring subscriptions or - 'one_time' for single payments + Billing model for this plan: `renewal` (recurring) or `one_time` (single + payment). """ product: Optional[object] = None - """The product this plan belongs to, an object with an id and title. - - Null for standalone plans - """ + """Product this plan belongs to; `null` for standalone plans.""" purchase_url: str - """The full URL where customers can purchase this plan directly""" + """URL where customers can purchase this plan directly.""" release_method: Literal["buy_now", "waitlist"] - """The method used to sell this plan, e.g. 'buy_now' or 'waitlist'""" + """Sales method for this plan, such as `buy_now` or `waitlist`.""" renewal_price: float - """The recurring price charged every billing period in the plan's currency""" + """Recurring price charged every billing period.""" split_pay_required_payments: Optional[float] = None - """The number of installment payments required before the subscription pauses""" + """Installment payments required before the subscription pauses.""" stock: Optional[float] = None - """The number of units available for purchase. + """Units available for purchase, when visible to the requester.""" - Only visible to authorized team members - """ - - tax_type: str - """How tax is handled for this plan: 'inclusive', 'exclusive', or 'unspecified'""" + tax_type: Literal["inclusive", "exclusive", "unspecified"] + """How tax is handled for this plan.""" three_ds_level: Optional[Literal["mandate_challenge", "frictionless"]] = None - """The 3D Secure behavior for this plan. - - Null means the plan inherits the account default - """ + """3D Secure behavior for this plan; `null` inherits account default.""" title: Optional[str] = None - """The display name of the plan shown to customers""" + """Plan display name shown to customers.""" trial_period_days: Optional[float] = None - """The number of free trial days before the first charge on a recurring plan""" + """Free trial days before the first renewal charge. + + `null` if no trial is configured or the user has already used a trial for this + plan. + """ unlimited_stock: bool - """Whether the plan has unlimited stock""" + """Whether the plan has unlimited stock.""" updated_at: str - """When the plan was last updated, as an ISO 8601 timestamp""" + """When the plan was last updated, as an ISO 8601 timestamp.""" visibility: Literal["visible", "hidden", "archived", "quick_link"] - """Whether the plan is visible to customers or hidden from public view""" + """Whether the plan is visible to customers or hidden from public view.""" diff --git a/src/whop_sdk/types/shared/product.py b/src/whop_sdk/types/shared/product.py index 3b22f84b..70afd71f 100644 --- a/src/whop_sdk/types/shared/product.py +++ b/src/whop_sdk/types/shared/product.py @@ -19,9 +19,7 @@ class Company(BaseModel): """The unique identifier for the company.""" route: str - """ - The URL slug for the company's store page (e.g., 'pickaxe' in whop.com/pickaxe). - """ + """URL slug for the account's store page, e.g. `pickaxe` in whop.com/pickaxe.""" title: str """The display name of the company shown to customers.""" @@ -69,12 +67,12 @@ class ProductTaxCode(BaseModel): """The unique identifier for the product tax code.""" name: str - """The human-readable name of this tax classification (e.g., 'Digital - SaaS').""" + """Human-readable name of this tax classification, such as 'Digital - SaaS'.""" product_type: Literal["physical", "digital", "services"] """ - The broad product category this tax code covers (e.g., physical goods, digital - services). + Broad product category this tax code covers, such as physical goods or digital + services. """ @@ -94,10 +92,7 @@ class Product(BaseModel): """The datetime the product was created.""" custom_cta: CustomCta - """ - The call-to-action button label displayed on the product's purchase page (e.g., - 'join', 'buy', 'subscribe'). - """ + """Call-to-action button label shown on the product purchase page.""" custom_cta_url: Optional[str] = None """ @@ -106,10 +101,9 @@ class Product(BaseModel): """ custom_statement_descriptor: Optional[str] = None - """ - A custom text label that appears on the customer's bank or credit card statement - for purchases of this product. Maximum 22 characters, including the required - prefix WHOP\\**. + """Custom bank statement descriptor for product purchases. + + Maximum 22 characters, including required `WHOP*` prefix. """ description: Optional[str] = None @@ -119,10 +113,10 @@ class Product(BaseModel): """ external_identifier: Optional[str] = None - """A unique identifier used to create or update products via the API. + """External identifier for the product. - When provided on product creation endpoints, an existing product with this - identifier will be updated instead of creating a new one. + Providing it on a product creation endpoint updates the existing product with + this identifier instead of creating a new one. """ gallery_images: List[GalleryImage] @@ -130,8 +124,8 @@ class Product(BaseModel): global_affiliate_percentage: Optional[float] = None """ - The commission rate (as a percentage) that affiliates earn on sales through the - Whop marketplace global affiliate program. Null if the program is not active. + Marketplace affiliate commission percentage for this product, or `null` if + program is inactive. """ global_affiliate_status: GlobalAffiliateStatus @@ -145,25 +139,24 @@ class Product(BaseModel): member_affiliate_percentage: Optional[float] = None """ - The commission rate (as a percentage) that existing members earn when referring - new customers through the member affiliate program. Null if the program is not - active. + Member referral commission percentage for this product, or `null` if program is + inactive. """ member_affiliate_status: GlobalAffiliateStatus """The enrollment status of this product in the member affiliate program.""" member_count: int - """The number of users who currently hold an active membership to this product. + """Active memberships for this product. - Returns 0 if the company has disabled public member counts. + Returns `0` if the account has disabled public member counts. """ metadata: Optional[Dict[str, object]] = None - """Custom key-value pairs stored on the product. - - Included in webhook payloads for payment and membership events. Max 50 keys, 100 - chars per key, 500 chars per string value. + """ + Custom key-value pairs stored on the product and included in payment and + membership webhook payloads. Max 50 keys, 100 characters per key, 500 characters + per string value. """ owner_user: OwnerUser @@ -179,9 +172,9 @@ class Product(BaseModel): """The total number of published customer reviews for this product's company.""" route: str - """ - The URL slug used in the product's public link (e.g., 'my-product' in - whop.com/company/my-product). + """URL slug in the product's public link, e.g. + + `pickaxe-analytics` in whop.com/company/pickaxe-analytics. """ title: str diff --git a/src/whop_sdk/types/shared/product_list_item.py b/src/whop_sdk/types/shared/product_list_item.py index 742b735b..eb084358 100644 --- a/src/whop_sdk/types/shared/product_list_item.py +++ b/src/whop_sdk/types/shared/product_list_item.py @@ -22,35 +22,35 @@ class ProductListItem(BaseModel): """The datetime the product was created.""" external_identifier: Optional[str] = None - """A unique identifier used to create or update products via the API. + """External identifier for the product. - When provided on product creation endpoints, an existing product with this - identifier will be updated instead of creating a new one. + Providing it on a product creation endpoint updates the existing product with + this identifier instead of creating a new one. """ headline: Optional[str] = None """A short marketing headline displayed prominently on the product's product page.""" member_count: int - """The number of users who currently hold an active membership to this product. + """Active memberships for this product. - Returns 0 if the company has disabled public member counts. + Returns `0` if the account has disabled public member counts. """ metadata: Optional[Dict[str, object]] = None - """Custom key-value pairs stored on the product. - - Included in webhook payloads for payment and membership events. Max 50 keys, 100 - chars per key, 500 chars per string value. + """ + Custom key-value pairs stored on the product and included in payment and + membership webhook payloads. Max 50 keys, 100 characters per key, 500 characters + per string value. """ published_reviews_count: int """The total number of published customer reviews for this product's company.""" route: str - """ - The URL slug used in the product's public link (e.g., 'my-product' in - whop.com/company/my-product). + """URL slug in the product's public link, e.g. + + `pickaxe-analytics` in whop.com/company/pickaxe-analytics. """ title: str diff --git a/src/whop_sdk/types/swap_create_params.py b/src/whop_sdk/types/swap_create_params.py index f8335f49..ec3e05c8 100644 --- a/src/whop_sdk/types/swap_create_params.py +++ b/src/whop_sdk/types/swap_create_params.py @@ -13,16 +13,25 @@ class SwapCreateParams(TypedDict, total=False): """Business or user account ID (biz*\\** / user*\\**).""" amount: Required[str] - """Input token amount.""" + """Source token amount.""" from_token: Required[str] - """Source token, by contract address or ticker symbol (e.g. "USDT").""" + """Source token contract address or ticker symbol, such as "USDT".""" to_token: Required[str] - """Destination token, by contract address or ticker symbol (e.g. "XAUT").""" + """Destination token contract address or ticker symbol, such as "XAUT".""" from_chain: Union[str, int, None] + """Source chain name or chain ID. + + Defaults to the source token's chain when omitted. + """ slippage_bps: Optional[int] + """Maximum slippage tolerance in basis points.""" to_chain: Union[str, int, None] + """Destination chain name or chain ID. + + Defaults to the destination token's chain when omitted. + """ diff --git a/src/whop_sdk/types/swap_create_quote_params.py b/src/whop_sdk/types/swap_create_quote_params.py index 6343bbde..9a1cc323 100644 --- a/src/whop_sdk/types/swap_create_quote_params.py +++ b/src/whop_sdk/types/swap_create_quote_params.py @@ -10,22 +10,34 @@ class SwapCreateQuoteParams(TypedDict, total=False): amount: Required[str] - """Input token amount.""" + """Source token amount.""" from_token: Required[str] - """Source token, by contract address or ticker symbol (e.g. "USDT").""" + """Source token contract address or ticker symbol, such as "USDT".""" to_token: Required[str] - """Destination token, by contract address or ticker symbol (e.g. "XAUT").""" + """Destination token contract address or ticker symbol, such as "XAUT".""" from_address: Optional[str] + """Source wallet address used for the quote.""" from_chain: Union[str, int, None] + """Source chain name or chain ID. + + Defaults to the source token's chain when omitted. + """ metadata: Dict[str, object] + """Metadata to include with the quote response.""" slippage_bps: Optional[int] + """Maximum slippage tolerance in basis points.""" to_address: Optional[str] + """Destination wallet address used for the quote.""" to_chain: Union[str, int, None] + """Destination chain name or chain ID. + + Defaults to the destination token's chain when omitted. + """ diff --git a/src/whop_sdk/types/swap_create_quote_response.py b/src/whop_sdk/types/swap_create_quote_response.py index fa90b64b..8cff4e8b 100644 --- a/src/whop_sdk/types/swap_create_quote_response.py +++ b/src/whop_sdk/types/swap_create_quote_response.py @@ -11,29 +11,42 @@ class SwapCreateQuoteResponse(BaseModel): amount_in: str + """Source token amount used for the quote.""" amount_out: str + """Estimated destination token amount.""" fee_bps: int + """Whop fee in basis points.""" from_token: Dict[str, object] + """Resolved source token details.""" metadata: Dict[str, object] + """Metadata from the request.""" object: Literal["swap_quote"] rate: str + """Quoted exchange rate.""" to_token: Dict[str, builtins.object] + """Resolved destination token details.""" amount_out_min: Optional[str] = None + """Minimum destination amount after slippage.""" bridge_fee: Optional[str] = None + """Estimated bridge fee for cross-chain swaps.""" estimated_duration_seconds: Optional[int] = None + """Estimated time for the swap to complete.""" from_address: Optional[str] = None + """Source wallet address used for the quote.""" requires_token_approval: Optional[bool] = None + """Whether the source token needs approval before swapping.""" to_address: Optional[str] = None + """Destination wallet address used for the quote.""" diff --git a/src/whop_sdk/types/swap_create_response.py b/src/whop_sdk/types/swap_create_response.py index 10a62e85..da5979b9 100644 --- a/src/whop_sdk/types/swap_create_response.py +++ b/src/whop_sdk/types/swap_create_response.py @@ -10,18 +10,24 @@ class SwapCreateResponse(BaseModel): id: str - """Swap ID — poll GET /swaps/{id} for status.""" + """Swap ID. Poll `GET /swaps/:id` for status.""" account_id: str + """Account ID that owns the wallet used for the swap.""" object: Literal["swap"] status: str + """Initial swap status.""" amount_out_expected: Optional[str] = None + """Expected destination token amount.""" amount_out_min: Optional[str] = None + """Minimum destination amount after slippage.""" rate: Optional[str] = None + """Quoted exchange rate used to create the swap.""" to_chain: Optional[str] = None + """Destination chain for the swap.""" diff --git a/src/whop_sdk/types/swap_list_response.py b/src/whop_sdk/types/swap_list_response.py index e1c02d5f..ab55ffdd 100644 --- a/src/whop_sdk/types/swap_list_response.py +++ b/src/whop_sdk/types/swap_list_response.py @@ -10,17 +10,23 @@ class Data(BaseModel): id: str + """Swap ID.""" account_id: str + """Account ID that owns the wallet used for the swap.""" object: Literal["swap"] status: str + """Current swap status.""" tx_hashes: List[str] + """On-chain transaction hashes produced by the swap.""" error: Optional[str] = None + """Latest error returned for a failed swap.""" class SwapListResponse(BaseModel): data: List[Data] + """Swaps returned for this account.""" diff --git a/src/whop_sdk/types/swap_retrieve_response.py b/src/whop_sdk/types/swap_retrieve_response.py index cd161b34..91f98537 100644 --- a/src/whop_sdk/types/swap_retrieve_response.py +++ b/src/whop_sdk/types/swap_retrieve_response.py @@ -10,13 +10,18 @@ class SwapRetrieveResponse(BaseModel): id: str + """Swap ID.""" account_id: str + """Account ID that owns the wallet used for the swap.""" object: Literal["swap"] status: str + """Current swap status.""" tx_hashes: List[str] + """On-chain transaction hashes produced by the swap.""" error: Optional[str] = None + """Latest error returned for a failed swap.""" diff --git a/src/whop_sdk/types/transfer_create_params.py b/src/whop_sdk/types/transfer_create_params.py index 1718177e..c9e43000 100644 --- a/src/whop_sdk/types/transfer_create_params.py +++ b/src/whop_sdk/types/transfer_create_params.py @@ -22,7 +22,7 @@ class TransferCreateParams(TypedDict, total=False): """ currency: str - """The currency, such as usd. Required for ledger transfers.""" + """Currency, such as `usd`. Required for ledger transfers.""" destination_id: str """The recipient. diff --git a/src/whop_sdk/types/transfer_create_response.py b/src/whop_sdk/types/transfer_create_response.py index dd6d5e70..669531d8 100644 --- a/src/whop_sdk/types/transfer_create_response.py +++ b/src/whop_sdk/types/transfer_create_response.py @@ -26,22 +26,28 @@ class TransferDestinationCompany(BaseModel): id: str + """Account ID.""" typename: Literal["Company"] route: Optional[str] = None + """Account route.""" title: Optional[str] = None + """Account display name.""" class TransferDestinationUser(BaseModel): id: str + """User ID.""" typename: Literal["User"] name: Optional[str] = None + """User display name.""" username: Optional[str] = None + """User's username.""" TransferDestination: TypeAlias = Annotated[ @@ -51,22 +57,28 @@ class TransferDestinationUser(BaseModel): class TransferOriginCompany(BaseModel): id: str + """Account ID.""" typename: Literal["Company"] route: Optional[str] = None + """Account route.""" title: Optional[str] = None + """Account display name.""" class TransferOriginUser(BaseModel): id: str + """User ID.""" typename: Literal["User"] name: Optional[str] = None + """User display name.""" username: Optional[str] = None + """User's username.""" TransferOrigin: TypeAlias = Annotated[ @@ -78,26 +90,37 @@ class Transfer(BaseModel): """A transfer of credit between two ledger accounts.""" id: str + """Transfer ID.""" amount: float + """Transfer amount.""" created_at: datetime + """When the transfer was created.""" currency: str + """Transfer currency.""" destination: TransferDestination + """Account or user receiving funds.""" destination_ledger_account_id: str + """Destination ledger account ID.""" origin: TransferOrigin + """Account or user sending funds.""" origin_ledger_account_id: str + """Source ledger account ID.""" fee_amount: Optional[float] = None + """Fee charged for the transfer.""" metadata: Optional[Dict[str, object]] = None + """Custom metadata attached to the transfer.""" notes: Optional[str] = None + """Transfer note.""" class SendDestination(BaseModel): diff --git a/src/whop_sdk/types/transfer_list_response.py b/src/whop_sdk/types/transfer_list_response.py index 5a167016..7f516918 100644 --- a/src/whop_sdk/types/transfer_list_response.py +++ b/src/whop_sdk/types/transfer_list_response.py @@ -12,19 +12,28 @@ class TransferListResponse(BaseModel): """A transfer of credit between two ledger accounts.""" id: str + """Transfer ID.""" amount: float + """Transfer amount.""" created_at: datetime + """When the transfer was created.""" currency: str + """Transfer currency.""" destination_ledger_account_id: str + """Destination ledger account ID.""" origin_ledger_account_id: str + """Source ledger account ID.""" fee_amount: Optional[float] = None + """Fee charged for the transfer.""" metadata: Optional[Dict[str, object]] = None + """Custom metadata attached to the transfer.""" notes: Optional[str] = None + """Transfer note.""" diff --git a/src/whop_sdk/types/transfer_retrieve_response.py b/src/whop_sdk/types/transfer_retrieve_response.py index c06cef55..cd41c697 100644 --- a/src/whop_sdk/types/transfer_retrieve_response.py +++ b/src/whop_sdk/types/transfer_retrieve_response.py @@ -20,22 +20,28 @@ class DestinationCompany(BaseModel): id: str + """Account ID.""" typename: Literal["Company"] route: Optional[str] = None + """Account route.""" title: Optional[str] = None + """Account display name.""" class DestinationUser(BaseModel): id: str + """User ID.""" typename: Literal["User"] name: Optional[str] = None + """User display name.""" username: Optional[str] = None + """User's username.""" Destination: TypeAlias = Annotated[Union[DestinationCompany, DestinationUser], PropertyInfo(discriminator="typename")] @@ -43,22 +49,28 @@ class DestinationUser(BaseModel): class OriginCompany(BaseModel): id: str + """Account ID.""" typename: Literal["Company"] route: Optional[str] = None + """Account route.""" title: Optional[str] = None + """Account display name.""" class OriginUser(BaseModel): id: str + """User ID.""" typename: Literal["User"] name: Optional[str] = None + """User display name.""" username: Optional[str] = None + """User's username.""" Origin: TypeAlias = Annotated[Union[OriginCompany, OriginUser], PropertyInfo(discriminator="typename")] @@ -68,23 +80,34 @@ class TransferRetrieveResponse(BaseModel): """A transfer of credit between two ledger accounts.""" id: str + """Transfer ID.""" amount: float + """Transfer amount.""" created_at: datetime + """When the transfer was created.""" currency: str + """Transfer currency.""" destination: Destination + """Account or user receiving funds.""" destination_ledger_account_id: str + """Destination ledger account ID.""" origin: Origin + """Account or user sending funds.""" origin_ledger_account_id: str + """Source ledger account ID.""" fee_amount: Optional[float] = None + """Fee charged for the transfer.""" metadata: Optional[Dict[str, object]] = None + """Custom metadata attached to the transfer.""" notes: Optional[str] = None + """Transfer note.""" diff --git a/src/whop_sdk/types/user.py b/src/whop_sdk/types/user.py index 9295b603..5630f4b3 100644 --- a/src/whop_sdk/types/user.py +++ b/src/whop_sdk/types/user.py @@ -8,40 +8,40 @@ class Balance(BaseModel): - """The user's holdings (crypto and fiat), each with its USD value. + """User holdings (crypto and fiat), each with USD value. - Empty when total_usd is null (not computed) + Empty when `total_usd` is `null`. """ balance: str - """The total amount held in native units, as a decimal string""" + """Total amount held in native units, as a decimal string.""" breakdown: object """ - The holding split into available, pending, and reserve amounts (native-unit - decimal strings). On-chain crypto is entirely available; good_funds and fiat - cash can have pending/reserve portions + Balance split into available, pending, and reserve amounts, as native-unit + decimal strings. On-chain crypto is entirely available; good_funds and fiat cash + can have pending or reserve portions. """ icon_url: Optional[str] = None - """The URL of the holding's icon, when available""" + """Holding icon URL.""" name: str """The holding's display name""" price_usd: Optional[float] = None - """The USD price per unit, or null when no exchange rate is available""" + """USD price per unit, or `null` when no exchange rate is available.""" symbol: str - """The holding's display symbol, e.g. USDT, cbBTC, or EUR""" + """Holding display symbol, such as `USDT`, `cbBTC`, or `EUR`.""" value_usd: Optional[str] = None - """The total USD value of the holding, or null when no exchange rate is available""" + """Holding USD value, or `null` when no exchange rate is available.""" class User(BaseModel): id: str - """The ID of the user, which will look like user\\__******\\********""" + """User ID, prefixed `user_`.""" balances: List[Balance] @@ -58,20 +58,18 @@ class User(BaseModel): """The user's profile picture, an object with a url""" total_usd: Optional[str] = None - """Total USD value across all balances with a known exchange rate. + """Total USD value across the user's balances with known exchange rates. - Only computed on the self-view (GET /users/me) for callers with the balance-read - scope; null (with an empty balances array) otherwise and when the balance source - is unavailable + Computed only on `GET /users/me` self-view for callers with balance-read scope; + `null` otherwise. """ username: str """The user's unique username""" verification: object - """The user's identity-verification status. - - `individual` is KYC, `business` is KYB; each is null when that profile has not - been created, otherwise { status } where status is one of not_started, pending, - approved, rejected + """ + Identity verification status for the user's `individual` (KYC) and `business` + (KYB) profiles. Each is `null` until created, otherwise a `status` of + `not_started`, `pending`, `approved`, or `rejected`. """ diff --git a/src/whop_sdk/types/verification_create_params.py b/src/whop_sdk/types/verification_create_params.py index 792d5488..a60f1183 100644 --- a/src/whop_sdk/types/verification_create_params.py +++ b/src/whop_sdk/types/verification_create_params.py @@ -13,65 +13,59 @@ class VerificationCreateParams(TypedDict, total=False): """The account ID to verify (biz\\__ tag).""" address: Dict[str, object] - """Optional pre-fill claim. Address (line1, city, state, postal_code).""" + """Address to prefill in provider session.""" business_name: str - """Optional pre-fill claim for businesses.""" + """Legal business name to prefill in provider session.""" business_structure: str - """Optional. Business structure (e.g. llc, corporation).""" + """Business entity structure, such as `llc` or `corporation`.""" business_website: str - """Optional. + """Business website URL for account verifications. - Business website URL. Accepted for both individual and business verifications on - company accounts; persisted to the account's metadata and used to provision the - payout account on approval. Whop store pages are rejected. + Stored on the account and used when provisioning the payout account. Whop store + pages are rejected. """ country: str - """Optional pre-fill claim. + """Country code for provider session. - Country code; for businesses, the country of incorporation. + For businesses, use the country of incorporation. """ date_of_birth: str - """Optional pre-fill claim. + """Date of birth to prefill in provider session. - Seeds the Sumsub session; attested values come from Sumsub on approval. + Approved values come from the provider. """ first_name: str - """Optional pre-fill claim. + """First name to prefill in provider session. - Seeds the Sumsub session; attested values come from Sumsub on approval. + Approved values come from the provider. """ kind: Literal["individual", "business"] - """The verification type. Defaults to individual.""" + """Verification profile type. Defaults to `individual`.""" last_name: str - """Optional pre-fill claim. + """Last name to prefill in provider session. - Seeds the Sumsub session; attested values come from Sumsub on approval. + Approved values come from the provider. """ phone: str - """Optional pre-fill claim — phone number.""" + """Phone number to prefill in provider session.""" place_of_incorporation: str - """Optional. - - Place of incorporation (state/region); maps to the business address state. - """ + """State or region of incorporation for business verification.""" restart: bool """Whether to restart an in-flight verification.""" tax_identification_number: str - """Optional. + """Tax ID for verification, such as an SSN for individuals or EIN for businesses. - Tax identification number — SSN for individuals, EIN for businesses. Tokenized - in transit, never stored raw; stored on the profile so the payout account, - provisioned on approval, doesn't raise a tax-id RFI. + Tokenized in transit and stored only on the profile. """ diff --git a/src/whop_sdk/types/verification_create_response.py b/src/whop_sdk/types/verification_create_response.py index ac7a1faa..2b46ef7e 100644 --- a/src/whop_sdk/types/verification_create_response.py +++ b/src/whop_sdk/types/verification_create_response.py @@ -10,64 +10,83 @@ class RfiRequestedFile(BaseModel): category: Optional[str] = None + """Provider document category.""" is_optional: Optional[bool] = None + """Whether this document can be omitted.""" kind: Optional[str] = None + """Document kind to upload when answering the RFI.""" class Rfi(BaseModel): id: Optional[str] = None + """RFI ID to send when answering this request.""" created_at: Optional[str] = None + """When the RFI was created.""" description: Optional[str] = None + """Request text from verification provider.""" error_message: Optional[str] = None + """Provider error for invalid response, if any.""" requested_files: Optional[List[RfiRequestedFile]] = None - """Documents the provider is requesting (file-upload RFIs). - - The `kind` is what to send back when answering. - """ + """Documents requested for a file-upload RFI.""" status: Optional[Literal["outstanding", "invalid"]] = None + """RFI status.""" type: Optional[str] = None + """Expected answer type for this RFI.""" class VerificationCreateResponse(BaseModel): id: Optional[str] = None - """The verification ID, e.g. idpf\\__\\**""" + """Verification ID, prefixed `idpf_`.""" address: Optional[object] = None + """Address associated with the verification profile.""" business_name: Optional[str] = None + """Legal business name for business verification.""" business_structure: Optional[str] = None + """Business entity structure, such as `llc` or `corporation`.""" country: Optional[str] = None - """ISO 3166-1 alpha-2 country code (e.g. - - `US`, `GB`). For individuals this is the country of citizenship or residence - reported by the identity provider; for businesses this is the country of - incorporation. - """ + """Two-letter ISO 3166-1 country code reported by the identity provider.""" created_at: Optional[str] = None + """When the verification profile was created.""" date_of_birth: Optional[str] = None + """Date of birth for individual verification.""" first_name: Optional[str] = None + """First name for individual verification.""" kind: Optional[Literal["individual", "business"]] = None + """Verification profile type.""" last_name: Optional[str] = None + """Last name for individual verification.""" rfis: Optional[List[Rfi]] = None + """ + Outstanding or invalid requests for information that must be answered to + continue verification. + """ session_url: Optional[str] = None + """Hosted provider session URL for pending verifications.""" status: Optional[Literal["not_started", "pending", "approved", "rejected", "action_required"]] = None + """Current verification status. + + `action_required` means one or more RFIs need a response. + """ updated_at: Optional[str] = None + """When the verification profile was last updated.""" diff --git a/src/whop_sdk/types/verification_delete_response.py b/src/whop_sdk/types/verification_delete_response.py index 3448aef4..8159b283 100644 --- a/src/whop_sdk/types/verification_delete_response.py +++ b/src/whop_sdk/types/verification_delete_response.py @@ -9,5 +9,7 @@ class VerificationDeleteResponse(BaseModel): id: Optional[str] = None + """Deleted verification ID.""" deleted: Optional[bool] = None + """Whether the verification was unlinked.""" diff --git a/src/whop_sdk/types/verification_list_response.py b/src/whop_sdk/types/verification_list_response.py index 0bd21076..a3b10909 100644 --- a/src/whop_sdk/types/verification_list_response.py +++ b/src/whop_sdk/types/verification_list_response.py @@ -10,68 +10,88 @@ class DataRfiRequestedFile(BaseModel): category: Optional[str] = None + """Provider document category.""" is_optional: Optional[bool] = None + """Whether this document can be omitted.""" kind: Optional[str] = None + """Document kind to upload when answering the RFI.""" class DataRfi(BaseModel): id: Optional[str] = None + """RFI ID to send when answering this request.""" created_at: Optional[str] = None + """When the RFI was created.""" description: Optional[str] = None + """Request text from verification provider.""" error_message: Optional[str] = None + """Provider error for invalid response, if any.""" requested_files: Optional[List[DataRfiRequestedFile]] = None - """Documents the provider is requesting (file-upload RFIs). - - The `kind` is what to send back when answering. - """ + """Documents requested for a file-upload RFI.""" status: Optional[Literal["outstanding", "invalid"]] = None + """RFI status.""" type: Optional[str] = None + """Expected answer type for this RFI.""" class Data(BaseModel): id: Optional[str] = None - """The verification ID, e.g. idpf\\__\\**""" + """Verification ID, prefixed `idpf_`.""" address: Optional[object] = None + """Address associated with the verification profile.""" business_name: Optional[str] = None + """Legal business name for business verification.""" business_structure: Optional[str] = None + """Business entity structure, such as `llc` or `corporation`.""" country: Optional[str] = None - """ISO 3166-1 alpha-2 country code (e.g. - - `US`, `GB`). For individuals this is the country of citizenship or residence - reported by the identity provider; for businesses this is the country of - incorporation. - """ + """Two-letter ISO 3166-1 country code reported by the identity provider.""" created_at: Optional[str] = None + """When the verification profile was created.""" date_of_birth: Optional[str] = None + """Date of birth for individual verification.""" first_name: Optional[str] = None + """First name for individual verification.""" kind: Optional[Literal["individual", "business"]] = None + """Verification profile type.""" last_name: Optional[str] = None + """Last name for individual verification.""" rfis: Optional[List[DataRfi]] = None + """ + Outstanding or invalid requests for information that must be answered to + continue verification. + """ session_url: Optional[str] = None + """Hosted provider session URL for pending verifications.""" status: Optional[Literal["not_started", "pending", "approved", "rejected", "action_required"]] = None + """Current verification status. + + `action_required` means one or more RFIs need a response. + """ updated_at: Optional[str] = None + """When the verification profile was last updated.""" class VerificationListResponse(BaseModel): data: Optional[List[Data]] = None + """Verification profiles for this account.""" diff --git a/src/whop_sdk/types/verification_retrieve_response.py b/src/whop_sdk/types/verification_retrieve_response.py index 2b37ff1e..2e284616 100644 --- a/src/whop_sdk/types/verification_retrieve_response.py +++ b/src/whop_sdk/types/verification_retrieve_response.py @@ -10,64 +10,83 @@ class RfiRequestedFile(BaseModel): category: Optional[str] = None + """Provider document category.""" is_optional: Optional[bool] = None + """Whether this document can be omitted.""" kind: Optional[str] = None + """Document kind to upload when answering the RFI.""" class Rfi(BaseModel): id: Optional[str] = None + """RFI ID to send when answering this request.""" created_at: Optional[str] = None + """When the RFI was created.""" description: Optional[str] = None + """Request text from verification provider.""" error_message: Optional[str] = None + """Provider error for invalid response, if any.""" requested_files: Optional[List[RfiRequestedFile]] = None - """Documents the provider is requesting (file-upload RFIs). - - The `kind` is what to send back when answering. - """ + """Documents requested for a file-upload RFI.""" status: Optional[Literal["outstanding", "invalid"]] = None + """RFI status.""" type: Optional[str] = None + """Expected answer type for this RFI.""" class VerificationRetrieveResponse(BaseModel): id: Optional[str] = None - """The verification ID, e.g. idpf\\__\\**""" + """Verification ID, prefixed `idpf_`.""" address: Optional[object] = None + """Address associated with the verification profile.""" business_name: Optional[str] = None + """Legal business name for business verification.""" business_structure: Optional[str] = None + """Business entity structure, such as `llc` or `corporation`.""" country: Optional[str] = None - """ISO 3166-1 alpha-2 country code (e.g. - - `US`, `GB`). For individuals this is the country of citizenship or residence - reported by the identity provider; for businesses this is the country of - incorporation. - """ + """Two-letter ISO 3166-1 country code reported by the identity provider.""" created_at: Optional[str] = None + """When the verification profile was created.""" date_of_birth: Optional[str] = None + """Date of birth for individual verification.""" first_name: Optional[str] = None + """First name for individual verification.""" kind: Optional[Literal["individual", "business"]] = None + """Verification profile type.""" last_name: Optional[str] = None + """Last name for individual verification.""" rfis: Optional[List[Rfi]] = None + """ + Outstanding or invalid requests for information that must be answered to + continue verification. + """ session_url: Optional[str] = None + """Hosted provider session URL for pending verifications.""" status: Optional[Literal["not_started", "pending", "approved", "rejected", "action_required"]] = None + """Current verification status. + + `action_required` means one or more RFIs need a response. + """ updated_at: Optional[str] = None + """When the verification profile was last updated.""" diff --git a/src/whop_sdk/types/verification_update_params.py b/src/whop_sdk/types/verification_update_params.py index 1e1b3b3e..e046f4c6 100644 --- a/src/whop_sdk/types/verification_update_params.py +++ b/src/whop_sdk/types/verification_update_params.py @@ -10,39 +10,39 @@ class VerificationUpdateParams(TypedDict, total=False): business_address: Dict[str, object] - """The business address.""" + """Business address to submit for verification.""" business_name: str - """The business name.""" + """Legal business name to submit for verification.""" business_structure: str - """The business structure.""" + """Business entity structure to submit for verification.""" country: str - """The country code.""" + """Country code to submit for verification.""" date_of_birth: str - """The date of birth.""" + """Date of birth to submit for individual verification.""" first_name: str - """The first name on the verification.""" + """First name to submit for individual verification.""" last_name: str - """The last name on the verification.""" + """Last name to submit for individual verification.""" personal_address: Dict[str, object] - """The personal address.""" + """Personal address to submit for individual verification.""" rfis: Iterable[Rfi] - """RFI responses. + """Responses to outstanding RFIs. - Each entry must include id and a value, address, or files payload. + Each entry must include an RFI ID and a value, address, or files payload. """ class Rfi(TypedDict, total=False): id: Required[str] - """The RFI tag (paa\\__\\**).""" + """RFI ID being answered.""" address: Dict[str, object] """Address payload for address RFIs.""" @@ -51,7 +51,7 @@ class Rfi(TypedDict, total=False): """File upload payload for document RFIs.""" value: str - """The value for text/date/phone RFIs.""" + """Answer value for text, date, or phone RFIs.""" value_type: Literal["raw", "vault_token"] - """Defaults to raw.""" + """How the answer value is encoded. Defaults to `raw`.""" diff --git a/src/whop_sdk/types/verification_update_response.py b/src/whop_sdk/types/verification_update_response.py index a75097ee..7737fc7f 100644 --- a/src/whop_sdk/types/verification_update_response.py +++ b/src/whop_sdk/types/verification_update_response.py @@ -10,64 +10,83 @@ class RfiRequestedFile(BaseModel): category: Optional[str] = None + """Provider document category.""" is_optional: Optional[bool] = None + """Whether this document can be omitted.""" kind: Optional[str] = None + """Document kind to upload when answering the RFI.""" class Rfi(BaseModel): id: Optional[str] = None + """RFI ID to send when answering this request.""" created_at: Optional[str] = None + """When the RFI was created.""" description: Optional[str] = None + """Request text from verification provider.""" error_message: Optional[str] = None + """Provider error for invalid response, if any.""" requested_files: Optional[List[RfiRequestedFile]] = None - """Documents the provider is requesting (file-upload RFIs). - - The `kind` is what to send back when answering. - """ + """Documents requested for a file-upload RFI.""" status: Optional[Literal["outstanding", "invalid"]] = None + """RFI status.""" type: Optional[str] = None + """Expected answer type for this RFI.""" class VerificationUpdateResponse(BaseModel): id: Optional[str] = None - """The verification ID, e.g. idpf\\__\\**""" + """Verification ID, prefixed `idpf_`.""" address: Optional[object] = None + """Address associated with the verification profile.""" business_name: Optional[str] = None + """Legal business name for business verification.""" business_structure: Optional[str] = None + """Business entity structure, such as `llc` or `corporation`.""" country: Optional[str] = None - """ISO 3166-1 alpha-2 country code (e.g. - - `US`, `GB`). For individuals this is the country of citizenship or residence - reported by the identity provider; for businesses this is the country of - incorporation. - """ + """Two-letter ISO 3166-1 country code reported by the identity provider.""" created_at: Optional[str] = None + """When the verification profile was created.""" date_of_birth: Optional[str] = None + """Date of birth for individual verification.""" first_name: Optional[str] = None + """First name for individual verification.""" kind: Optional[Literal["individual", "business"]] = None + """Verification profile type.""" last_name: Optional[str] = None + """Last name for individual verification.""" rfis: Optional[List[Rfi]] = None + """ + Outstanding or invalid requests for information that must be answered to + continue verification. + """ session_url: Optional[str] = None + """Hosted provider session URL for pending verifications.""" status: Optional[Literal["not_started", "pending", "approved", "rejected", "action_required"]] = None + """Current verification status. + + `action_required` means one or more RFIs need a response. + """ updated_at: Optional[str] = None + """When the verification profile was last updated.""" diff --git a/tests/api_resources/referrals/test_businesses.py b/tests/api_resources/referrals/test_businesses.py index 51c774a5..d3075554 100644 --- a/tests/api_resources/referrals/test_businesses.py +++ b/tests/api_resources/referrals/test_businesses.py @@ -79,6 +79,8 @@ def test_method_list_with_all_params(self, client: Whop) -> None: first=100, has_earnings=True, last=100, + order="asc", + sort="created_at", status="active", ) assert_matches_type(SyncCursorPage[BusinessListResponse], business, path=["response"]) @@ -211,6 +213,8 @@ async def test_method_list_with_all_params(self, async_client: AsyncWhop) -> Non first=100, has_earnings=True, last=100, + order="asc", + sort="created_at", status="active", ) assert_matches_type(AsyncCursorPage[BusinessListResponse], business, path=["response"]) From 56ed50a85ff5d9a14b74b1ede9b30c67616973d5 Mon Sep 17 00:00:00 2001 From: stlc-bot Date: Sat, 27 Jun 2026 03:59:55 +0000 Subject: [PATCH 082/109] feat(payouts): unify payout verification and audit RMIs as information requests Stainless-Generated-From: d2fe5b972e8cd8c9e91f38d6d5aefde985255ff1 --- src/whop_sdk/resources/verifications.py | 118 +++++++++--------- .../types/verification_create_params.py | 44 ++++--- .../types/verification_create_response.py | 75 +++++------ .../types/verification_delete_response.py | 2 - .../types/verification_list_response.py | 76 +++++------ .../types/verification_retrieve_response.py | 75 +++++------ .../types/verification_update_params.py | 36 +++--- .../types/verification_update_response.py | 75 +++++------ tests/api_resources/test_verifications.py | 4 +- 9 files changed, 263 insertions(+), 242 deletions(-) diff --git a/src/whop_sdk/resources/verifications.py b/src/whop_sdk/resources/verifications.py index 708b7ad3..0fe280bb 100644 --- a/src/whop_sdk/resources/verifications.py +++ b/src/whop_sdk/resources/verifications.py @@ -80,37 +80,40 @@ def create( Args: account_id: The account ID to verify (biz\\__ tag). - address: Address to prefill in provider session. + address: Optional pre-fill claim. Address (line1, city, state, postal_code). - business_name: Legal business name to prefill in provider session. + business_name: Optional pre-fill claim for businesses. - business_structure: Business entity structure, such as `llc` or `corporation`. + business_structure: Optional. Business structure (e.g. llc, corporation). - business_website: Business website URL for account verifications. Stored on the account and used - when provisioning the payout account. Whop store pages are rejected. + business_website: Optional. Business website URL. Accepted for both individual and business + verifications on company accounts; persisted to the account's metadata and used + to provision the payout account on approval. Whop store pages are rejected. - country: Country code for provider session. For businesses, use the country of + country: Optional pre-fill claim. Country code; for businesses, the country of incorporation. - date_of_birth: Date of birth to prefill in provider session. Approved values come from the - provider. + date_of_birth: Optional pre-fill claim. Seeds the Sumsub session; attested values come from + Sumsub on approval. - first_name: First name to prefill in provider session. Approved values come from the - provider. + first_name: Optional pre-fill claim. Seeds the Sumsub session; attested values come from + Sumsub on approval. - kind: Verification profile type. Defaults to `individual`. + kind: The verification type. Defaults to individual. - last_name: Last name to prefill in provider session. Approved values come from the - provider. + last_name: Optional pre-fill claim. Seeds the Sumsub session; attested values come from + Sumsub on approval. - phone: Phone number to prefill in provider session. + phone: Optional pre-fill claim — phone number. - place_of_incorporation: State or region of incorporation for business verification. + place_of_incorporation: Optional. Place of incorporation (state/region); maps to the business address + state. restart: Whether to restart an in-flight verification. - tax_identification_number: Tax ID for verification, such as an SSN for individuals or EIN for businesses. - Tokenized in transit and stored only on the profile. + tax_identification_number: Optional. Tax identification number — SSN for individuals, EIN for businesses. + Tokenized in transit, never stored raw; stored on the profile so the payout + account, provisioned on approval, doesn't raise a tax-id RFI. extra_headers: Send extra headers @@ -195,7 +198,7 @@ def update( first_name: str | Omit = omit, last_name: str | Omit = omit, personal_address: Dict[str, object] | Omit = omit, - rfis: Iterable[verification_update_params.Rfi] | Omit = omit, + requested_information: Iterable[verification_update_params.RequestedInformation] | 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, @@ -208,23 +211,23 @@ def update( RFIs. Args: - business_address: Business address to submit for verification. + business_address: The business address. - business_name: Legal business name to submit for verification. + business_name: The business name. - business_structure: Business entity structure to submit for verification. + business_structure: The business structure. - country: Country code to submit for verification. + country: The country code. - date_of_birth: Date of birth to submit for individual verification. + date_of_birth: The date of birth. - first_name: First name to submit for individual verification. + first_name: The first name on the verification. - last_name: Last name to submit for individual verification. + last_name: The last name on the verification. - personal_address: Personal address to submit for individual verification. + personal_address: The personal address. - rfis: Responses to outstanding RFIs. Each entry must include an RFI ID and a value, + requested_information: Answers to requested information. Each entry must include id and a value, address, or files payload. extra_headers: Send extra headers @@ -249,7 +252,7 @@ def update( "first_name": first_name, "last_name": last_name, "personal_address": personal_address, - "rfis": rfis, + "requested_information": requested_information, }, verification_update_params.VerificationUpdateParams, ), @@ -382,37 +385,40 @@ async def create( Args: account_id: The account ID to verify (biz\\__ tag). - address: Address to prefill in provider session. + address: Optional pre-fill claim. Address (line1, city, state, postal_code). - business_name: Legal business name to prefill in provider session. + business_name: Optional pre-fill claim for businesses. - business_structure: Business entity structure, such as `llc` or `corporation`. + business_structure: Optional. Business structure (e.g. llc, corporation). - business_website: Business website URL for account verifications. Stored on the account and used - when provisioning the payout account. Whop store pages are rejected. + business_website: Optional. Business website URL. Accepted for both individual and business + verifications on company accounts; persisted to the account's metadata and used + to provision the payout account on approval. Whop store pages are rejected. - country: Country code for provider session. For businesses, use the country of + country: Optional pre-fill claim. Country code; for businesses, the country of incorporation. - date_of_birth: Date of birth to prefill in provider session. Approved values come from the - provider. + date_of_birth: Optional pre-fill claim. Seeds the Sumsub session; attested values come from + Sumsub on approval. - first_name: First name to prefill in provider session. Approved values come from the - provider. + first_name: Optional pre-fill claim. Seeds the Sumsub session; attested values come from + Sumsub on approval. - kind: Verification profile type. Defaults to `individual`. + kind: The verification type. Defaults to individual. - last_name: Last name to prefill in provider session. Approved values come from the - provider. + last_name: Optional pre-fill claim. Seeds the Sumsub session; attested values come from + Sumsub on approval. - phone: Phone number to prefill in provider session. + phone: Optional pre-fill claim — phone number. - place_of_incorporation: State or region of incorporation for business verification. + place_of_incorporation: Optional. Place of incorporation (state/region); maps to the business address + state. restart: Whether to restart an in-flight verification. - tax_identification_number: Tax ID for verification, such as an SSN for individuals or EIN for businesses. - Tokenized in transit and stored only on the profile. + tax_identification_number: Optional. Tax identification number — SSN for individuals, EIN for businesses. + Tokenized in transit, never stored raw; stored on the profile so the payout + account, provisioned on approval, doesn't raise a tax-id RFI. extra_headers: Send extra headers @@ -499,7 +505,7 @@ async def update( first_name: str | Omit = omit, last_name: str | Omit = omit, personal_address: Dict[str, object] | Omit = omit, - rfis: Iterable[verification_update_params.Rfi] | Omit = omit, + requested_information: Iterable[verification_update_params.RequestedInformation] | 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, @@ -512,23 +518,23 @@ async def update( RFIs. Args: - business_address: Business address to submit for verification. + business_address: The business address. - business_name: Legal business name to submit for verification. + business_name: The business name. - business_structure: Business entity structure to submit for verification. + business_structure: The business structure. - country: Country code to submit for verification. + country: The country code. - date_of_birth: Date of birth to submit for individual verification. + date_of_birth: The date of birth. - first_name: First name to submit for individual verification. + first_name: The first name on the verification. - last_name: Last name to submit for individual verification. + last_name: The last name on the verification. - personal_address: Personal address to submit for individual verification. + personal_address: The personal address. - rfis: Responses to outstanding RFIs. Each entry must include an RFI ID and a value, + requested_information: Answers to requested information. Each entry must include id and a value, address, or files payload. extra_headers: Send extra headers @@ -553,7 +559,7 @@ async def update( "first_name": first_name, "last_name": last_name, "personal_address": personal_address, - "rfis": rfis, + "requested_information": requested_information, }, verification_update_params.VerificationUpdateParams, ), diff --git a/src/whop_sdk/types/verification_create_params.py b/src/whop_sdk/types/verification_create_params.py index a60f1183..792d5488 100644 --- a/src/whop_sdk/types/verification_create_params.py +++ b/src/whop_sdk/types/verification_create_params.py @@ -13,59 +13,65 @@ class VerificationCreateParams(TypedDict, total=False): """The account ID to verify (biz\\__ tag).""" address: Dict[str, object] - """Address to prefill in provider session.""" + """Optional pre-fill claim. Address (line1, city, state, postal_code).""" business_name: str - """Legal business name to prefill in provider session.""" + """Optional pre-fill claim for businesses.""" business_structure: str - """Business entity structure, such as `llc` or `corporation`.""" + """Optional. Business structure (e.g. llc, corporation).""" business_website: str - """Business website URL for account verifications. + """Optional. - Stored on the account and used when provisioning the payout account. Whop store - pages are rejected. + Business website URL. Accepted for both individual and business verifications on + company accounts; persisted to the account's metadata and used to provision the + payout account on approval. Whop store pages are rejected. """ country: str - """Country code for provider session. + """Optional pre-fill claim. - For businesses, use the country of incorporation. + Country code; for businesses, the country of incorporation. """ date_of_birth: str - """Date of birth to prefill in provider session. + """Optional pre-fill claim. - Approved values come from the provider. + Seeds the Sumsub session; attested values come from Sumsub on approval. """ first_name: str - """First name to prefill in provider session. + """Optional pre-fill claim. - Approved values come from the provider. + Seeds the Sumsub session; attested values come from Sumsub on approval. """ kind: Literal["individual", "business"] - """Verification profile type. Defaults to `individual`.""" + """The verification type. Defaults to individual.""" last_name: str - """Last name to prefill in provider session. + """Optional pre-fill claim. - Approved values come from the provider. + Seeds the Sumsub session; attested values come from Sumsub on approval. """ phone: str - """Phone number to prefill in provider session.""" + """Optional pre-fill claim — phone number.""" place_of_incorporation: str - """State or region of incorporation for business verification.""" + """Optional. + + Place of incorporation (state/region); maps to the business address state. + """ restart: bool """Whether to restart an in-flight verification.""" tax_identification_number: str - """Tax ID for verification, such as an SSN for individuals or EIN for businesses. + """Optional. - Tokenized in transit and stored only on the profile. + Tax identification number — SSN for individuals, EIN for businesses. Tokenized + in transit, never stored raw; stored on the profile so the payout account, + provisioned on approval, doesn't raise a tax-id RFI. """ diff --git a/src/whop_sdk/types/verification_create_response.py b/src/whop_sdk/types/verification_create_response.py index 2b46ef7e..abb8e392 100644 --- a/src/whop_sdk/types/verification_create_response.py +++ b/src/whop_sdk/types/verification_create_response.py @@ -5,88 +5,91 @@ from .._models import BaseModel -__all__ = ["VerificationCreateResponse", "Rfi", "RfiRequestedFile"] +__all__ = ["VerificationCreateResponse", "RequestedInformation", "RequestedInformationRequestedFile"] -class RfiRequestedFile(BaseModel): +class RequestedInformationRequestedFile(BaseModel): category: Optional[str] = None - """Provider document category.""" + """ + Identifier to send back with the uploaded file so it routes correctly; null for + a generic upload. + """ is_optional: Optional[bool] = None - """Whether this document can be omitted.""" + """Whether this slot can be left empty.""" kind: Optional[str] = None - """Document kind to upload when answering the RFI.""" + """Provider-specific document kind, when applicable.""" + label: Optional[str] = None + """Label for this upload slot (e.g. "Front of ID Document").""" -class Rfi(BaseModel): - id: Optional[str] = None - """RFI ID to send when answering this request.""" + multiple: Optional[bool] = None + """Whether this slot accepts more than one file.""" - created_at: Optional[str] = None - """When the RFI was created.""" + +class RequestedInformation(BaseModel): + id: Optional[str] = None + """The requested information item id (inrqi\\__\\**). Use this when answering.""" description: Optional[str] = None - """Request text from verification provider.""" + """Additional guidance for the field beyond the label.""" error_message: Optional[str] = None - """Provider error for invalid response, if any.""" + """The reason a previously submitted value was rejected, or null.""" - requested_files: Optional[List[RfiRequestedFile]] = None - """Documents requested for a file-upload RFI.""" + field: Optional[str] = None + """Stable snake_case key for the field (e.g. ssn, business_description).""" - status: Optional[Literal["outstanding", "invalid"]] = None - """RFI status.""" + label: Optional[str] = None + """Human-readable label for the field (e.g. "Social Security Number").""" + + requested_files: Optional[List[RequestedInformationRequestedFile]] = None + """ + Upload slots for a files item — always at least one when type is `files`, empty + otherwise. + """ type: Optional[str] = None - """Expected answer type for this RFI.""" + """How to render the input: text, date, phone, address, or files.""" class VerificationCreateResponse(BaseModel): id: Optional[str] = None - """Verification ID, prefixed `idpf_`.""" + """The verification ID, e.g. idpf\\__\\**""" address: Optional[object] = None - """Address associated with the verification profile.""" business_name: Optional[str] = None - """Legal business name for business verification.""" business_structure: Optional[str] = None - """Business entity structure, such as `llc` or `corporation`.""" country: Optional[str] = None - """Two-letter ISO 3166-1 country code reported by the identity provider.""" + """ISO 3166-1 alpha-2 country code (e.g. + + `US`, `GB`). For individuals this is the country of citizenship or residence + reported by the identity provider; for businesses this is the country of + incorporation. + """ created_at: Optional[str] = None - """When the verification profile was created.""" date_of_birth: Optional[str] = None - """Date of birth for individual verification.""" first_name: Optional[str] = None - """First name for individual verification.""" kind: Optional[Literal["individual", "business"]] = None - """Verification profile type.""" last_name: Optional[str] = None - """Last name for individual verification.""" - rfis: Optional[List[Rfi]] = None + requested_information: Optional[List[RequestedInformation]] = None """ - Outstanding or invalid requests for information that must be answered to - continue verification. + The outstanding information this verification still needs — payout RFIs and + audit RMIs, one uniform shape. """ session_url: Optional[str] = None - """Hosted provider session URL for pending verifications.""" status: Optional[Literal["not_started", "pending", "approved", "rejected", "action_required"]] = None - """Current verification status. - - `action_required` means one or more RFIs need a response. - """ updated_at: Optional[str] = None - """When the verification profile was last updated.""" diff --git a/src/whop_sdk/types/verification_delete_response.py b/src/whop_sdk/types/verification_delete_response.py index 8159b283..3448aef4 100644 --- a/src/whop_sdk/types/verification_delete_response.py +++ b/src/whop_sdk/types/verification_delete_response.py @@ -9,7 +9,5 @@ class VerificationDeleteResponse(BaseModel): id: Optional[str] = None - """Deleted verification ID.""" deleted: Optional[bool] = None - """Whether the verification was unlinked.""" diff --git a/src/whop_sdk/types/verification_list_response.py b/src/whop_sdk/types/verification_list_response.py index a3b10909..12954af0 100644 --- a/src/whop_sdk/types/verification_list_response.py +++ b/src/whop_sdk/types/verification_list_response.py @@ -5,93 +5,95 @@ from .._models import BaseModel -__all__ = ["VerificationListResponse", "Data", "DataRfi", "DataRfiRequestedFile"] +__all__ = ["VerificationListResponse", "Data", "DataRequestedInformation", "DataRequestedInformationRequestedFile"] -class DataRfiRequestedFile(BaseModel): +class DataRequestedInformationRequestedFile(BaseModel): category: Optional[str] = None - """Provider document category.""" + """ + Identifier to send back with the uploaded file so it routes correctly; null for + a generic upload. + """ is_optional: Optional[bool] = None - """Whether this document can be omitted.""" + """Whether this slot can be left empty.""" kind: Optional[str] = None - """Document kind to upload when answering the RFI.""" + """Provider-specific document kind, when applicable.""" + label: Optional[str] = None + """Label for this upload slot (e.g. "Front of ID Document").""" -class DataRfi(BaseModel): - id: Optional[str] = None - """RFI ID to send when answering this request.""" + multiple: Optional[bool] = None + """Whether this slot accepts more than one file.""" - created_at: Optional[str] = None - """When the RFI was created.""" + +class DataRequestedInformation(BaseModel): + id: Optional[str] = None + """The requested information item id (inrqi\\__\\**). Use this when answering.""" description: Optional[str] = None - """Request text from verification provider.""" + """Additional guidance for the field beyond the label.""" error_message: Optional[str] = None - """Provider error for invalid response, if any.""" + """The reason a previously submitted value was rejected, or null.""" - requested_files: Optional[List[DataRfiRequestedFile]] = None - """Documents requested for a file-upload RFI.""" + field: Optional[str] = None + """Stable snake_case key for the field (e.g. ssn, business_description).""" - status: Optional[Literal["outstanding", "invalid"]] = None - """RFI status.""" + label: Optional[str] = None + """Human-readable label for the field (e.g. "Social Security Number").""" + + requested_files: Optional[List[DataRequestedInformationRequestedFile]] = None + """ + Upload slots for a files item — always at least one when type is `files`, empty + otherwise. + """ type: Optional[str] = None - """Expected answer type for this RFI.""" + """How to render the input: text, date, phone, address, or files.""" class Data(BaseModel): id: Optional[str] = None - """Verification ID, prefixed `idpf_`.""" + """The verification ID, e.g. idpf\\__\\**""" address: Optional[object] = None - """Address associated with the verification profile.""" business_name: Optional[str] = None - """Legal business name for business verification.""" business_structure: Optional[str] = None - """Business entity structure, such as `llc` or `corporation`.""" country: Optional[str] = None - """Two-letter ISO 3166-1 country code reported by the identity provider.""" + """ISO 3166-1 alpha-2 country code (e.g. + + `US`, `GB`). For individuals this is the country of citizenship or residence + reported by the identity provider; for businesses this is the country of + incorporation. + """ created_at: Optional[str] = None - """When the verification profile was created.""" date_of_birth: Optional[str] = None - """Date of birth for individual verification.""" first_name: Optional[str] = None - """First name for individual verification.""" kind: Optional[Literal["individual", "business"]] = None - """Verification profile type.""" last_name: Optional[str] = None - """Last name for individual verification.""" - rfis: Optional[List[DataRfi]] = None + requested_information: Optional[List[DataRequestedInformation]] = None """ - Outstanding or invalid requests for information that must be answered to - continue verification. + The outstanding information this verification still needs — payout RFIs and + audit RMIs, one uniform shape. """ session_url: Optional[str] = None - """Hosted provider session URL for pending verifications.""" status: Optional[Literal["not_started", "pending", "approved", "rejected", "action_required"]] = None - """Current verification status. - - `action_required` means one or more RFIs need a response. - """ updated_at: Optional[str] = None - """When the verification profile was last updated.""" class VerificationListResponse(BaseModel): data: Optional[List[Data]] = None - """Verification profiles for this account.""" diff --git a/src/whop_sdk/types/verification_retrieve_response.py b/src/whop_sdk/types/verification_retrieve_response.py index 2e284616..b03d73b4 100644 --- a/src/whop_sdk/types/verification_retrieve_response.py +++ b/src/whop_sdk/types/verification_retrieve_response.py @@ -5,88 +5,91 @@ from .._models import BaseModel -__all__ = ["VerificationRetrieveResponse", "Rfi", "RfiRequestedFile"] +__all__ = ["VerificationRetrieveResponse", "RequestedInformation", "RequestedInformationRequestedFile"] -class RfiRequestedFile(BaseModel): +class RequestedInformationRequestedFile(BaseModel): category: Optional[str] = None - """Provider document category.""" + """ + Identifier to send back with the uploaded file so it routes correctly; null for + a generic upload. + """ is_optional: Optional[bool] = None - """Whether this document can be omitted.""" + """Whether this slot can be left empty.""" kind: Optional[str] = None - """Document kind to upload when answering the RFI.""" + """Provider-specific document kind, when applicable.""" + label: Optional[str] = None + """Label for this upload slot (e.g. "Front of ID Document").""" -class Rfi(BaseModel): - id: Optional[str] = None - """RFI ID to send when answering this request.""" + multiple: Optional[bool] = None + """Whether this slot accepts more than one file.""" - created_at: Optional[str] = None - """When the RFI was created.""" + +class RequestedInformation(BaseModel): + id: Optional[str] = None + """The requested information item id (inrqi\\__\\**). Use this when answering.""" description: Optional[str] = None - """Request text from verification provider.""" + """Additional guidance for the field beyond the label.""" error_message: Optional[str] = None - """Provider error for invalid response, if any.""" + """The reason a previously submitted value was rejected, or null.""" - requested_files: Optional[List[RfiRequestedFile]] = None - """Documents requested for a file-upload RFI.""" + field: Optional[str] = None + """Stable snake_case key for the field (e.g. ssn, business_description).""" - status: Optional[Literal["outstanding", "invalid"]] = None - """RFI status.""" + label: Optional[str] = None + """Human-readable label for the field (e.g. "Social Security Number").""" + + requested_files: Optional[List[RequestedInformationRequestedFile]] = None + """ + Upload slots for a files item — always at least one when type is `files`, empty + otherwise. + """ type: Optional[str] = None - """Expected answer type for this RFI.""" + """How to render the input: text, date, phone, address, or files.""" class VerificationRetrieveResponse(BaseModel): id: Optional[str] = None - """Verification ID, prefixed `idpf_`.""" + """The verification ID, e.g. idpf\\__\\**""" address: Optional[object] = None - """Address associated with the verification profile.""" business_name: Optional[str] = None - """Legal business name for business verification.""" business_structure: Optional[str] = None - """Business entity structure, such as `llc` or `corporation`.""" country: Optional[str] = None - """Two-letter ISO 3166-1 country code reported by the identity provider.""" + """ISO 3166-1 alpha-2 country code (e.g. + + `US`, `GB`). For individuals this is the country of citizenship or residence + reported by the identity provider; for businesses this is the country of + incorporation. + """ created_at: Optional[str] = None - """When the verification profile was created.""" date_of_birth: Optional[str] = None - """Date of birth for individual verification.""" first_name: Optional[str] = None - """First name for individual verification.""" kind: Optional[Literal["individual", "business"]] = None - """Verification profile type.""" last_name: Optional[str] = None - """Last name for individual verification.""" - rfis: Optional[List[Rfi]] = None + requested_information: Optional[List[RequestedInformation]] = None """ - Outstanding or invalid requests for information that must be answered to - continue verification. + The outstanding information this verification still needs — payout RFIs and + audit RMIs, one uniform shape. """ session_url: Optional[str] = None - """Hosted provider session URL for pending verifications.""" status: Optional[Literal["not_started", "pending", "approved", "rejected", "action_required"]] = None - """Current verification status. - - `action_required` means one or more RFIs need a response. - """ updated_at: Optional[str] = None - """When the verification profile was last updated.""" diff --git a/src/whop_sdk/types/verification_update_params.py b/src/whop_sdk/types/verification_update_params.py index e046f4c6..41b64f0a 100644 --- a/src/whop_sdk/types/verification_update_params.py +++ b/src/whop_sdk/types/verification_update_params.py @@ -5,53 +5,53 @@ from typing import Dict, Iterable from typing_extensions import Literal, Required, TypedDict -__all__ = ["VerificationUpdateParams", "Rfi"] +__all__ = ["VerificationUpdateParams", "RequestedInformation"] class VerificationUpdateParams(TypedDict, total=False): business_address: Dict[str, object] - """Business address to submit for verification.""" + """The business address.""" business_name: str - """Legal business name to submit for verification.""" + """The business name.""" business_structure: str - """Business entity structure to submit for verification.""" + """The business structure.""" country: str - """Country code to submit for verification.""" + """The country code.""" date_of_birth: str - """Date of birth to submit for individual verification.""" + """The date of birth.""" first_name: str - """First name to submit for individual verification.""" + """The first name on the verification.""" last_name: str - """Last name to submit for individual verification.""" + """The last name on the verification.""" personal_address: Dict[str, object] - """Personal address to submit for individual verification.""" + """The personal address.""" - rfis: Iterable[Rfi] - """Responses to outstanding RFIs. + requested_information: Iterable[RequestedInformation] + """Answers to requested information. - Each entry must include an RFI ID and a value, address, or files payload. + Each entry must include id and a value, address, or files payload. """ -class Rfi(TypedDict, total=False): +class RequestedInformation(TypedDict, total=False): id: Required[str] - """RFI ID being answered.""" + """The requested information item id (inrqi\\__\\**).""" address: Dict[str, object] - """Address payload for address RFIs.""" + """Address payload for address items.""" files: Iterable[object] - """File upload payload for document RFIs.""" + """File upload payload for document items.""" value: str - """Answer value for text, date, or phone RFIs.""" + """The value for text/date/phone items.""" value_type: Literal["raw", "vault_token"] - """How the answer value is encoded. Defaults to `raw`.""" + """Defaults to the field's configured type.""" diff --git a/src/whop_sdk/types/verification_update_response.py b/src/whop_sdk/types/verification_update_response.py index 7737fc7f..1439f67c 100644 --- a/src/whop_sdk/types/verification_update_response.py +++ b/src/whop_sdk/types/verification_update_response.py @@ -5,88 +5,91 @@ from .._models import BaseModel -__all__ = ["VerificationUpdateResponse", "Rfi", "RfiRequestedFile"] +__all__ = ["VerificationUpdateResponse", "RequestedInformation", "RequestedInformationRequestedFile"] -class RfiRequestedFile(BaseModel): +class RequestedInformationRequestedFile(BaseModel): category: Optional[str] = None - """Provider document category.""" + """ + Identifier to send back with the uploaded file so it routes correctly; null for + a generic upload. + """ is_optional: Optional[bool] = None - """Whether this document can be omitted.""" + """Whether this slot can be left empty.""" kind: Optional[str] = None - """Document kind to upload when answering the RFI.""" + """Provider-specific document kind, when applicable.""" + label: Optional[str] = None + """Label for this upload slot (e.g. "Front of ID Document").""" -class Rfi(BaseModel): - id: Optional[str] = None - """RFI ID to send when answering this request.""" + multiple: Optional[bool] = None + """Whether this slot accepts more than one file.""" - created_at: Optional[str] = None - """When the RFI was created.""" + +class RequestedInformation(BaseModel): + id: Optional[str] = None + """The requested information item id (inrqi\\__\\**). Use this when answering.""" description: Optional[str] = None - """Request text from verification provider.""" + """Additional guidance for the field beyond the label.""" error_message: Optional[str] = None - """Provider error for invalid response, if any.""" + """The reason a previously submitted value was rejected, or null.""" - requested_files: Optional[List[RfiRequestedFile]] = None - """Documents requested for a file-upload RFI.""" + field: Optional[str] = None + """Stable snake_case key for the field (e.g. ssn, business_description).""" - status: Optional[Literal["outstanding", "invalid"]] = None - """RFI status.""" + label: Optional[str] = None + """Human-readable label for the field (e.g. "Social Security Number").""" + + requested_files: Optional[List[RequestedInformationRequestedFile]] = None + """ + Upload slots for a files item — always at least one when type is `files`, empty + otherwise. + """ type: Optional[str] = None - """Expected answer type for this RFI.""" + """How to render the input: text, date, phone, address, or files.""" class VerificationUpdateResponse(BaseModel): id: Optional[str] = None - """Verification ID, prefixed `idpf_`.""" + """The verification ID, e.g. idpf\\__\\**""" address: Optional[object] = None - """Address associated with the verification profile.""" business_name: Optional[str] = None - """Legal business name for business verification.""" business_structure: Optional[str] = None - """Business entity structure, such as `llc` or `corporation`.""" country: Optional[str] = None - """Two-letter ISO 3166-1 country code reported by the identity provider.""" + """ISO 3166-1 alpha-2 country code (e.g. + + `US`, `GB`). For individuals this is the country of citizenship or residence + reported by the identity provider; for businesses this is the country of + incorporation. + """ created_at: Optional[str] = None - """When the verification profile was created.""" date_of_birth: Optional[str] = None - """Date of birth for individual verification.""" first_name: Optional[str] = None - """First name for individual verification.""" kind: Optional[Literal["individual", "business"]] = None - """Verification profile type.""" last_name: Optional[str] = None - """Last name for individual verification.""" - rfis: Optional[List[Rfi]] = None + requested_information: Optional[List[RequestedInformation]] = None """ - Outstanding or invalid requests for information that must be answered to - continue verification. + The outstanding information this verification still needs — payout RFIs and + audit RMIs, one uniform shape. """ session_url: Optional[str] = None - """Hosted provider session URL for pending verifications.""" status: Optional[Literal["not_started", "pending", "approved", "rejected", "action_required"]] = None - """Current verification status. - - `action_required` means one or more RFIs need a response. - """ updated_at: Optional[str] = None - """When the verification profile was last updated.""" diff --git a/tests/api_resources/test_verifications.py b/tests/api_resources/test_verifications.py index 1cb41c09..6be8f3ae 100644 --- a/tests/api_resources/test_verifications.py +++ b/tests/api_resources/test_verifications.py @@ -141,7 +141,7 @@ def test_method_update_with_all_params(self, client: Whop) -> None: first_name="first_name", last_name="last_name", personal_address={"foo": "bar"}, - rfis=[ + requested_information=[ { "id": "id", "address": {"foo": "bar"}, @@ -387,7 +387,7 @@ async def test_method_update_with_all_params(self, async_client: AsyncWhop) -> N first_name="first_name", last_name="last_name", personal_address={"foo": "bar"}, - rfis=[ + requested_information=[ { "id": "id", "address": {"foo": "bar"}, From 7b1de4b04a0948215f0f37228512b09701669c33 Mon Sep 17 00:00:00 2001 From: stlc-bot Date: Sat, 27 Jun 2026 05:39:41 +0000 Subject: [PATCH 083/109] fix(ads): complete result counts e2e ENG-23970 ENG-23941 Stainless-Generated-From: 818fb5c6696eacb9a99e5694cd6396f4b60fe702 --- src/whop_sdk/types/ad.py | 60 +++++++++++++++++++++++++++ src/whop_sdk/types/ad_campaign.py | 69 +++++++++++++++++++++++++++++-- src/whop_sdk/types/ad_group.py | 60 +++++++++++++++++++++++++++ 3 files changed, 186 insertions(+), 3 deletions(-) diff --git a/src/whop_sdk/types/ad.py b/src/whop_sdk/types/ad.py index 8e64293f..be0f0275 100644 --- a/src/whop_sdk/types/ad.py +++ b/src/whop_sdk/types/ad.py @@ -34,6 +34,9 @@ class Ad(BaseModel): ad_group: object """The ad group this ad belongs to, an object with an id.""" + added_to_carts: float + """Whop pixel-attributed add-to-cart events, last-click.""" + call_to_action: Optional[ Literal[ "learn_more", @@ -77,9 +80,33 @@ class Ad(BaseModel): clicks: float """The number of clicks.""" + completed_registrations: float + """Whop pixel-attributed complete-registration events, last-click.""" + + contacts: float + """Whop pixel-attributed contact events, last-click.""" + + cost_per_added_to_cart: Optional[float] = None + """ + Spend divided by attributed add-to-cart events; null when they are not the goal + and none are attributed. + """ + cost_per_click: float """Spend divided by clicks; 0 when there are no clicks.""" + cost_per_completed_registration: Optional[float] = None + """ + Spend divided by attributed complete-registration events; null when they are not + the goal and none are attributed. + """ + + cost_per_contact: Optional[float] = None + """ + Spend divided by attributed contact events; null when contacts are not the goal + and none are attributed. + """ + cost_per_lead: Optional[float] = None """ Spend divided by attributed leads; null when leads are not a goal and none are @@ -95,11 +122,35 @@ class Ad(BaseModel): none are attributed. """ + cost_per_schedule: Optional[float] = None + """ + Spend divided by attributed schedule events; null when schedules are not the + goal and none are attributed. + """ + + cost_per_submitted_application: Optional[float] = None + """ + Spend divided by attributed submit-application events; null when they are not + the goal and none are attributed. + """ + + cost_per_viewed_content: Optional[float] = None + """ + Spend divided by attributed view-content events; null when they are not the goal + and none are attributed. + """ + created_at: str """When the ad was created, as an ISO 8601 timestamp.""" creatives: List[object] + custom_conversions: float + """ + Whop pixel-attributed custom (merchant-defined) conversion events, last-click, + across all custom event names. + """ + descriptions: List[str] frequency: Optional[float] = None @@ -129,6 +180,9 @@ class Ad(BaseModel): return_on_ad_spend: float """Purchase value divided by spend; 0 when there is no spend.""" + schedules: float + """Whop pixel-attributed schedule events, last-click.""" + social_accounts: List[object] spend: float @@ -140,6 +194,9 @@ class Ad(BaseModel): status: Literal["active", "paused", "in_review", "rejected"] """The delivery status of the ad.""" + submitted_applications: float + """Whop pixel-attributed submit-application events, last-click.""" + title: Optional[str] = None """The display title of the ad. Falls back to the creative set caption when unset.""" @@ -157,3 +214,6 @@ class Ad(BaseModel): url_parameters: object """Query parameters appended to the URL, as a string-to-string map.""" + + viewed_contents: float + """Whop pixel-attributed view-content events, last-click.""" diff --git a/src/whop_sdk/types/ad_campaign.py b/src/whop_sdk/types/ad_campaign.py index f54443fe..84902b72 100644 --- a/src/whop_sdk/types/ad_campaign.py +++ b/src/whop_sdk/types/ad_campaign.py @@ -28,6 +28,9 @@ class AdCampaign(BaseModel): id: str """Unique identifier for the ad campaign.""" + added_to_carts: float + """Whop pixel-attributed add-to-cart events, last-click.""" + bid_type: Optional[Literal["minimum_cost", "average_target", "maximum_target"]] = None """The bidding strategy the campaign uses.""" @@ -47,11 +50,35 @@ class AdCampaign(BaseModel): """Clicks divided by impressions, between 0 and 1.""" clicks: float - """The number of clicks for all ads within this campaign.""" + """The number of clicks.""" + + completed_registrations: float + """Whop pixel-attributed complete-registration events, last-click.""" + + contacts: float + """Whop pixel-attributed contact events, last-click.""" + + cost_per_added_to_cart: Optional[float] = None + """ + Spend divided by attributed add-to-cart events; null when they are not the goal + and none are attributed. + """ cost_per_click: float """Spend divided by clicks; 0 when there are no clicks.""" + cost_per_completed_registration: Optional[float] = None + """ + Spend divided by attributed complete-registration events; null when they are not + the goal and none are attributed. + """ + + cost_per_contact: Optional[float] = None + """ + Spend divided by attributed contact events; null when contacts are not the goal + and none are attributed. + """ + cost_per_lead: Optional[float] = None """ Spend divided by attributed leads; null when leads are not a goal and none are @@ -68,11 +95,38 @@ class AdCampaign(BaseModel): """ cost_per_result: Optional[float] = None - """Spend divided by results; null when nothing is being optimized for.""" + """ + Spend divided by Whop pixel-attributed results; null when nothing + Whop-attributable is being optimized for. + """ + + cost_per_schedule: Optional[float] = None + """ + Spend divided by attributed schedule events; null when schedules are not the + goal and none are attributed. + """ + + cost_per_submitted_application: Optional[float] = None + """ + Spend divided by attributed submit-application events; null when they are not + the goal and none are attributed. + """ + + cost_per_viewed_content: Optional[float] = None + """ + Spend divided by attributed view-content events; null when they are not the goal + and none are attributed. + """ created_at: str """When the campaign was created, as an ISO 8601 timestamp.""" + custom_conversions: float + """ + Whop pixel-attributed custom (merchant-defined) conversion events, last-click, + across all custom event names. + """ + frequency: Optional[float] = None """Platform-reported impressions divided by reach.""" @@ -104,11 +158,14 @@ class AdCampaign(BaseModel): """Whop pixel-attributed purchases, last-click.""" reach: float - """The number of unique people who saw an ad.""" + """The number of unique people who saw this.""" return_on_ad_spend: float """Purchase value divided by spend; 0 when there is no spend.""" + schedules: float + """Whop pixel-attributed schedule events, last-click.""" + special_ad_categories: List[Literal["housing", "employment", "financial_products", "politics"]] spend: float @@ -120,6 +177,9 @@ class AdCampaign(BaseModel): status: Literal["draft", "active", "paused", "payment_failed"] """The lifecycle status of the ad campaign.""" + submitted_applications: float + """Whop pixel-attributed submit-application events, last-click.""" + title: str """The title of the ad campaign.""" @@ -131,3 +191,6 @@ class AdCampaign(BaseModel): updated_at: str """When the campaign was last updated, as an ISO 8601 timestamp.""" + + viewed_contents: float + """Whop pixel-attributed view-content events, last-click.""" diff --git a/src/whop_sdk/types/ad_group.py b/src/whop_sdk/types/ad_group.py index fb1d5894..028213e3 100644 --- a/src/whop_sdk/types/ad_group.py +++ b/src/whop_sdk/types/ad_group.py @@ -31,6 +31,9 @@ class AdGroup(BaseModel): ad_campaign: object """The ad campaign this ad group belongs to, an object with an id.""" + added_to_carts: float + """Whop pixel-attributed add-to-cart events, last-click.""" + audience: object """Demographic targeting: automatic (Advantage+), age range, gender.""" @@ -49,6 +52,12 @@ class AdGroup(BaseModel): clicks: float """The number of clicks.""" + completed_registrations: float + """Whop pixel-attributed complete-registration events, last-click.""" + + contacts: float + """Whop pixel-attributed contact events, last-click.""" + conversion_event: Union[ Literal[ "purchase", @@ -79,9 +88,27 @@ class AdGroup(BaseModel): conversion_location: Optional[Literal["website"]] = None """Where conversions happen.""" + cost_per_added_to_cart: Optional[float] = None + """ + Spend divided by attributed add-to-cart events; null when they are not the goal + and none are attributed. + """ + cost_per_click: float """Spend divided by clicks; 0 when there are no clicks.""" + cost_per_completed_registration: Optional[float] = None + """ + Spend divided by attributed complete-registration events; null when they are not + the goal and none are attributed. + """ + + cost_per_contact: Optional[float] = None + """ + Spend divided by attributed contact events; null when contacts are not the goal + and none are attributed. + """ + cost_per_lead: Optional[float] = None """ Spend divided by attributed leads; null when leads are not a goal and none are @@ -97,9 +124,33 @@ class AdGroup(BaseModel): none are attributed. """ + cost_per_schedule: Optional[float] = None + """ + Spend divided by attributed schedule events; null when schedules are not the + goal and none are attributed. + """ + + cost_per_submitted_application: Optional[float] = None + """ + Spend divided by attributed submit-application events; null when they are not + the goal and none are attributed. + """ + + cost_per_viewed_content: Optional[float] = None + """ + Spend divided by attributed view-content events; null when they are not the goal + and none are attributed. + """ + created_at: str """When the ad group was created, ISO 8601.""" + custom_conversions: float + """ + Whop pixel-attributed custom (merchant-defined) conversion events, last-click, + across all custom event names. + """ + desired_cost_per_result: Optional[float] = None """Target/cap cost for average_target / maximum_target.""" @@ -146,6 +197,9 @@ class AdGroup(BaseModel): return_on_ad_spend: float """Purchase value divided by spend; 0 when there is no spend.""" + schedules: float + """Whop pixel-attributed schedule events, last-click.""" + spend: float """The amount charged, in spend_currency.""" @@ -158,6 +212,9 @@ class AdGroup(BaseModel): status: Literal["active", "paused", "rejected"] """Delivery status of the ad group.""" + submitted_applications: float + """Whop pixel-attributed submit-application events, last-click.""" + title: Optional[str] = None """The display title of the ad group.""" @@ -169,3 +226,6 @@ class AdGroup(BaseModel): updated_at: str """When the ad group was last updated, ISO 8601.""" + + viewed_contents: float + """Whop pixel-attributed view-content events, last-click.""" From b9a960a797e1e32ea6c6fae48b05320586037626 Mon Sep 17 00:00:00 2001 From: stlc-bot Date: Sat, 27 Jun 2026 07:14:42 +0000 Subject: [PATCH 084/109] Add custom audience uploads for ads Stainless-Generated-From: cc1605716bbb44c1333ca8f9c6449010e1f94c78 --- .stats.yml | 2 +- api.md | 14 + src/whop_sdk/_client.py | 38 ++ src/whop_sdk/resources/__init__.py | 14 + src/whop_sdk/resources/audiences.py | 411 ++++++++++++++++++ src/whop_sdk/types/__init__.py | 4 + src/whop_sdk/types/audience.py | 42 ++ src/whop_sdk/types/audience_create_params.py | 25 ++ .../types/audience_delete_response.py | 9 + src/whop_sdk/types/audience_list_params.py | 27 ++ tests/api_resources/test_audiences.py | 285 ++++++++++++ 11 files changed, 870 insertions(+), 1 deletion(-) create mode 100644 src/whop_sdk/resources/audiences.py create mode 100644 src/whop_sdk/types/audience.py create mode 100644 src/whop_sdk/types/audience_create_params.py create mode 100644 src/whop_sdk/types/audience_delete_response.py create mode 100644 src/whop_sdk/types/audience_list_params.py create mode 100644 tests/api_resources/test_audiences.py diff --git a/.stats.yml b/.stats.yml index 090fb0bc..db503358 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1 +1 @@ -configured_endpoints: 251 +configured_endpoints: 254 diff --git a/api.md b/api.md index c0ff5d27..6ab6faac 100644 --- a/api.md +++ b/api.md @@ -146,6 +146,20 @@ Methods: - client.social_accounts.delete(id, \*\*params) -> SocialAccountDeleteResponse - client.social_accounts.posts(id, \*\*params) -> SocialAccountPostsResponse +# Audiences + +Types: + +```python +from whop_sdk.types import Audience, AudienceDeleteResponse +``` + +Methods: + +- client.audiences.create(\*\*params) -> Audience +- client.audiences.list(\*\*params) -> SyncCursorPage[Audience] +- client.audiences.delete(audience_id) -> AudienceDeleteResponse + # Companies Types: diff --git a/src/whop_sdk/_client.py b/src/whop_sdk/_client.py index ff0d4a66..c6cbe6ad 100644 --- a/src/whop_sdk/_client.py +++ b/src/whop_sdk/_client.py @@ -63,6 +63,7 @@ products, webhooks, ad_groups, + audiences, companies, reactions, referrals, @@ -131,6 +132,7 @@ from .resources.products import ProductsResource, AsyncProductsResource from .resources.webhooks import WebhooksResource, AsyncWebhooksResource from .resources.ad_groups import AdGroupsResource, AsyncAdGroupsResource + from .resources.audiences import AudiencesResource, AsyncAudiencesResource from .resources.companies import CompaniesResource, AsyncCompaniesResource from .resources.reactions import ReactionsResource, AsyncReactionsResource from .resources.shipments import ShipmentsResource, AsyncShipmentsResource @@ -301,6 +303,12 @@ def social_accounts(self) -> SocialAccountsResource: return SocialAccountsResource(self) + @cached_property + def audiences(self) -> AudiencesResource: + from .resources.audiences import AudiencesResource + + return AudiencesResource(self) + @cached_property def companies(self) -> CompaniesResource: """Companies""" @@ -962,6 +970,12 @@ def social_accounts(self) -> AsyncSocialAccountsResource: return AsyncSocialAccountsResource(self) + @cached_property + def audiences(self) -> AsyncAudiencesResource: + from .resources.audiences import AsyncAudiencesResource + + return AsyncAudiencesResource(self) + @cached_property def companies(self) -> AsyncCompaniesResource: """Companies""" @@ -1543,6 +1557,12 @@ def social_accounts(self) -> social_accounts.SocialAccountsResourceWithRawRespon return SocialAccountsResourceWithRawResponse(self._client.social_accounts) + @cached_property + def audiences(self) -> audiences.AudiencesResourceWithRawResponse: + from .resources.audiences import AudiencesResourceWithRawResponse + + return AudiencesResourceWithRawResponse(self._client.audiences) + @cached_property def companies(self) -> companies.CompaniesResourceWithRawResponse: """Companies""" @@ -2006,6 +2026,12 @@ def social_accounts(self) -> social_accounts.AsyncSocialAccountsResourceWithRawR return AsyncSocialAccountsResourceWithRawResponse(self._client.social_accounts) + @cached_property + def audiences(self) -> audiences.AsyncAudiencesResourceWithRawResponse: + from .resources.audiences import AsyncAudiencesResourceWithRawResponse + + return AsyncAudiencesResourceWithRawResponse(self._client.audiences) + @cached_property def companies(self) -> companies.AsyncCompaniesResourceWithRawResponse: """Companies""" @@ -2471,6 +2497,12 @@ def social_accounts(self) -> social_accounts.SocialAccountsResourceWithStreaming return SocialAccountsResourceWithStreamingResponse(self._client.social_accounts) + @cached_property + def audiences(self) -> audiences.AudiencesResourceWithStreamingResponse: + from .resources.audiences import AudiencesResourceWithStreamingResponse + + return AudiencesResourceWithStreamingResponse(self._client.audiences) + @cached_property def companies(self) -> companies.CompaniesResourceWithStreamingResponse: """Companies""" @@ -2936,6 +2968,12 @@ def social_accounts(self) -> social_accounts.AsyncSocialAccountsResourceWithStre return AsyncSocialAccountsResourceWithStreamingResponse(self._client.social_accounts) + @cached_property + def audiences(self) -> audiences.AsyncAudiencesResourceWithStreamingResponse: + from .resources.audiences import AsyncAudiencesResourceWithStreamingResponse + + return AsyncAudiencesResourceWithStreamingResponse(self._client.audiences) + @cached_property def companies(self) -> companies.AsyncCompaniesResourceWithStreamingResponse: """Companies""" diff --git a/src/whop_sdk/resources/__init__.py b/src/whop_sdk/resources/__init__.py index e3b4f707..224978f6 100644 --- a/src/whop_sdk/resources/__init__.py +++ b/src/whop_sdk/resources/__init__.py @@ -216,6 +216,14 @@ AdGroupsResourceWithStreamingResponse, AsyncAdGroupsResourceWithStreamingResponse, ) +from .audiences import ( + AudiencesResource, + AsyncAudiencesResource, + AudiencesResourceWithRawResponse, + AsyncAudiencesResourceWithRawResponse, + AudiencesResourceWithStreamingResponse, + AsyncAudiencesResourceWithStreamingResponse, +) from .companies import ( CompaniesResource, AsyncCompaniesResource, @@ -568,6 +576,12 @@ "AsyncSocialAccountsResourceWithRawResponse", "SocialAccountsResourceWithStreamingResponse", "AsyncSocialAccountsResourceWithStreamingResponse", + "AudiencesResource", + "AsyncAudiencesResource", + "AudiencesResourceWithRawResponse", + "AsyncAudiencesResourceWithRawResponse", + "AudiencesResourceWithStreamingResponse", + "AsyncAudiencesResourceWithStreamingResponse", "CompaniesResource", "AsyncCompaniesResource", "CompaniesResourceWithRawResponse", diff --git a/src/whop_sdk/resources/audiences.py b/src/whop_sdk/resources/audiences.py new file mode 100644 index 00000000..0c173533 --- /dev/null +++ b/src/whop_sdk/resources/audiences.py @@ -0,0 +1,411 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Dict + +import httpx + +from ..types import audience_list_params, audience_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.audience import Audience +from ..types.audience_delete_response import AudienceDeleteResponse + +__all__ = ["AudiencesResource", "AsyncAudiencesResource"] + + +class AudiencesResource(SyncAPIResource): + @cached_property + def with_raw_response(self) -> AudiencesResourceWithRawResponse: + """ + 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 AudiencesResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AudiencesResourceWithStreamingResponse: + """ + 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 AudiencesResourceWithStreamingResponse(self) + + def create( + self, + *, + account_id: str, + column_mapping: Dict[str, str], + file_id: str, + name: 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, + ) -> Audience: + """ + Creates a custom audience from an uploaded CSV file and starts processing it. + + Args: + account_id: The ID of the account that will own the audience. + + column_mapping: Map of identity field (email, phone, first_name, last_name, country) to the CSV + column header that holds it. Map at least an email or phone column. + + file_id: A direct upload ID returned by the standard media upload endpoint. + + name: A display name for the audience. + + 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( + "/audiences", + body=maybe_transform( + { + "account_id": account_id, + "column_mapping": column_mapping, + "file_id": file_id, + "name": name, + }, + audience_create_params.AudienceCreateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=Audience, + ) + + def list( + self, + *, + account_id: str, + after: str | Omit = omit, + audience_id: str | Omit = omit, + first: int | 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[Audience]: + """ + Lists the custom audiences (uploaded CSV customer lists) for an account. + + Args: + account_id: The ID of the account that owns the audiences, which will look like + biz\\__******\\********. + + after: A cursor; returns audiences after this position. + + audience_id: Optional audience ID to filter the response to one audience, which will look + like adaud\\__******\\********. + + first: The number of audiences to return (default 20, max 100). + + 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( + "/audiences", + page=SyncCursorPage[Audience], + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "account_id": account_id, + "after": after, + "audience_id": audience_id, + "first": first, + }, + audience_list_params.AudienceListParams, + ), + ), + model=Audience, + ) + + def delete( + self, + audience_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, + ) -> AudienceDeleteResponse: + """ + Deletes (soft-discards) a custom audience. + + 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 audience_id: + raise ValueError(f"Expected a non-empty value for `audience_id` but received {audience_id!r}") + return self._delete( + path_template("/audiences/{audience_id}", audience_id=audience_id), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=AudienceDeleteResponse, + ) + + +class AsyncAudiencesResource(AsyncAPIResource): + @cached_property + def with_raw_response(self) -> AsyncAudiencesResourceWithRawResponse: + """ + 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 AsyncAudiencesResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncAudiencesResourceWithStreamingResponse: + """ + 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 AsyncAudiencesResourceWithStreamingResponse(self) + + async def create( + self, + *, + account_id: str, + column_mapping: Dict[str, str], + file_id: str, + name: 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, + ) -> Audience: + """ + Creates a custom audience from an uploaded CSV file and starts processing it. + + Args: + account_id: The ID of the account that will own the audience. + + column_mapping: Map of identity field (email, phone, first_name, last_name, country) to the CSV + column header that holds it. Map at least an email or phone column. + + file_id: A direct upload ID returned by the standard media upload endpoint. + + name: A display name for the audience. + + 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( + "/audiences", + body=await async_maybe_transform( + { + "account_id": account_id, + "column_mapping": column_mapping, + "file_id": file_id, + "name": name, + }, + audience_create_params.AudienceCreateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=Audience, + ) + + def list( + self, + *, + account_id: str, + after: str | Omit = omit, + audience_id: str | Omit = omit, + first: int | 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[Audience, AsyncCursorPage[Audience]]: + """ + Lists the custom audiences (uploaded CSV customer lists) for an account. + + Args: + account_id: The ID of the account that owns the audiences, which will look like + biz\\__******\\********. + + after: A cursor; returns audiences after this position. + + audience_id: Optional audience ID to filter the response to one audience, which will look + like adaud\\__******\\********. + + first: The number of audiences to return (default 20, max 100). + + 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( + "/audiences", + page=AsyncCursorPage[Audience], + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "account_id": account_id, + "after": after, + "audience_id": audience_id, + "first": first, + }, + audience_list_params.AudienceListParams, + ), + ), + model=Audience, + ) + + async def delete( + self, + audience_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, + ) -> AudienceDeleteResponse: + """ + Deletes (soft-discards) a custom audience. + + 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 audience_id: + raise ValueError(f"Expected a non-empty value for `audience_id` but received {audience_id!r}") + return await self._delete( + path_template("/audiences/{audience_id}", audience_id=audience_id), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=AudienceDeleteResponse, + ) + + +class AudiencesResourceWithRawResponse: + def __init__(self, audiences: AudiencesResource) -> None: + self._audiences = audiences + + self.create = to_raw_response_wrapper( + audiences.create, + ) + self.list = to_raw_response_wrapper( + audiences.list, + ) + self.delete = to_raw_response_wrapper( + audiences.delete, + ) + + +class AsyncAudiencesResourceWithRawResponse: + def __init__(self, audiences: AsyncAudiencesResource) -> None: + self._audiences = audiences + + self.create = async_to_raw_response_wrapper( + audiences.create, + ) + self.list = async_to_raw_response_wrapper( + audiences.list, + ) + self.delete = async_to_raw_response_wrapper( + audiences.delete, + ) + + +class AudiencesResourceWithStreamingResponse: + def __init__(self, audiences: AudiencesResource) -> None: + self._audiences = audiences + + self.create = to_streamed_response_wrapper( + audiences.create, + ) + self.list = to_streamed_response_wrapper( + audiences.list, + ) + self.delete = to_streamed_response_wrapper( + audiences.delete, + ) + + +class AsyncAudiencesResourceWithStreamingResponse: + def __init__(self, audiences: AsyncAudiencesResource) -> None: + self._audiences = audiences + + self.create = async_to_streamed_response_wrapper( + audiences.create, + ) + self.list = async_to_streamed_response_wrapper( + audiences.list, + ) + self.delete = async_to_streamed_response_wrapper( + audiences.delete, + ) diff --git a/src/whop_sdk/types/__init__.py b/src/whop_sdk/types/__init__.py index e344364e..e8e4d43a 100644 --- a/src/whop_sdk/types/__init__.py +++ b/src/whop_sdk/types/__init__.py @@ -73,6 +73,7 @@ from .webhook import Webhook as Webhook from .ad_group import AdGroup as AdGroup from .app_type import AppType as AppType +from .audience import Audience as Audience from .affiliate import Affiliate as Affiliate from .dm_member import DmMember as DmMember from .languages import Languages as Languages @@ -164,6 +165,7 @@ 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 .audience_list_params import AudienceListParams as AudienceListParams from .bounty_create_params import BountyCreateParams as BountyCreateParams from .bounty_list_response import BountyListResponse as BountyListResponse from .card_create_response import CardCreateResponse as CardCreateResponse @@ -221,6 +223,7 @@ 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 .audience_create_params import AudienceCreateParams as AudienceCreateParams from .bounty_create_response import BountyCreateResponse as BountyCreateResponse from .card_retrieve_response import CardRetrieveResponse as CardRetrieveResponse from .course_delete_response import CourseDeleteResponse as CourseDeleteResponse @@ -261,6 +264,7 @@ from .webhook_create_response import WebhookCreateResponse as WebhookCreateResponse from .webhook_delete_response import WebhookDeleteResponse as WebhookDeleteResponse from .ad_group_delete_response import AdGroupDeleteResponse as AdGroupDeleteResponse +from .audience_delete_response import AudienceDeleteResponse as AudienceDeleteResponse 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 diff --git a/src/whop_sdk/types/audience.py b/src/whop_sdk/types/audience.py new file mode 100644 index 00000000..bb1a1686 --- /dev/null +++ b/src/whop_sdk/types/audience.py @@ -0,0 +1,42 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Optional + +from .._models import BaseModel + +__all__ = ["Audience"] + + +class Audience(BaseModel): + id: str + """The ID of the audience, which will look like adaud\\__******\\********""" + + created_at: float + """When the audience was created, as a Unix timestamp""" + + error_message: Optional[str] = None + """Populated when the audience is partial or failed""" + + matched_rows: float + """Rows uploaded to the ad platform""" + + name: str + """The display name of the audience""" + + platform_audience_ids: List[object] + """External ad-platform audience IDs created for this audience""" + + processed_rows: float + """Rows ingested so far""" + + progress_percent: float + """Processing progress from 0 to 100""" + + status: str + """Processing status: pending, processing, syncing, ready, partial, or failed""" + + total_rows: float + """Total data rows detected in the uploaded CSV""" + + updated_at: float + """When the audience was last updated, as a Unix timestamp""" diff --git a/src/whop_sdk/types/audience_create_params.py b/src/whop_sdk/types/audience_create_params.py new file mode 100644 index 00000000..dea64354 --- /dev/null +++ b/src/whop_sdk/types/audience_create_params.py @@ -0,0 +1,25 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Dict +from typing_extensions import Required, TypedDict + +__all__ = ["AudienceCreateParams"] + + +class AudienceCreateParams(TypedDict, total=False): + account_id: Required[str] + """The ID of the account that will own the audience.""" + + column_mapping: Required[Dict[str, str]] + """ + Map of identity field (email, phone, first_name, last_name, country) to the CSV + column header that holds it. Map at least an email or phone column. + """ + + file_id: Required[str] + """A direct upload ID returned by the standard media upload endpoint.""" + + name: Required[str] + """A display name for the audience.""" diff --git a/src/whop_sdk/types/audience_delete_response.py b/src/whop_sdk/types/audience_delete_response.py new file mode 100644 index 00000000..6d8b5978 --- /dev/null +++ b/src/whop_sdk/types/audience_delete_response.py @@ -0,0 +1,9 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from .._models import BaseModel + +__all__ = ["AudienceDeleteResponse"] + + +class AudienceDeleteResponse(BaseModel): + success: bool diff --git a/src/whop_sdk/types/audience_list_params.py b/src/whop_sdk/types/audience_list_params.py new file mode 100644 index 00000000..32b9cbd7 --- /dev/null +++ b/src/whop_sdk/types/audience_list_params.py @@ -0,0 +1,27 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Required, TypedDict + +__all__ = ["AudienceListParams"] + + +class AudienceListParams(TypedDict, total=False): + account_id: Required[str] + """ + The ID of the account that owns the audiences, which will look like + biz\\__******\\********. + """ + + after: str + """A cursor; returns audiences after this position.""" + + audience_id: str + """ + Optional audience ID to filter the response to one audience, which will look + like adaud\\__******\\********. + """ + + first: int + """The number of audiences to return (default 20, max 100).""" diff --git a/tests/api_resources/test_audiences.py b/tests/api_resources/test_audiences.py new file mode 100644 index 00000000..21646aa1 --- /dev/null +++ b/tests/api_resources/test_audiences.py @@ -0,0 +1,285 @@ +# 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 Audience, AudienceDeleteResponse +from whop_sdk.pagination import SyncCursorPage, AsyncCursorPage + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestAudiences: + 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: + audience = client.audiences.create( + account_id="account_id", + column_mapping={"foo": "string"}, + file_id="file_id", + name="name", + ) + assert_matches_type(Audience, audience, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_raw_response_create(self, client: Whop) -> None: + response = client.audiences.with_raw_response.create( + account_id="account_id", + column_mapping={"foo": "string"}, + file_id="file_id", + name="name", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + audience = response.parse() + assert_matches_type(Audience, audience, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_streaming_response_create(self, client: Whop) -> None: + with client.audiences.with_streaming_response.create( + account_id="account_id", + column_mapping={"foo": "string"}, + file_id="file_id", + name="name", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + audience = response.parse() + assert_matches_type(Audience, audience, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_list(self, client: Whop) -> None: + audience = client.audiences.list( + account_id="account_id", + ) + assert_matches_type(SyncCursorPage[Audience], audience, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_list_with_all_params(self, client: Whop) -> None: + audience = client.audiences.list( + account_id="account_id", + after="after", + audience_id="audience_id", + first=0, + ) + assert_matches_type(SyncCursorPage[Audience], audience, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_raw_response_list(self, client: Whop) -> None: + response = client.audiences.with_raw_response.list( + account_id="account_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + audience = response.parse() + assert_matches_type(SyncCursorPage[Audience], audience, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_streaming_response_list(self, client: Whop) -> None: + with client.audiences.with_streaming_response.list( + account_id="account_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + audience = response.parse() + assert_matches_type(SyncCursorPage[Audience], audience, 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: + audience = client.audiences.delete( + "audience_id", + ) + assert_matches_type(AudienceDeleteResponse, audience, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_raw_response_delete(self, client: Whop) -> None: + response = client.audiences.with_raw_response.delete( + "audience_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + audience = response.parse() + assert_matches_type(AudienceDeleteResponse, audience, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_streaming_response_delete(self, client: Whop) -> None: + with client.audiences.with_streaming_response.delete( + "audience_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + audience = response.parse() + assert_matches_type(AudienceDeleteResponse, audience, 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 `audience_id` but received ''"): + client.audiences.with_raw_response.delete( + "", + ) + + +class TestAsyncAudiences: + 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: + audience = await async_client.audiences.create( + account_id="account_id", + column_mapping={"foo": "string"}, + file_id="file_id", + name="name", + ) + assert_matches_type(Audience, audience, 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.audiences.with_raw_response.create( + account_id="account_id", + column_mapping={"foo": "string"}, + file_id="file_id", + name="name", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + audience = await response.parse() + assert_matches_type(Audience, audience, 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.audiences.with_streaming_response.create( + account_id="account_id", + column_mapping={"foo": "string"}, + file_id="file_id", + name="name", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + audience = await response.parse() + assert_matches_type(Audience, audience, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_list(self, async_client: AsyncWhop) -> None: + audience = await async_client.audiences.list( + account_id="account_id", + ) + assert_matches_type(AsyncCursorPage[Audience], audience, 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: + audience = await async_client.audiences.list( + account_id="account_id", + after="after", + audience_id="audience_id", + first=0, + ) + assert_matches_type(AsyncCursorPage[Audience], audience, 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.audiences.with_raw_response.list( + account_id="account_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + audience = await response.parse() + assert_matches_type(AsyncCursorPage[Audience], audience, 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.audiences.with_streaming_response.list( + account_id="account_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + audience = await response.parse() + assert_matches_type(AsyncCursorPage[Audience], audience, 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: + audience = await async_client.audiences.delete( + "audience_id", + ) + assert_matches_type(AudienceDeleteResponse, audience, 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.audiences.with_raw_response.delete( + "audience_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + audience = await response.parse() + assert_matches_type(AudienceDeleteResponse, audience, 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.audiences.with_streaming_response.delete( + "audience_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + audience = await response.parse() + assert_matches_type(AudienceDeleteResponse, audience, 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 `audience_id` but received ''"): + await async_client.audiences.with_raw_response.delete( + "", + ) From 3f3381ee54770625db80cf0772e5c9d87fc27f45 Mon Sep 17 00:00:00 2001 From: stlc-bot Date: Sat, 27 Jun 2026 08:37:29 +0000 Subject: [PATCH 085/109] fix(backend): sort standardization Stainless-Generated-From: 3f736ed9e43e7a98f9f2d6c4373665b85ab36618 --- src/whop_sdk/resources/accounts.py | 17 +++++++ .../resources/checkout_configurations.py | 16 +++++-- .../referrals/businesses/businesses.py | 48 +++++++++---------- .../referrals/businesses/earnings.py | 24 +++++----- src/whop_sdk/resources/social_accounts.py | 16 +++++++ src/whop_sdk/resources/verifications.py | 28 ++++++++++- src/whop_sdk/types/account_list_params.py | 8 +++- .../checkout_configuration_list_params.py | 9 ++-- .../business_list_earnings_params.py | 10 ++-- .../types/referrals/business_list_params.py | 10 ++-- .../businesses/earning_list_params.py | 10 ++-- .../types/social_account_list_params.py | 6 +++ .../types/verification_list_params.py | 8 +++- .../referrals/businesses/test_earnings.py | 8 ++-- .../referrals/test_businesses.py | 16 +++---- tests/api_resources/test_accounts.py | 4 ++ .../test_checkout_configurations.py | 6 ++- tests/api_resources/test_social_accounts.py | 4 ++ tests/api_resources/test_verifications.py | 20 ++++++++ 19 files changed, 192 insertions(+), 76 deletions(-) diff --git a/src/whop_sdk/resources/accounts.py b/src/whop_sdk/resources/accounts.py index a72741cd..0f04b7e7 100644 --- a/src/whop_sdk/resources/accounts.py +++ b/src/whop_sdk/resources/accounts.py @@ -3,6 +3,7 @@ from __future__ import annotations from typing import Dict, Iterable, Optional +from typing_extensions import Literal import httpx @@ -287,8 +288,10 @@ def list( *, after: str | Omit = omit, before: str | Omit = omit, + direction: Literal["asc", "desc"] | Omit = omit, first: int | Omit = omit, last: int | Omit = omit, + order: Literal["created_at"] | 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, @@ -307,10 +310,14 @@ def list( before: A cursor; returns accounts before this position. + direction: Sort direction. + first: The number of accounts to return (default 10, max 50). last: The number of accounts to return from the end of the range. + order: The field to sort accounts by. + extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -331,8 +338,10 @@ def list( { "after": after, "before": before, + "direction": direction, "first": first, "last": last, + "order": order, }, account_list_params.AccountListParams, ), @@ -626,8 +635,10 @@ def list( *, after: str | Omit = omit, before: str | Omit = omit, + direction: Literal["asc", "desc"] | Omit = omit, first: int | Omit = omit, last: int | Omit = omit, + order: Literal["created_at"] | 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, @@ -646,10 +657,14 @@ def list( before: A cursor; returns accounts before this position. + direction: Sort direction. + first: The number of accounts to return (default 10, max 50). last: The number of accounts to return from the end of the range. + order: The field to sort accounts by. + extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -670,8 +685,10 @@ def list( { "after": after, "before": before, + "direction": direction, "first": first, "last": last, + "order": order, }, account_list_params.AccountListParams, ), diff --git a/src/whop_sdk/resources/checkout_configurations.py b/src/whop_sdk/resources/checkout_configurations.py index 7a15580f..a196a4b9 100644 --- a/src/whop_sdk/resources/checkout_configurations.py +++ b/src/whop_sdk/resources/checkout_configurations.py @@ -164,8 +164,9 @@ def list( after: str | Omit = omit, created_after: int | Omit = omit, created_before: int | Omit = omit, - direction: str | Omit = omit, + direction: Literal["asc", "desc"] | Omit = omit, first: int | Omit = omit, + order: Literal["created_at"] | Omit = omit, plan_id: 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. @@ -186,10 +187,12 @@ def list( created_before: Filter to configurations created before this Unix timestamp. - direction: Sort direction: asc or desc. Defaults to desc. + direction: Sort direction. first: Number of results to return (forward pagination). + order: The field to sort checkout configurations by. + plan_id: Filter by plan ID. extra_headers: Send extra headers @@ -216,6 +219,7 @@ def list( "created_before": created_before, "direction": direction, "first": first, + "order": order, "plan_id": plan_id, }, checkout_configuration_list_params.CheckoutConfigurationListParams, @@ -396,8 +400,9 @@ def list( after: str | Omit = omit, created_after: int | Omit = omit, created_before: int | Omit = omit, - direction: str | Omit = omit, + direction: Literal["asc", "desc"] | Omit = omit, first: int | Omit = omit, + order: Literal["created_at"] | Omit = omit, plan_id: 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. @@ -418,10 +423,12 @@ def list( created_before: Filter to configurations created before this Unix timestamp. - direction: Sort direction: asc or desc. Defaults to desc. + direction: Sort direction. first: Number of results to return (forward pagination). + order: The field to sort checkout configurations by. + plan_id: Filter by plan ID. extra_headers: Send extra headers @@ -448,6 +455,7 @@ def list( "created_before": created_before, "direction": direction, "first": first, + "order": order, "plan_id": plan_id, }, checkout_configuration_list_params.CheckoutConfigurationListParams, diff --git a/src/whop_sdk/resources/referrals/businesses/businesses.py b/src/whop_sdk/resources/referrals/businesses/businesses.py index 14ecbeed..f3be6cf3 100644 --- a/src/whop_sdk/resources/referrals/businesses/businesses.py +++ b/src/whop_sdk/resources/referrals/businesses/businesses.py @@ -96,11 +96,11 @@ def list( *, after: str | Omit = omit, before: str | Omit = omit, + direction: Literal["asc", "desc"] | Omit = omit, first: int | Omit = omit, has_earnings: bool | Omit = omit, last: int | Omit = omit, - order: Literal["asc", "desc"] | Omit = omit, - sort: Literal["created_at", "referral_started_at", "referral_expires_at", "payout_percentage"] | Omit = omit, + order: Literal["created_at", "referral_started_at", "referral_expires_at", "payout_percentage"] | Omit = omit, status: Literal["active", "removed"] | 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. @@ -118,6 +118,8 @@ def list( before: Cursor to fetch the page before (from page_info.start_cursor). + direction: Sort direction. + first: Number of business referrals to return from the start of the window. has_earnings: When true, only businesses that have paid out at least one earning to the @@ -125,9 +127,7 @@ def list( last: Number of business referrals to return from the end of the window. - order: Sort direction. - - sort: Field to sort business referrals by. + order: The field to sort business referrals by. status: Filter by referral status. @@ -151,11 +151,11 @@ def list( { "after": after, "before": before, + "direction": direction, "first": first, "has_earnings": has_earnings, "last": last, "order": order, - "sort": sort, "status": status, }, business_list_params.BusinessListParams, @@ -169,11 +169,11 @@ def list_earnings( *, after: str | Omit = omit, before: str | Omit = omit, + direction: Literal["asc", "desc"] | Omit = omit, first: int | Omit = omit, include: Literal["receipt_fees"] | Omit = omit, last: int | Omit = omit, - order: Literal["asc", "desc"] | Omit = omit, - sort: Literal["created_at", "commission_amount", "transaction_amount", "payout_at"] | Omit = omit, + order: Literal["created_at", "commission_amount", "transaction_amount", "payout_at"] | Omit = omit, status: Literal["awaiting_settlement", "pending", "completed", "canceled", "reversed"] | 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. @@ -187,12 +187,12 @@ def list_earnings( first. Args: + direction: Sort direction. + include: Comma-separated extras to embed. Supported: receipt_fees (adds amount_after_fees and the receipt_fees breakdown). - order: Sort direction. - - sort: Field to sort earnings by. + order: The field to sort earnings by. status: Filter by earning status. @@ -216,11 +216,11 @@ def list_earnings( { "after": after, "before": before, + "direction": direction, "first": first, "include": include, "last": last, "order": order, - "sort": sort, "status": status, }, business_list_earnings_params.BusinessListEarningsParams, @@ -292,11 +292,11 @@ def list( *, after: str | Omit = omit, before: str | Omit = omit, + direction: Literal["asc", "desc"] | Omit = omit, first: int | Omit = omit, has_earnings: bool | Omit = omit, last: int | Omit = omit, - order: Literal["asc", "desc"] | Omit = omit, - sort: Literal["created_at", "referral_started_at", "referral_expires_at", "payout_percentage"] | Omit = omit, + order: Literal["created_at", "referral_started_at", "referral_expires_at", "payout_percentage"] | Omit = omit, status: Literal["active", "removed"] | 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. @@ -314,6 +314,8 @@ def list( before: Cursor to fetch the page before (from page_info.start_cursor). + direction: Sort direction. + first: Number of business referrals to return from the start of the window. has_earnings: When true, only businesses that have paid out at least one earning to the @@ -321,9 +323,7 @@ def list( last: Number of business referrals to return from the end of the window. - order: Sort direction. - - sort: Field to sort business referrals by. + order: The field to sort business referrals by. status: Filter by referral status. @@ -347,11 +347,11 @@ def list( { "after": after, "before": before, + "direction": direction, "first": first, "has_earnings": has_earnings, "last": last, "order": order, - "sort": sort, "status": status, }, business_list_params.BusinessListParams, @@ -365,11 +365,11 @@ def list_earnings( *, after: str | Omit = omit, before: str | Omit = omit, + direction: Literal["asc", "desc"] | Omit = omit, first: int | Omit = omit, include: Literal["receipt_fees"] | Omit = omit, last: int | Omit = omit, - order: Literal["asc", "desc"] | Omit = omit, - sort: Literal["created_at", "commission_amount", "transaction_amount", "payout_at"] | Omit = omit, + order: Literal["created_at", "commission_amount", "transaction_amount", "payout_at"] | Omit = omit, status: Literal["awaiting_settlement", "pending", "completed", "canceled", "reversed"] | 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. @@ -383,12 +383,12 @@ def list_earnings( first. Args: + direction: Sort direction. + include: Comma-separated extras to embed. Supported: receipt_fees (adds amount_after_fees and the receipt_fees breakdown). - order: Sort direction. - - sort: Field to sort earnings by. + order: The field to sort earnings by. status: Filter by earning status. @@ -412,11 +412,11 @@ def list_earnings( { "after": after, "before": before, + "direction": direction, "first": first, "include": include, "last": last, "order": order, - "sort": sort, "status": status, }, business_list_earnings_params.BusinessListEarningsParams, diff --git a/src/whop_sdk/resources/referrals/businesses/earnings.py b/src/whop_sdk/resources/referrals/businesses/earnings.py index d853b430..9657ee3b 100644 --- a/src/whop_sdk/resources/referrals/businesses/earnings.py +++ b/src/whop_sdk/resources/referrals/businesses/earnings.py @@ -50,11 +50,11 @@ def list( *, after: str | Omit = omit, before: str | Omit = omit, + direction: Literal["asc", "desc"] | Omit = omit, first: int | Omit = omit, include: Literal["receipt_fees"] | Omit = omit, last: int | Omit = omit, - order: Literal["asc", "desc"] | Omit = omit, - sort: Literal["created_at", "commission_amount", "transaction_amount", "payout_at"] | Omit = omit, + order: Literal["created_at", "commission_amount", "transaction_amount", "payout_at"] | Omit = omit, status: Literal["awaiting_settlement", "pending", "completed", "canceled", "reversed"] | 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. @@ -68,12 +68,12 @@ def list( recent first. Args: + direction: Sort direction. + include: Comma-separated extras to embed. Supported: receipt_fees (adds amount_after_fees and the receipt_fees breakdown). - order: Sort direction. - - sort: Field to sort earnings by. + order: The field to sort earnings by. status: Filter by earning status. @@ -99,11 +99,11 @@ def list( { "after": after, "before": before, + "direction": direction, "first": first, "include": include, "last": last, "order": order, - "sort": sort, "status": status, }, earning_list_params.EarningListParams, @@ -139,11 +139,11 @@ def list( *, after: str | Omit = omit, before: str | Omit = omit, + direction: Literal["asc", "desc"] | Omit = omit, first: int | Omit = omit, include: Literal["receipt_fees"] | Omit = omit, last: int | Omit = omit, - order: Literal["asc", "desc"] | Omit = omit, - sort: Literal["created_at", "commission_amount", "transaction_amount", "payout_at"] | Omit = omit, + order: Literal["created_at", "commission_amount", "transaction_amount", "payout_at"] | Omit = omit, status: Literal["awaiting_settlement", "pending", "completed", "canceled", "reversed"] | 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. @@ -157,12 +157,12 @@ def list( recent first. Args: + direction: Sort direction. + include: Comma-separated extras to embed. Supported: receipt_fees (adds amount_after_fees and the receipt_fees breakdown). - order: Sort direction. - - sort: Field to sort earnings by. + order: The field to sort earnings by. status: Filter by earning status. @@ -188,11 +188,11 @@ def list( { "after": after, "before": before, + "direction": direction, "first": first, "include": include, "last": last, "order": order, - "sort": sort, "status": status, }, earning_list_params.EarningListParams, diff --git a/src/whop_sdk/resources/social_accounts.py b/src/whop_sdk/resources/social_accounts.py index c7052b5a..d3d2da41 100644 --- a/src/whop_sdk/resources/social_accounts.py +++ b/src/whop_sdk/resources/social_accounts.py @@ -118,8 +118,10 @@ def list( account_id: str | Omit = omit, after: str | Omit = omit, before: str | Omit = omit, + direction: Literal["asc", "desc"] | Omit = omit, first: int | Omit = omit, last: int | Omit = omit, + order: Literal["display_order", "created_at"] | Omit = omit, platform: Literal["x", "instagram", "youtube", "tiktok", "facebook"] | Omit = omit, scopes: List[Literal["advertise"]] | Omit = omit, user_id: str | Omit = omit, @@ -145,10 +147,14 @@ def list( before: Cursor to fetch the page before (from page_info.start_cursor). + direction: Sort direction. + first: The number of social accounts to return. last: The number of social accounts to return from the end of the range. + order: The field to sort social accounts by. + platform: Only return social accounts for the platform that is specified. scopes: Only return social accounts that have these scopes. @@ -179,8 +185,10 @@ def list( "account_id": account_id, "after": after, "before": before, + "direction": direction, "first": first, "last": last, + "order": order, "platform": platform, "scopes": scopes, "user_id": user_id, @@ -390,8 +398,10 @@ def list( account_id: str | Omit = omit, after: str | Omit = omit, before: str | Omit = omit, + direction: Literal["asc", "desc"] | Omit = omit, first: int | Omit = omit, last: int | Omit = omit, + order: Literal["display_order", "created_at"] | Omit = omit, platform: Literal["x", "instagram", "youtube", "tiktok", "facebook"] | Omit = omit, scopes: List[Literal["advertise"]] | Omit = omit, user_id: str | Omit = omit, @@ -417,10 +427,14 @@ def list( before: Cursor to fetch the page before (from page_info.start_cursor). + direction: Sort direction. + first: The number of social accounts to return. last: The number of social accounts to return from the end of the range. + order: The field to sort social accounts by. + platform: Only return social accounts for the platform that is specified. scopes: Only return social accounts that have these scopes. @@ -451,8 +465,10 @@ def list( "account_id": account_id, "after": after, "before": before, + "direction": direction, "first": first, "last": last, + "order": order, "platform": platform, "scopes": scopes, "user_id": user_id, diff --git a/src/whop_sdk/resources/verifications.py b/src/whop_sdk/resources/verifications.py index 0fe280bb..485df1ce 100644 --- a/src/whop_sdk/resources/verifications.py +++ b/src/whop_sdk/resources/verifications.py @@ -266,6 +266,8 @@ def list( self, *, account_id: str, + direction: Literal["asc", "desc"] | Omit = omit, + order: Literal["updated_at", "created_at"] | 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, @@ -279,6 +281,10 @@ def list( Args: account_id: The account ID to list verifications for (biz\\__ tag). + direction: Sort direction. + + order: The field to sort verifications by. + extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -294,7 +300,14 @@ def list( extra_query=extra_query, extra_body=extra_body, timeout=timeout, - query=maybe_transform({"account_id": account_id}, verification_list_params.VerificationListParams), + query=maybe_transform( + { + "account_id": account_id, + "direction": direction, + "order": order, + }, + verification_list_params.VerificationListParams, + ), ), cast_to=VerificationListResponse, ) @@ -573,6 +586,8 @@ async def list( self, *, account_id: str, + direction: Literal["asc", "desc"] | Omit = omit, + order: Literal["updated_at", "created_at"] | 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, @@ -586,6 +601,10 @@ async def list( Args: account_id: The account ID to list verifications for (biz\\__ tag). + direction: Sort direction. + + order: The field to sort verifications by. + extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -602,7 +621,12 @@ async def list( extra_body=extra_body, timeout=timeout, query=await async_maybe_transform( - {"account_id": account_id}, verification_list_params.VerificationListParams + { + "account_id": account_id, + "direction": direction, + "order": order, + }, + verification_list_params.VerificationListParams, ), ), cast_to=VerificationListResponse, diff --git a/src/whop_sdk/types/account_list_params.py b/src/whop_sdk/types/account_list_params.py index 92d86bd4..1b9cd3b9 100644 --- a/src/whop_sdk/types/account_list_params.py +++ b/src/whop_sdk/types/account_list_params.py @@ -2,7 +2,7 @@ from __future__ import annotations -from typing_extensions import TypedDict +from typing_extensions import Literal, TypedDict __all__ = ["AccountListParams"] @@ -14,8 +14,14 @@ class AccountListParams(TypedDict, total=False): before: str """A cursor; returns accounts before this position.""" + direction: Literal["asc", "desc"] + """Sort direction.""" + first: int """The number of accounts to return (default 10, max 50).""" last: int """The number of accounts to return from the end of the range.""" + + order: Literal["created_at"] + """The field to sort accounts by.""" diff --git a/src/whop_sdk/types/checkout_configuration_list_params.py b/src/whop_sdk/types/checkout_configuration_list_params.py index bd593714..9056e81b 100644 --- a/src/whop_sdk/types/checkout_configuration_list_params.py +++ b/src/whop_sdk/types/checkout_configuration_list_params.py @@ -2,7 +2,7 @@ from __future__ import annotations -from typing_extensions import Required, TypedDict +from typing_extensions import Literal, Required, TypedDict __all__ = ["CheckoutConfigurationListParams"] @@ -20,11 +20,14 @@ class CheckoutConfigurationListParams(TypedDict, total=False): created_before: int """Filter to configurations created before this Unix timestamp.""" - direction: str - """Sort direction: asc or desc. Defaults to desc.""" + direction: Literal["asc", "desc"] + """Sort direction.""" first: int """Number of results to return (forward pagination).""" + order: Literal["created_at"] + """The field to sort checkout configurations by.""" + plan_id: str """Filter by plan ID.""" diff --git a/src/whop_sdk/types/referrals/business_list_earnings_params.py b/src/whop_sdk/types/referrals/business_list_earnings_params.py index b762aaef..b91e8ce4 100644 --- a/src/whop_sdk/types/referrals/business_list_earnings_params.py +++ b/src/whop_sdk/types/referrals/business_list_earnings_params.py @@ -12,6 +12,9 @@ class BusinessListEarningsParams(TypedDict, total=False): before: str + direction: Literal["asc", "desc"] + """Sort direction.""" + first: int include: Literal["receipt_fees"] @@ -22,11 +25,8 @@ class BusinessListEarningsParams(TypedDict, total=False): last: int - order: Literal["asc", "desc"] - """Sort direction.""" - - sort: Literal["created_at", "commission_amount", "transaction_amount", "payout_at"] - """Field to sort earnings by.""" + order: Literal["created_at", "commission_amount", "transaction_amount", "payout_at"] + """The field to sort earnings by.""" status: Literal["awaiting_settlement", "pending", "completed", "canceled", "reversed"] """Filter by earning status.""" diff --git a/src/whop_sdk/types/referrals/business_list_params.py b/src/whop_sdk/types/referrals/business_list_params.py index 07b65a33..ddad7083 100644 --- a/src/whop_sdk/types/referrals/business_list_params.py +++ b/src/whop_sdk/types/referrals/business_list_params.py @@ -14,6 +14,9 @@ class BusinessListParams(TypedDict, total=False): before: str """Cursor to fetch the page before (from page_info.start_cursor).""" + direction: Literal["asc", "desc"] + """Sort direction.""" + first: int """Number of business referrals to return from the start of the window.""" @@ -26,11 +29,8 @@ class BusinessListParams(TypedDict, total=False): last: int """Number of business referrals to return from the end of the window.""" - order: Literal["asc", "desc"] - """Sort direction.""" - - sort: Literal["created_at", "referral_started_at", "referral_expires_at", "payout_percentage"] - """Field to sort business referrals by.""" + order: Literal["created_at", "referral_started_at", "referral_expires_at", "payout_percentage"] + """The field to sort business referrals by.""" status: Literal["active", "removed"] """Filter by referral status.""" diff --git a/src/whop_sdk/types/referrals/businesses/earning_list_params.py b/src/whop_sdk/types/referrals/businesses/earning_list_params.py index 89fae383..091c4d8c 100644 --- a/src/whop_sdk/types/referrals/businesses/earning_list_params.py +++ b/src/whop_sdk/types/referrals/businesses/earning_list_params.py @@ -12,6 +12,9 @@ class EarningListParams(TypedDict, total=False): before: str + direction: Literal["asc", "desc"] + """Sort direction.""" + first: int include: Literal["receipt_fees"] @@ -22,11 +25,8 @@ class EarningListParams(TypedDict, total=False): last: int - order: Literal["asc", "desc"] - """Sort direction.""" - - sort: Literal["created_at", "commission_amount", "transaction_amount", "payout_at"] - """Field to sort earnings by.""" + order: Literal["created_at", "commission_amount", "transaction_amount", "payout_at"] + """The field to sort earnings by.""" status: Literal["awaiting_settlement", "pending", "completed", "canceled", "reversed"] """Filter by earning status.""" diff --git a/src/whop_sdk/types/social_account_list_params.py b/src/whop_sdk/types/social_account_list_params.py index ab56f7ed..c6b97e9a 100644 --- a/src/whop_sdk/types/social_account_list_params.py +++ b/src/whop_sdk/types/social_account_list_params.py @@ -21,12 +21,18 @@ class SocialAccountListParams(TypedDict, total=False): before: str """Cursor to fetch the page before (from page_info.start_cursor).""" + direction: Literal["asc", "desc"] + """Sort direction.""" + first: int """The number of social accounts to return.""" last: int """The number of social accounts to return from the end of the range.""" + order: Literal["display_order", "created_at"] + """The field to sort social accounts by.""" + platform: Literal["x", "instagram", "youtube", "tiktok", "facebook"] """Only return social accounts for the platform that is specified.""" diff --git a/src/whop_sdk/types/verification_list_params.py b/src/whop_sdk/types/verification_list_params.py index 9b376d72..16f6863a 100644 --- a/src/whop_sdk/types/verification_list_params.py +++ b/src/whop_sdk/types/verification_list_params.py @@ -2,7 +2,7 @@ from __future__ import annotations -from typing_extensions import Required, TypedDict +from typing_extensions import Literal, Required, TypedDict __all__ = ["VerificationListParams"] @@ -10,3 +10,9 @@ class VerificationListParams(TypedDict, total=False): account_id: Required[str] """The account ID to list verifications for (biz\\__ tag).""" + + direction: Literal["asc", "desc"] + """Sort direction.""" + + order: Literal["updated_at", "created_at"] + """The field to sort verifications by.""" diff --git a/tests/api_resources/referrals/businesses/test_earnings.py b/tests/api_resources/referrals/businesses/test_earnings.py index 5625938a..bd08246b 100644 --- a/tests/api_resources/referrals/businesses/test_earnings.py +++ b/tests/api_resources/referrals/businesses/test_earnings.py @@ -33,11 +33,11 @@ def test_method_list_with_all_params(self, client: Whop) -> None: id="id", after="after", before="before", + direction="asc", first=100, include="receipt_fees", last=100, - order="asc", - sort="created_at", + order="created_at", status="awaiting_settlement", ) assert_matches_type(SyncCursorPage[EarningListResponse], earning, path=["response"]) @@ -97,11 +97,11 @@ async def test_method_list_with_all_params(self, async_client: AsyncWhop) -> Non id="id", after="after", before="before", + direction="asc", first=100, include="receipt_fees", last=100, - order="asc", - sort="created_at", + order="created_at", status="awaiting_settlement", ) assert_matches_type(AsyncCursorPage[EarningListResponse], earning, path=["response"]) diff --git a/tests/api_resources/referrals/test_businesses.py b/tests/api_resources/referrals/test_businesses.py index d3075554..4aeb9f65 100644 --- a/tests/api_resources/referrals/test_businesses.py +++ b/tests/api_resources/referrals/test_businesses.py @@ -76,11 +76,11 @@ def test_method_list_with_all_params(self, client: Whop) -> None: business = client.referrals.businesses.list( after="after", before="before", + direction="asc", first=100, has_earnings=True, last=100, - order="asc", - sort="created_at", + order="created_at", status="active", ) assert_matches_type(SyncCursorPage[BusinessListResponse], business, path=["response"]) @@ -119,11 +119,11 @@ def test_method_list_earnings_with_all_params(self, client: Whop) -> None: business = client.referrals.businesses.list_earnings( after="after", before="before", + direction="asc", first=100, include="receipt_fees", last=100, - order="asc", - sort="created_at", + order="created_at", status="awaiting_settlement", ) assert_matches_type(SyncCursorPage[BusinessListEarningsResponse], business, path=["response"]) @@ -210,11 +210,11 @@ async def test_method_list_with_all_params(self, async_client: AsyncWhop) -> Non business = await async_client.referrals.businesses.list( after="after", before="before", + direction="asc", first=100, has_earnings=True, last=100, - order="asc", - sort="created_at", + order="created_at", status="active", ) assert_matches_type(AsyncCursorPage[BusinessListResponse], business, path=["response"]) @@ -253,11 +253,11 @@ async def test_method_list_earnings_with_all_params(self, async_client: AsyncWho business = await async_client.referrals.businesses.list_earnings( after="after", before="before", + direction="asc", first=100, include="receipt_fees", last=100, - order="asc", - sort="created_at", + order="created_at", status="awaiting_settlement", ) assert_matches_type(AsyncCursorPage[BusinessListEarningsResponse], business, path=["response"]) diff --git a/tests/api_resources/test_accounts.py b/tests/api_resources/test_accounts.py index d57ce2c0..f7862867 100644 --- a/tests/api_resources/test_accounts.py +++ b/tests/api_resources/test_accounts.py @@ -188,8 +188,10 @@ def test_method_list_with_all_params(self, client: Whop) -> None: account = client.accounts.list( after="after", before="before", + direction="asc", first=0, last=0, + order="created_at", ) assert_matches_type(SyncCursorPage[Account], account, path=["response"]) @@ -419,8 +421,10 @@ async def test_method_list_with_all_params(self, async_client: AsyncWhop) -> Non account = await async_client.accounts.list( after="after", before="before", + direction="asc", first=0, last=0, + order="created_at", ) assert_matches_type(AsyncCursorPage[Account], account, path=["response"]) diff --git a/tests/api_resources/test_checkout_configurations.py b/tests/api_resources/test_checkout_configurations.py index acdcd76b..03b8452c 100644 --- a/tests/api_resources/test_checkout_configurations.py +++ b/tests/api_resources/test_checkout_configurations.py @@ -155,8 +155,9 @@ def test_method_list_with_all_params(self, client: Whop) -> None: after="after", created_after=0, created_before=0, - direction="direction", + direction="asc", first=0, + order="created_at", plan_id="plan_id", ) assert_matches_type( @@ -374,8 +375,9 @@ async def test_method_list_with_all_params(self, async_client: AsyncWhop) -> Non after="after", created_after=0, created_before=0, - direction="direction", + direction="asc", first=0, + order="created_at", plan_id="plan_id", ) assert_matches_type( diff --git a/tests/api_resources/test_social_accounts.py b/tests/api_resources/test_social_accounts.py index 5d776302..96d1c035 100644 --- a/tests/api_resources/test_social_accounts.py +++ b/tests/api_resources/test_social_accounts.py @@ -84,8 +84,10 @@ def test_method_list_with_all_params(self, client: Whop) -> None: account_id="account_id", after="after", before="before", + direction="asc", first=100, last=100, + order="display_order", platform="x", scopes=["advertise"], user_id="user_id", @@ -292,8 +294,10 @@ async def test_method_list_with_all_params(self, async_client: AsyncWhop) -> Non account_id="account_id", after="after", before="before", + direction="asc", first=100, last=100, + order="display_order", platform="x", scopes=["advertise"], user_id="user_id", diff --git a/tests/api_resources/test_verifications.py b/tests/api_resources/test_verifications.py index 6be8f3ae..36804505 100644 --- a/tests/api_resources/test_verifications.py +++ b/tests/api_resources/test_verifications.py @@ -195,6 +195,16 @@ def test_method_list(self, client: Whop) -> None: ) assert_matches_type(VerificationListResponse, verification, path=["response"]) + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_list_with_all_params(self, client: Whop) -> None: + verification = client.verifications.list( + account_id="account_id", + direction="asc", + order="updated_at", + ) + assert_matches_type(VerificationListResponse, verification, path=["response"]) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_raw_response_list(self, client: Whop) -> None: @@ -441,6 +451,16 @@ async def test_method_list(self, async_client: AsyncWhop) -> None: ) assert_matches_type(VerificationListResponse, verification, 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: + verification = await async_client.verifications.list( + account_id="account_id", + direction="asc", + order="updated_at", + ) + assert_matches_type(VerificationListResponse, verification, path=["response"]) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_raw_response_list(self, async_client: AsyncWhop) -> None: From 67a9adc0ee7150c0498ca181c1d5b792f148997d Mon Sep 17 00:00:00 2001 From: stlc-bot Date: Sat, 27 Jun 2026 22:32:49 +0000 Subject: [PATCH 086/109] docs(api): show status in referral earnings example Stainless-Generated-From: b9b4e2a4596090217c0f76206ae9abce3f3f854d --- src/whop_sdk/types/referrals/business_list_earnings_response.py | 1 + src/whop_sdk/types/referrals/businesses/earning_list_response.py | 1 + 2 files changed, 2 insertions(+) diff --git a/src/whop_sdk/types/referrals/business_list_earnings_response.py b/src/whop_sdk/types/referrals/business_list_earnings_response.py index 4c414287..5987818a 100644 --- a/src/whop_sdk/types/referrals/business_list_earnings_response.py +++ b/src/whop_sdk/types/referrals/business_list_earnings_response.py @@ -115,6 +115,7 @@ class BusinessListEarningsResponse(BaseModel): receipt: Optional[Receipt] = None status: Literal["awaiting_settlement", "pending", "completed", "canceled", "reversed"] + """Current status of the earning.""" transaction_amount_usd: str """The sale amount the commission is calculated from, in USD.""" diff --git a/src/whop_sdk/types/referrals/businesses/earning_list_response.py b/src/whop_sdk/types/referrals/businesses/earning_list_response.py index 8f551bdd..40ba477a 100644 --- a/src/whop_sdk/types/referrals/businesses/earning_list_response.py +++ b/src/whop_sdk/types/referrals/businesses/earning_list_response.py @@ -115,6 +115,7 @@ class EarningListResponse(BaseModel): receipt: Optional[Receipt] = None status: Literal["awaiting_settlement", "pending", "completed", "canceled", "reversed"] + """Current status of the earning.""" transaction_amount_usd: str """The sale amount the commission is calculated from, in USD.""" From 4677c67339e5de676b831332d428b34a4b351f28 Mon Sep 17 00:00:00 2001 From: stlc-bot Date: Sun, 28 Jun 2026 01:31:45 +0000 Subject: [PATCH 087/109] Deposits API: crypto options as a per-network list with tokens & icons Stainless-Generated-From: 928e15cff8fc20a52e5781a2a99c927c6b5b5d64 --- src/whop_sdk/types/deposit_create_response.py | 42 ++++++++++++++----- 1 file changed, 32 insertions(+), 10 deletions(-) diff --git a/src/whop_sdk/types/deposit_create_response.py b/src/whop_sdk/types/deposit_create_response.py index 65ccb5eb..7e2d351c 100644 --- a/src/whop_sdk/types/deposit_create_response.py +++ b/src/whop_sdk/types/deposit_create_response.py @@ -5,7 +5,14 @@ from .._models import BaseModel -__all__ = ["DepositCreateResponse", "Methods", "MethodsBank", "MethodsBankCurrency", "MethodsCrypto"] +__all__ = [ + "DepositCreateResponse", + "Methods", + "MethodsBank", + "MethodsBankCurrency", + "MethodsCrypto", + "MethodsCryptoSupportedCurrency", +] class MethodsBankCurrency(BaseModel): @@ -41,17 +48,29 @@ class MethodsBank(BaseModel): """Bank transfer currencies available for this deposit.""" +class MethodsCryptoSupportedCurrency(BaseModel): + icon_url: Optional[str] = None + """Token icon URL. Null when no icon is available.""" + + name: str + """Token symbol, such as `USDC`.""" + + class MethodsCrypto(BaseModel): - """Crypto wallet addresses available for this deposit.""" + deposit_address: Optional[str] = None + """Address to send funds to on this network. - evm: str - """EVM-compatible deposit address.""" + Null when the provider has not issued one yet. + """ - solana: str - """Solana deposit address.""" + icon_url: Optional[str] = None + """Network icon URL.""" - wallet: str - """Primary wallet address for destination account.""" + name: str + """Network display name, such as `Ethereum` or `Solana`.""" + + supported_currencies: List[MethodsCryptoSupportedCurrency] + """Tokens accepted for deposit on this network.""" class Methods(BaseModel): @@ -63,8 +82,11 @@ class Methods(BaseModel): Only present when bank deposits are active for the destination account. """ - crypto: MethodsCrypto - """Crypto wallet addresses available for this deposit.""" + crypto: List[MethodsCrypto] + """ + Crypto networks available for this deposit, each with its on-chain deposit + address and the tokens accepted on that network. + """ class DepositCreateResponse(BaseModel): From d3674f6c57b12ea53e5304ea9de8b639ced301b0 Mon Sep 17 00:00:00 2001 From: stlc-bot Date: Mon, 29 Jun 2026 08:38:57 +0000 Subject: [PATCH 088/109] Issue company cards over the public REST API Stainless-Generated-From: af5562b7de51894f43609df62e983a03448a57c3 --- src/whop_sdk/resources/cards.py | 34 ++++++++++++++++++------ src/whop_sdk/types/card_create_params.py | 6 +++++ tests/api_resources/test_cards.py | 2 ++ 3 files changed, 34 insertions(+), 8 deletions(-) diff --git a/src/whop_sdk/resources/cards.py b/src/whop_sdk/resources/cards.py index 1bdb27f9..da7ecab0 100644 --- a/src/whop_sdk/resources/cards.py +++ b/src/whop_sdk/resources/cards.py @@ -49,6 +49,7 @@ def create( self, *, account_id: str | Omit = omit, + assigned_user_id: str | Omit = omit, name: str | Omit = omit, spend_limit: float | Omit = omit, spend_limit_frequency: Literal["daily", "weekly", "monthly", "one_time"] | Omit = omit, @@ -61,15 +62,22 @@ def create( extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> CardCreateResponse: - """Issues a virtual card for an individual (consumer) card issuing account. + """Issues a virtual card. - The - ledger's owner is passed as exactly one of account*id (a biz* identifier) or - user*id (a user* identifier). Returns the newly created card resource. + For an individual (consumer) card issuing account, the + card is issued to the account's own cardholder. For a company (business) card + issuing account, pass assigned*user_id to issue the card to a company member; if + that member is not yet an approved card-issuing user, the card is provisioned + asynchronously or an onboarding invitation is sent (HTTP 202). The ledger's + owner is passed as exactly one of account_id (a biz* identifier) or user*id (a + user* identifier). Returns the newly created card resource. Args: account_id: The owning account ID (a biz\\__ identifier). Provide this or user_id. + assigned_user_id: The company member (a user\\__ identifier) to assign the card to. Required for + company (business) card issuing accounts. + name: A display name for the card. spend_limit: Spending limit amount, in dollars. @@ -93,6 +101,7 @@ def create( body=maybe_transform( { "account_id": account_id, + "assigned_user_id": assigned_user_id, "name": name, "spend_limit": spend_limit, "spend_limit_frequency": spend_limit_frequency, @@ -234,6 +243,7 @@ async def create( self, *, account_id: str | Omit = omit, + assigned_user_id: str | Omit = omit, name: str | Omit = omit, spend_limit: float | Omit = omit, spend_limit_frequency: Literal["daily", "weekly", "monthly", "one_time"] | Omit = omit, @@ -246,15 +256,22 @@ async def create( extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> CardCreateResponse: - """Issues a virtual card for an individual (consumer) card issuing account. + """Issues a virtual card. - The - ledger's owner is passed as exactly one of account*id (a biz* identifier) or - user*id (a user* identifier). Returns the newly created card resource. + For an individual (consumer) card issuing account, the + card is issued to the account's own cardholder. For a company (business) card + issuing account, pass assigned*user_id to issue the card to a company member; if + that member is not yet an approved card-issuing user, the card is provisioned + asynchronously or an onboarding invitation is sent (HTTP 202). The ledger's + owner is passed as exactly one of account_id (a biz* identifier) or user*id (a + user* identifier). Returns the newly created card resource. Args: account_id: The owning account ID (a biz\\__ identifier). Provide this or user_id. + assigned_user_id: The company member (a user\\__ identifier) to assign the card to. Required for + company (business) card issuing accounts. + name: A display name for the card. spend_limit: Spending limit amount, in dollars. @@ -278,6 +295,7 @@ async def create( body=await async_maybe_transform( { "account_id": account_id, + "assigned_user_id": assigned_user_id, "name": name, "spend_limit": spend_limit, "spend_limit_frequency": spend_limit_frequency, diff --git a/src/whop_sdk/types/card_create_params.py b/src/whop_sdk/types/card_create_params.py index 8f91ffac..082ca095 100644 --- a/src/whop_sdk/types/card_create_params.py +++ b/src/whop_sdk/types/card_create_params.py @@ -11,6 +11,12 @@ class CardCreateParams(TypedDict, total=False): account_id: str """The owning account ID (a biz\\__ identifier). Provide this or user_id.""" + assigned_user_id: str + """The company member (a user\\__ identifier) to assign the card to. + + Required for company (business) card issuing accounts. + """ + name: str """A display name for the card.""" diff --git a/tests/api_resources/test_cards.py b/tests/api_resources/test_cards.py index f56dc4c6..9716fe1a 100644 --- a/tests/api_resources/test_cards.py +++ b/tests/api_resources/test_cards.py @@ -32,6 +32,7 @@ def test_method_create(self, client: Whop) -> None: def test_method_create_with_all_params(self, client: Whop) -> None: card = client.cards.create( account_id="account_id", + assigned_user_id="assigned_user_id", name="name", spend_limit=0, spend_limit_frequency="daily", @@ -168,6 +169,7 @@ async def test_method_create(self, async_client: AsyncWhop) -> None: async def test_method_create_with_all_params(self, async_client: AsyncWhop) -> None: card = await async_client.cards.create( account_id="account_id", + assigned_user_id="assigned_user_id", name="name", spend_limit=0, spend_limit_frequency="daily", From d78fbfec344612474212e5994b422919e1c55dc3 Mon Sep 17 00:00:00 2001 From: stlc-bot Date: Mon, 29 Jun 2026 11:21:50 +0000 Subject: [PATCH 089/109] add product tax code id field to Update Product docs Stainless-Generated-From: 39e571ce3a58eb58e8e04b6d9bf281b6f3f803f5 --- src/whop_sdk/resources/products.py | 8 ++++++++ src/whop_sdk/types/product_update_params.py | 3 +++ tests/api_resources/test_products.py | 2 ++ 3 files changed, 13 insertions(+) diff --git a/src/whop_sdk/resources/products.py b/src/whop_sdk/resources/products.py index 5bb4f3b4..a9fb7d02 100644 --- a/src/whop_sdk/resources/products.py +++ b/src/whop_sdk/resources/products.py @@ -194,6 +194,7 @@ def update( description: Optional[str] | Omit = omit, headline: Optional[str] | Omit = omit, metadata: Optional[object] | Omit = omit, + product_tax_code_id: Optional[str] | Omit = omit, title: str | Omit = omit, visibility: str | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. @@ -213,6 +214,8 @@ def update( metadata: Custom key-value pairs to store on the product. + product_tax_code_id: The unique identifier of the tax classification code. + title: The display name of the product. visibility: Whether the product is visible to customers. @@ -234,6 +237,7 @@ def update( "description": description, "headline": headline, "metadata": metadata, + "product_tax_code_id": product_tax_code_id, "title": title, "visibility": visibility, }, @@ -523,6 +527,7 @@ async def update( description: Optional[str] | Omit = omit, headline: Optional[str] | Omit = omit, metadata: Optional[object] | Omit = omit, + product_tax_code_id: Optional[str] | Omit = omit, title: str | Omit = omit, visibility: str | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. @@ -542,6 +547,8 @@ async def update( metadata: Custom key-value pairs to store on the product. + product_tax_code_id: The unique identifier of the tax classification code. + title: The display name of the product. visibility: Whether the product is visible to customers. @@ -563,6 +570,7 @@ async def update( "description": description, "headline": headline, "metadata": metadata, + "product_tax_code_id": product_tax_code_id, "title": title, "visibility": visibility, }, diff --git a/src/whop_sdk/types/product_update_params.py b/src/whop_sdk/types/product_update_params.py index d9d48096..67e3748f 100644 --- a/src/whop_sdk/types/product_update_params.py +++ b/src/whop_sdk/types/product_update_params.py @@ -18,6 +18,9 @@ class ProductUpdateParams(TypedDict, total=False): metadata: Optional[object] """Custom key-value pairs to store on the product.""" + product_tax_code_id: Optional[str] + """The unique identifier of the tax classification code.""" + title: str """The display name of the product.""" diff --git a/tests/api_resources/test_products.py b/tests/api_resources/test_products.py index 10201f78..99906e8b 100644 --- a/tests/api_resources/test_products.py +++ b/tests/api_resources/test_products.py @@ -135,6 +135,7 @@ def test_method_update_with_all_params(self, client: Whop) -> None: description="description", headline="headline", metadata={}, + product_tax_code_id="product_tax_code_id", title="title", visibility="visibility", ) @@ -388,6 +389,7 @@ async def test_method_update_with_all_params(self, async_client: AsyncWhop) -> N description="description", headline="headline", metadata={}, + product_tax_code_id="product_tax_code_id", title="title", visibility="visibility", ) From 869c27a608ba2c9dbce48d4f763064be3df4a81b Mon Sep 17 00:00:00 2001 From: stlc-bot Date: Mon, 29 Jun 2026 13:34:59 +0000 Subject: [PATCH 090/109] Whop Tax Service: Add tax settings and update business_address to Public API Stainless-Generated-From: 559a9539ba7a52d0543522c699c669e5bc5148e8 --- src/whop_sdk/resources/accounts.py | 52 +++++- src/whop_sdk/types/account.py | 27 +++ src/whop_sdk/types/account_update_params.py | 176 +++++++++++++++++- .../types/plan_calculate_tax_params.py | 116 +++++++++++- tests/api_resources/test_accounts.py | 32 ++++ tests/api_resources/test_plans.py | 4 +- 6 files changed, 399 insertions(+), 8 deletions(-) diff --git a/src/whop_sdk/resources/accounts.py b/src/whop_sdk/resources/accounts.py index 0f04b7e7..cafcc5ab 100644 --- a/src/whop_sdk/resources/accounts.py +++ b/src/whop_sdk/resources/accounts.py @@ -60,7 +60,9 @@ def create( """Creates an account. User tokens create business accounts; business account API - keys create connected accounts. + keys create connected accounts. Tax fields (`tax_remitted_by`, + `product_tax_code_id`, `business_address`, `tax_identifiers`) are configured + with Update Account, not at creation. Args: email: The email address of the account owner. Required for business account API key @@ -132,6 +134,7 @@ def update( affiliate_application_required: bool | Omit = omit, affiliate_instructions: Optional[str] | Omit = omit, banner_image: Optional[Dict[str, object]] | Omit = omit, + business_address: account_update_params.BusinessAddress | Omit = omit, business_type: Optional[str] | Omit = omit, country: Optional[str] | Omit = omit, description: Optional[str] | Omit = omit, @@ -147,6 +150,7 @@ def update( opengraph_image_variant: Optional[str] | Omit = omit, other_business_description: Optional[str] | Omit = omit, other_industry_description: Optional[str] | Omit = omit, + product_tax_code_id: Optional[str] | Omit = omit, require_2fa: bool | Omit = omit, route: Optional[str] | Omit = omit, send_customer_emails: bool | Omit = omit, @@ -156,6 +160,8 @@ def update( social_links: Iterable[Dict[str, object]] | Omit = omit, store_page_config: Optional[Dict[str, object]] | Omit = omit, target_audience: Optional[str] | Omit = omit, + tax_identifiers: Iterable[account_update_params.TaxIdentifier] | Omit = omit, + tax_remitted_by: Literal["whop", "self", "none"] | Omit = omit, title: Optional[str] | Omit = omit, use_logo_as_opengraph_image_fallback: bool | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. @@ -178,6 +184,9 @@ def update( banner_image: Attachment input for the account banner image. + business_address: Account business address used to calculate tax. A complete address in a + supported country is required when `tax_remitted_by` is `self`. + business_type: High-level business category for the account. country: Country where the account is located. @@ -208,6 +217,8 @@ def update( other_industry_description: The description of the industry type when industry_type is other. + product_tax_code_id: ID of the tax classification code applied by default to the account's products. + require_2fa: Whether the account requires authorized users to have two-factor authentication enabled. @@ -227,6 +238,15 @@ def update( target_audience: The target audience for this account. + tax_identifiers: Account tax/VAT registrations to add or update. When `tax_remitted_by` is + `self`, tax is calculated and collected only in the countries where the account + holds a registration. + + tax_remitted_by: Who calculates and remits tax for the account: `whop` (Whop calculates and + remits), `self` (Whop calculates; the account collects and remits), or `none` + (neither; the account is responsible). `self` requires a `business_address` in a + supported country. + title: The display name of the account. use_logo_as_opengraph_image_fallback: Whether the account uses its logo as the fallback Open Graph image. @@ -248,6 +268,7 @@ def update( "affiliate_application_required": affiliate_application_required, "affiliate_instructions": affiliate_instructions, "banner_image": banner_image, + "business_address": business_address, "business_type": business_type, "country": country, "description": description, @@ -263,6 +284,7 @@ def update( "opengraph_image_variant": opengraph_image_variant, "other_business_description": other_business_description, "other_industry_description": other_industry_description, + "product_tax_code_id": product_tax_code_id, "require_2fa": require_2fa, "route": route, "send_customer_emails": send_customer_emails, @@ -272,6 +294,8 @@ def update( "social_links": social_links, "store_page_config": store_page_config, "target_audience": target_audience, + "tax_identifiers": tax_identifiers, + "tax_remitted_by": tax_remitted_by, "title": title, "use_logo_as_opengraph_image_fallback": use_logo_as_opengraph_image_fallback, }, @@ -407,7 +431,9 @@ async def create( """Creates an account. User tokens create business accounts; business account API - keys create connected accounts. + keys create connected accounts. Tax fields (`tax_remitted_by`, + `product_tax_code_id`, `business_address`, `tax_identifiers`) are configured + with Update Account, not at creation. Args: email: The email address of the account owner. Required for business account API key @@ -479,6 +505,7 @@ async def update( affiliate_application_required: bool | Omit = omit, affiliate_instructions: Optional[str] | Omit = omit, banner_image: Optional[Dict[str, object]] | Omit = omit, + business_address: account_update_params.BusinessAddress | Omit = omit, business_type: Optional[str] | Omit = omit, country: Optional[str] | Omit = omit, description: Optional[str] | Omit = omit, @@ -494,6 +521,7 @@ async def update( opengraph_image_variant: Optional[str] | Omit = omit, other_business_description: Optional[str] | Omit = omit, other_industry_description: Optional[str] | Omit = omit, + product_tax_code_id: Optional[str] | Omit = omit, require_2fa: bool | Omit = omit, route: Optional[str] | Omit = omit, send_customer_emails: bool | Omit = omit, @@ -503,6 +531,8 @@ async def update( social_links: Iterable[Dict[str, object]] | Omit = omit, store_page_config: Optional[Dict[str, object]] | Omit = omit, target_audience: Optional[str] | Omit = omit, + tax_identifiers: Iterable[account_update_params.TaxIdentifier] | Omit = omit, + tax_remitted_by: Literal["whop", "self", "none"] | Omit = omit, title: Optional[str] | Omit = omit, use_logo_as_opengraph_image_fallback: bool | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. @@ -525,6 +555,9 @@ async def update( banner_image: Attachment input for the account banner image. + business_address: Account business address used to calculate tax. A complete address in a + supported country is required when `tax_remitted_by` is `self`. + business_type: High-level business category for the account. country: Country where the account is located. @@ -555,6 +588,8 @@ async def update( other_industry_description: The description of the industry type when industry_type is other. + product_tax_code_id: ID of the tax classification code applied by default to the account's products. + require_2fa: Whether the account requires authorized users to have two-factor authentication enabled. @@ -574,6 +609,15 @@ async def update( target_audience: The target audience for this account. + tax_identifiers: Account tax/VAT registrations to add or update. When `tax_remitted_by` is + `self`, tax is calculated and collected only in the countries where the account + holds a registration. + + tax_remitted_by: Who calculates and remits tax for the account: `whop` (Whop calculates and + remits), `self` (Whop calculates; the account collects and remits), or `none` + (neither; the account is responsible). `self` requires a `business_address` in a + supported country. + title: The display name of the account. use_logo_as_opengraph_image_fallback: Whether the account uses its logo as the fallback Open Graph image. @@ -595,6 +639,7 @@ async def update( "affiliate_application_required": affiliate_application_required, "affiliate_instructions": affiliate_instructions, "banner_image": banner_image, + "business_address": business_address, "business_type": business_type, "country": country, "description": description, @@ -610,6 +655,7 @@ async def update( "opengraph_image_variant": opengraph_image_variant, "other_business_description": other_business_description, "other_industry_description": other_industry_description, + "product_tax_code_id": product_tax_code_id, "require_2fa": require_2fa, "route": route, "send_customer_emails": send_customer_emails, @@ -619,6 +665,8 @@ async def update( "social_links": social_links, "store_page_config": store_page_config, "target_audience": target_audience, + "tax_identifiers": tax_identifiers, + "tax_remitted_by": tax_remitted_by, "title": title, "use_logo_as_opengraph_image_fallback": use_logo_as_opengraph_image_fallback, }, diff --git a/src/whop_sdk/types/account.py b/src/whop_sdk/types/account.py index 54334f8e..76f5f34e 100644 --- a/src/whop_sdk/types/account.py +++ b/src/whop_sdk/types/account.py @@ -163,6 +163,12 @@ class Account(BaseModel): banner_image_url: Optional[str] = None """Account banner image URL.""" + business_address: Optional[object] = None + """ + Account business address used to calculate tax, with `line1`, `line2`, `city`, + `state`, `postal_code`, and `country`. `null` when no address is set. + """ + business_type: Optional[str] = None """High-level business category for the account.""" @@ -220,6 +226,12 @@ class Account(BaseModel): parent_account_id: Optional[str] = None """Parent account ID for connected accounts.""" + product_tax_code: Optional[object] = None + """ + Tax classification code applied by default to the account's products, with `id`, + `name`, and `product_type`. `null` when no default is set. + """ + recommended_actions: Optional[List[RecommendedAction]] = None require_2fa: bool @@ -256,6 +268,21 @@ class Account(BaseModel): target_audience: Optional[str] = None """Target audience for this account.""" + tax_identifiers: List[object] + """Account tax/VAT registrations, each with `id`, `tax_id_type`, and + `tax_id_value`. + + Empty when none are set. + """ + + tax_remitted_by: Optional[str] = None + """ + Who calculates and remits tax for the account: `whop` (Whop calculates and + remits), `self` (Whop calculates; the account collects and remits), or `none` + (neither; the account is responsible). `null` until the account enrolls in the + Whop tax service. + """ + title: str """Account display name.""" diff --git a/src/whop_sdk/types/account_update_params.py b/src/whop_sdk/types/account_update_params.py index b32d332e..6c59c954 100644 --- a/src/whop_sdk/types/account_update_params.py +++ b/src/whop_sdk/types/account_update_params.py @@ -3,11 +3,11 @@ from __future__ import annotations from typing import Dict, Iterable, Optional -from typing_extensions import TypedDict +from typing_extensions import Literal, Required, TypedDict from .._types import SequenceNotStr -__all__ = ["AccountUpdateParams"] +__all__ = ["AccountUpdateParams", "BusinessAddress", "TaxIdentifier"] class AccountUpdateParams(TypedDict, total=False): @@ -23,6 +23,13 @@ class AccountUpdateParams(TypedDict, total=False): banner_image: Optional[Dict[str, object]] """Attachment input for the account banner image.""" + business_address: BusinessAddress + """Account business address used to calculate tax. + + A complete address in a supported country is required when `tax_remitted_by` is + `self`. + """ + business_type: Optional[str] """High-level business category for the account.""" @@ -68,6 +75,9 @@ class AccountUpdateParams(TypedDict, total=False): other_industry_description: Optional[str] """The description of the industry type when industry_type is other.""" + product_tax_code_id: Optional[str] + """ID of the tax classification code applied by default to the account's products.""" + require_2fa: bool """ Whether the account requires authorized users to have two-factor authentication @@ -98,8 +108,170 @@ class AccountUpdateParams(TypedDict, total=False): target_audience: Optional[str] """The target audience for this account.""" + tax_identifiers: Iterable[TaxIdentifier] + """Account tax/VAT registrations to add or update. + + When `tax_remitted_by` is `self`, tax is calculated and collected only in the + countries where the account holds a registration. + """ + + tax_remitted_by: Literal["whop", "self", "none"] + """ + Who calculates and remits tax for the account: `whop` (Whop calculates and + remits), `self` (Whop calculates; the account collects and remits), or `none` + (neither; the account is responsible). `self` requires a `business_address` in a + supported country. + """ + title: Optional[str] """The display name of the account.""" use_logo_as_opengraph_image_fallback: bool """Whether the account uses its logo as the fallback Open Graph image.""" + + +class BusinessAddress(TypedDict, total=False): + """Account business address used to calculate tax. + + A complete address in a supported country is required when `tax_remitted_by` is `self`. + """ + + city: Optional[str] + """City name.""" + + country: str + """Two-letter ISO 3166-1 country code, for example `US`, `DE`, or `GB`.""" + + line1: str + """First line of the street address.""" + + line2: Optional[str] + """Second line of the street address.""" + + postal_code: Optional[str] + """Postal or ZIP code.""" + + state: Optional[str] + """State, province, or region code, for example `CA`.""" + + +class TaxIdentifier(TypedDict, total=False): + tax_id_type: Required[ + Literal[ + "ad_nrt", + "ao_tin", + "ar_cuit", + "al_tin", + "am_tin", + "aw_tin", + "au_abn", + "au_arn", + "eu_vat", + "az_tin", + "bs_tin", + "bh_vat", + "bd_bin", + "bb_tin", + "by_tin", + "bj_ifu", + "bo_tin", + "ba_tin", + "br_cnpj", + "br_cpf", + "bg_uic", + "bf_ifu", + "kh_tin", + "cm_niu", + "ca_bn", + "ca_gst_hst", + "ca_pst_bc", + "ca_pst_mb", + "ca_pst_sk", + "ca_qst", + "cv_nif", + "cl_tin", + "cn_tin", + "co_nit", + "cd_nif", + "cr_tin", + "hr_oib", + "do_rcn", + "ec_ruc", + "eg_tin", + "sv_nit", + "et_tin", + "eu_oss_vat", + "ge_vat", + "gh_tin", + "de_stn", + "gb_vat", + "gn_nif", + "hk_br", + "hu_tin", + "is_vat", + "in_gst", + "id_npwp", + "il_vat", + "jp_cn", + "jp_rn", + "jp_trn", + "kz_bin", + "ke_pin", + "kg_tin", + "la_tin", + "li_uid", + "li_vat", + "my_frp", + "my_itn", + "my_sst", + "mr_nif", + "mx_rfc", + "md_vat", + "me_pib", + "ma_vat", + "np_pan", + "nz_gst", + "ng_tin", + "mk_vat", + "no_vat", + "no_voec", + "om_vat", + "pe_ruc", + "ph_tin", + "ro_tin", + "ru_inn", + "ru_kpp", + "sa_vat", + "sn_ninea", + "rs_pib", + "sg_gst", + "sg_uen", + "si_tin", + "za_vat", + "kr_brn", + "es_cif", + "ch_uid", + "ch_vat", + "tw_vat", + "tj_tin", + "tz_vat", + "th_vat", + "tr_tin", + "ug_tin", + "ua_vat", + "ae_trn", + "us_ein", + "uy_ruc", + "uz_tin", + "uz_vat", + "ve_rif", + "vn_tin", + "zm_tin", + "zw_tin", + "sr_fin", + ] + ] + """Tax ID type, for example `eu_vat`, `gb_vat`, or `us_ein`.""" + + tax_id_value: Required[str] + """Tax ID value, for example `DE123456789`.""" diff --git a/src/whop_sdk/types/plan_calculate_tax_params.py b/src/whop_sdk/types/plan_calculate_tax_params.py index 1e9f5eb5..7e7c98e5 100644 --- a/src/whop_sdk/types/plan_calculate_tax_params.py +++ b/src/whop_sdk/types/plan_calculate_tax_params.py @@ -3,7 +3,7 @@ from __future__ import annotations from typing import Iterable, Optional -from typing_extensions import Required, TypedDict +from typing_extensions import Literal, Required, TypedDict __all__ = ["PlanCalculateTaxParams", "Address", "TaxID"] @@ -48,7 +48,119 @@ class Address(TypedDict, total=False): class TaxID(TypedDict, total=False): - type: str + type: Literal[ + "ad_nrt", + "ao_tin", + "ar_cuit", + "al_tin", + "am_tin", + "aw_tin", + "au_abn", + "au_arn", + "eu_vat", + "az_tin", + "bs_tin", + "bh_vat", + "bd_bin", + "bb_tin", + "by_tin", + "bj_ifu", + "bo_tin", + "ba_tin", + "br_cnpj", + "br_cpf", + "bg_uic", + "bf_ifu", + "kh_tin", + "cm_niu", + "ca_bn", + "ca_gst_hst", + "ca_pst_bc", + "ca_pst_mb", + "ca_pst_sk", + "ca_qst", + "cv_nif", + "cl_tin", + "cn_tin", + "co_nit", + "cd_nif", + "cr_tin", + "hr_oib", + "do_rcn", + "ec_ruc", + "eg_tin", + "sv_nit", + "et_tin", + "eu_oss_vat", + "ge_vat", + "gh_tin", + "de_stn", + "gb_vat", + "gn_nif", + "hk_br", + "hu_tin", + "is_vat", + "in_gst", + "id_npwp", + "il_vat", + "jp_cn", + "jp_rn", + "jp_trn", + "kz_bin", + "ke_pin", + "kg_tin", + "la_tin", + "li_uid", + "li_vat", + "my_frp", + "my_itn", + "my_sst", + "mr_nif", + "mx_rfc", + "md_vat", + "me_pib", + "ma_vat", + "np_pan", + "nz_gst", + "ng_tin", + "mk_vat", + "no_vat", + "no_voec", + "om_vat", + "pe_ruc", + "ph_tin", + "ro_tin", + "ru_inn", + "ru_kpp", + "sa_vat", + "sn_ninea", + "rs_pib", + "sg_gst", + "sg_uen", + "si_tin", + "za_vat", + "kr_brn", + "es_cif", + "ch_uid", + "ch_vat", + "tw_vat", + "tj_tin", + "tz_vat", + "th_vat", + "tr_tin", + "ug_tin", + "ua_vat", + "ae_trn", + "us_ein", + "uy_ruc", + "uz_tin", + "uz_vat", + "ve_rif", + "vn_tin", + "zm_tin", + "zw_tin", + "sr_fin", + ] """Tax ID type, for example `eu_vat`.""" value: str diff --git a/tests/api_resources/test_accounts.py b/tests/api_resources/test_accounts.py index f7862867..51d5ab34 100644 --- a/tests/api_resources/test_accounts.py +++ b/tests/api_resources/test_accounts.py @@ -113,6 +113,14 @@ def test_method_update_with_all_params(self, client: Whop) -> None: affiliate_application_required=True, affiliate_instructions="affiliate_instructions", banner_image={"foo": "bar"}, + business_address={ + "city": "city", + "country": "country", + "line1": "line1", + "line2": "line2", + "postal_code": "postal_code", + "state": "state", + }, business_type="business_type", country="country", description="description", @@ -128,6 +136,7 @@ def test_method_update_with_all_params(self, client: Whop) -> None: opengraph_image_variant="opengraph_image_variant", other_business_description="other_business_description", other_industry_description="other_industry_description", + product_tax_code_id="product_tax_code_id", require_2fa=True, route="route", send_customer_emails=True, @@ -137,6 +146,13 @@ def test_method_update_with_all_params(self, client: Whop) -> None: social_links=[{"foo": "bar"}], store_page_config={"foo": "bar"}, target_audience="target_audience", + tax_identifiers=[ + { + "tax_id_type": "ad_nrt", + "tax_id_value": "tax_id_value", + } + ], + tax_remitted_by="whop", title="title", use_logo_as_opengraph_image_fallback=True, ) @@ -346,6 +362,14 @@ async def test_method_update_with_all_params(self, async_client: AsyncWhop) -> N affiliate_application_required=True, affiliate_instructions="affiliate_instructions", banner_image={"foo": "bar"}, + business_address={ + "city": "city", + "country": "country", + "line1": "line1", + "line2": "line2", + "postal_code": "postal_code", + "state": "state", + }, business_type="business_type", country="country", description="description", @@ -361,6 +385,7 @@ async def test_method_update_with_all_params(self, async_client: AsyncWhop) -> N opengraph_image_variant="opengraph_image_variant", other_business_description="other_business_description", other_industry_description="other_industry_description", + product_tax_code_id="product_tax_code_id", require_2fa=True, route="route", send_customer_emails=True, @@ -370,6 +395,13 @@ async def test_method_update_with_all_params(self, async_client: AsyncWhop) -> N social_links=[{"foo": "bar"}], store_page_config={"foo": "bar"}, target_audience="target_audience", + tax_identifiers=[ + { + "tax_id_type": "ad_nrt", + "tax_id_value": "tax_id_value", + } + ], + tax_remitted_by="whop", title="title", use_logo_as_opengraph_image_fallback=True, ) diff --git a/tests/api_resources/test_plans.py b/tests/api_resources/test_plans.py index 32e2cec2..cc6f9788 100644 --- a/tests/api_resources/test_plans.py +++ b/tests/api_resources/test_plans.py @@ -352,7 +352,7 @@ def test_method_calculate_tax_with_all_params(self, client: Whop) -> None: ip_address="ip_address", tax_ids=[ { - "type": "type", + "type": "ad_nrt", "value": "value", } ], @@ -728,7 +728,7 @@ async def test_method_calculate_tax_with_all_params(self, async_client: AsyncWho ip_address="ip_address", tax_ids=[ { - "type": "type", + "type": "ad_nrt", "value": "value", } ], From f16d61c7247163bdedc276e5a770665a01030051 Mon Sep 17 00:00:00 2001 From: stlc-bot Date: Mon, 29 Jun 2026 23:39:59 +0000 Subject: [PATCH 091/109] Improve Cards API resource copy Stainless-Generated-From: 77920649a6df7302a364f6c6afea7b8ca6f9a75f --- src/whop_sdk/resources/cards.py | 40 ++++++++++---------- src/whop_sdk/resources/payouts.py | 22 +++++------ src/whop_sdk/types/card_create_response.py | 20 +++++----- src/whop_sdk/types/card_list_response.py | 20 +++++----- src/whop_sdk/types/card_retrieve_response.py | 20 +++++----- 5 files changed, 60 insertions(+), 62 deletions(-) diff --git a/src/whop_sdk/resources/cards.py b/src/whop_sdk/resources/cards.py index da7ecab0..6cf50577 100644 --- a/src/whop_sdk/resources/cards.py +++ b/src/whop_sdk/resources/cards.py @@ -68,9 +68,9 @@ def create( card is issued to the account's own cardholder. For a company (business) card issuing account, pass assigned*user_id to issue the card to a company member; if that member is not yet an approved card-issuing user, the card is provisioned - asynchronously or an onboarding invitation is sent (HTTP 202). The ledger's - owner is passed as exactly one of account_id (a biz* identifier) or user*id (a - user* identifier). Returns the newly created card resource. + asynchronously or an onboarding invitation is sent (HTTP 202). Pass exactly one + of account_id (a biz* identifier) or user*id (a user* identifier). Returns the + newly created card resource. Args: account_id: The owning account ID (a biz\\__ identifier). Provide this or user_id. @@ -179,13 +179,13 @@ def list( timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> CardListResponse: """ - Lists the issued (Whop Card) virtual and physical cards for a ledger account, - including pending invitation cards that have not been issued by the card - provider yet. The ledger's owner is passed as exactly one of account*id (a biz* - identifier) or user*id (a user* identifier). Non-owner team members only see - cards assigned to them. Users without the payout:account:read scope can still - list cards assigned to them (for example moderators or external cardholders). - Use GET /cards/:card_id to retrieve a single card with its secrets. + Lists issued Whop virtual cards for an account or user, including pending + invitation cards that have not been issued by the card provider yet. Pass + exactly one of account*id (a biz* identifier) or user*id (a user* identifier). + Non-owner team members only see cards assigned to them. Users without the + payout:account:read scope can still list cards assigned to them (for example + moderators or external cardholders). Use GET /cards/:card_id to retrieve a + single card with its secrets. Args: account_id: The owning account ID (a biz\\__ identifier). Provide this or user_id. @@ -262,9 +262,9 @@ async def create( card is issued to the account's own cardholder. For a company (business) card issuing account, pass assigned*user_id to issue the card to a company member; if that member is not yet an approved card-issuing user, the card is provisioned - asynchronously or an onboarding invitation is sent (HTTP 202). The ledger's - owner is passed as exactly one of account_id (a biz* identifier) or user*id (a - user* identifier). Returns the newly created card resource. + asynchronously or an onboarding invitation is sent (HTTP 202). Pass exactly one + of account_id (a biz* identifier) or user*id (a user* identifier). Returns the + newly created card resource. Args: account_id: The owning account ID (a biz\\__ identifier). Provide this or user_id. @@ -373,13 +373,13 @@ async def list( timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> CardListResponse: """ - Lists the issued (Whop Card) virtual and physical cards for a ledger account, - including pending invitation cards that have not been issued by the card - provider yet. The ledger's owner is passed as exactly one of account*id (a biz* - identifier) or user*id (a user* identifier). Non-owner team members only see - cards assigned to them. Users without the payout:account:read scope can still - list cards assigned to them (for example moderators or external cardholders). - Use GET /cards/:card_id to retrieve a single card with its secrets. + Lists issued Whop virtual cards for an account or user, including pending + invitation cards that have not been issued by the card provider yet. Pass + exactly one of account*id (a biz* identifier) or user*id (a user* identifier). + Non-owner team members only see cards assigned to them. Users without the + payout:account:read scope can still list cards assigned to them (for example + moderators or external cardholders). Use GET /cards/:card_id to retrieve a + single card with its secrets. Args: account_id: The owning account ID (a biz\\__ identifier). Provide this or user_id. diff --git a/src/whop_sdk/resources/payouts.py b/src/whop_sdk/resources/payouts.py index 90ec1ab1..bf2e03e0 100644 --- a/src/whop_sdk/resources/payouts.py +++ b/src/whop_sdk/resources/payouts.py @@ -59,12 +59,11 @@ def list( extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> SyncCursorPage[PayoutListResponse]: - """Lists payouts (withdrawal requests) for a ledger account, most recent first. - - The - ledger's owner is passed as exactly one of account*id (a biz* identifier) or - user*id (a user* identifier). The saved payout method on each payout - additionally requires the payout:destination:read scope and is null without it. + """ + Lists payouts (withdrawal requests) for an account or user, most recent first. + Pass exactly one of account*id (a biz* identifier) or user*id (a user* + identifier). The saved payout method on each payout additionally requires the + payout:destination:read scope and is null without it. Args: account_id: The owning account ID (a biz\\__ identifier). Provide this or user_id. @@ -151,12 +150,11 @@ def list( extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> AsyncPaginator[PayoutListResponse, AsyncCursorPage[PayoutListResponse]]: - """Lists payouts (withdrawal requests) for a ledger account, most recent first. - - The - ledger's owner is passed as exactly one of account*id (a biz* identifier) or - user*id (a user* identifier). The saved payout method on each payout - additionally requires the payout:destination:read scope and is null without it. + """ + Lists payouts (withdrawal requests) for an account or user, most recent first. + Pass exactly one of account*id (a biz* identifier) or user*id (a user* + identifier). The saved payout method on each payout additionally requires the + payout:destination:read scope and is null without it. Args: account_id: The owning account ID (a biz\\__ identifier). Provide this or user_id. diff --git a/src/whop_sdk/types/card_create_response.py b/src/whop_sdk/types/card_create_response.py index 0afd6612..9f4db74c 100644 --- a/src/whop_sdk/types/card_create_response.py +++ b/src/whop_sdk/types/card_create_response.py @@ -42,24 +42,24 @@ class Limit(BaseModel): class Secrets(BaseModel): - """The card's sensitive details. + """Sensitive card details. - Only present on GET /cards/:card_id (retrieve); null for cards that are not active or whose details could not be retrieved. + Present only on `GET /cards/:card_id` for active cards; `null` when the card is inactive or details cannot be retrieved. """ card_number: str - """The full card number.""" + """Full card number.""" cvc: str - """The card verification code.""" + """Card verification code.""" name_on_card: Optional[str] = None - """The cardholder name printed on the card.""" + """Cardholder name printed on the card.""" class CardCreateResponse(BaseModel): id: str - """The icrd\\__ identifier of the card.""" + """Card ID, prefixed `icrd_`.""" billing: Optional[Billing] = None """The billing address.""" @@ -97,11 +97,11 @@ class CardCreateResponse(BaseModel): """The card type.""" user_id: Optional[str] = None - """The user\\__ identifier of the cardholder, when assigned.""" + """Cardholder user ID, prefixed `user_`, when assigned.""" secrets: Optional[Secrets] = None - """The card's sensitive details. + """Sensitive card details. - Only present on GET /cards/:card_id (retrieve); null for cards that are not - active or whose details could not be retrieved. + Present only on `GET /cards/:card_id` for active cards; `null` when the card is + inactive or details cannot be retrieved. """ diff --git a/src/whop_sdk/types/card_list_response.py b/src/whop_sdk/types/card_list_response.py index d6c0530d..69ed575f 100644 --- a/src/whop_sdk/types/card_list_response.py +++ b/src/whop_sdk/types/card_list_response.py @@ -42,24 +42,24 @@ class DataLimit(BaseModel): class DataSecrets(BaseModel): - """The card's sensitive details. + """Sensitive card details. - Only present on GET /cards/:card_id (retrieve); null for cards that are not active or whose details could not be retrieved. + Present only on `GET /cards/:card_id` for active cards; `null` when the card is inactive or details cannot be retrieved. """ card_number: str - """The full card number.""" + """Full card number.""" cvc: str - """The card verification code.""" + """Card verification code.""" name_on_card: Optional[str] = None - """The cardholder name printed on the card.""" + """Cardholder name printed on the card.""" class Data(BaseModel): id: str - """The icrd\\__ identifier of the card.""" + """Card ID, prefixed `icrd_`.""" billing: Optional[DataBilling] = None """The billing address.""" @@ -97,13 +97,13 @@ class Data(BaseModel): """The card type.""" user_id: Optional[str] = None - """The user\\__ identifier of the cardholder, when assigned.""" + """Cardholder user ID, prefixed `user_`, when assigned.""" secrets: Optional[DataSecrets] = None - """The card's sensitive details. + """Sensitive card details. - Only present on GET /cards/:card_id (retrieve); null for cards that are not - active or whose details could not be retrieved. + Present only on `GET /cards/:card_id` for active cards; `null` when the card is + inactive or details cannot be retrieved. """ diff --git a/src/whop_sdk/types/card_retrieve_response.py b/src/whop_sdk/types/card_retrieve_response.py index 8cf6786f..9e1cb9e0 100644 --- a/src/whop_sdk/types/card_retrieve_response.py +++ b/src/whop_sdk/types/card_retrieve_response.py @@ -42,24 +42,24 @@ class Limit(BaseModel): class Secrets(BaseModel): - """The card's sensitive details. + """Sensitive card details. - Only present on GET /cards/:card_id (retrieve); null for cards that are not active or whose details could not be retrieved. + Present only on `GET /cards/:card_id` for active cards; `null` when the card is inactive or details cannot be retrieved. """ card_number: str - """The full card number.""" + """Full card number.""" cvc: str - """The card verification code.""" + """Card verification code.""" name_on_card: Optional[str] = None - """The cardholder name printed on the card.""" + """Cardholder name printed on the card.""" class CardRetrieveResponse(BaseModel): id: str - """The icrd\\__ identifier of the card.""" + """Card ID, prefixed `icrd_`.""" billing: Optional[Billing] = None """The billing address.""" @@ -97,11 +97,11 @@ class CardRetrieveResponse(BaseModel): """The card type.""" user_id: Optional[str] = None - """The user\\__ identifier of the cardholder, when assigned.""" + """Cardholder user ID, prefixed `user_`, when assigned.""" secrets: Optional[Secrets] = None - """The card's sensitive details. + """Sensitive card details. - Only present on GET /cards/:card_id (retrieve); null for cards that are not - active or whose details could not be retrieved. + Present only on `GET /cards/:card_id` for active cards; `null` when the card is + inactive or details cannot be retrieved. """ From 856629543adb7df0f8e55f134969d2214662899f Mon Sep 17 00:00:00 2001 From: stlc-bot Date: Tue, 30 Jun 2026 08:54:03 +0000 Subject: [PATCH 092/109] feat(platform-api): unified Stats API Stainless-Generated-From: eb42fd8d12adf47dfe60a2ca9ff2aec38ead1e7d --- .stats.yml | 2 +- api.md | 13 + src/whop_sdk/_client.py | 44 +++ src/whop_sdk/resources/__init__.py | 14 + src/whop_sdk/resources/stats.py | 358 +++++++++++++++++++ src/whop_sdk/types/__init__.py | 3 + src/whop_sdk/types/stat_list_response.py | 42 +++ src/whop_sdk/types/stat_retrieve_params.py | 73 ++++ src/whop_sdk/types/stat_retrieve_response.py | 35 ++ tests/api_resources/test_stats.py | 227 ++++++++++++ 10 files changed, 810 insertions(+), 1 deletion(-) create mode 100644 src/whop_sdk/resources/stats.py create mode 100644 src/whop_sdk/types/stat_list_response.py create mode 100644 src/whop_sdk/types/stat_retrieve_params.py create mode 100644 src/whop_sdk/types/stat_retrieve_response.py create mode 100644 tests/api_resources/test_stats.py diff --git a/.stats.yml b/.stats.yml index db503358..a78b7562 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1 +1 @@ -configured_endpoints: 254 +configured_endpoints: 256 diff --git a/api.md b/api.md index 6ab6faac..27d8149a 100644 --- a/api.md +++ b/api.md @@ -783,6 +783,19 @@ Methods: - client.financial_activity.list(\*\*params) -> FinancialActivityListResponse +# Stats + +Types: + +```python +from whop_sdk.types import StatRetrieveResponse, StatListResponse +``` + +Methods: + +- client.stats.retrieve(metric, \*\*params) -> StatRetrieveResponse +- client.stats.list() -> StatListResponse + # Payouts Types: diff --git a/src/whop_sdk/_client.py b/src/whop_sdk/_client.py index c6cbe6ad..31a748a6 100644 --- a/src/whop_sdk/_client.py +++ b/src/whop_sdk/_client.py @@ -42,6 +42,7 @@ files, leads, plans, + stats, swaps, users, forums, @@ -111,6 +112,7 @@ 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.swaps import SwapsResource, AsyncSwapsResource from .resources.users import UsersResource, AsyncUsersResource from .resources.forums import ForumsResource, AsyncForumsResource @@ -548,6 +550,13 @@ def financial_activity(self) -> FinancialActivityResource: return FinancialActivityResource(self) + @cached_property + def stats(self) -> StatsResource: + """Stats""" + from .resources.stats import StatsResource + + return StatsResource(self) + @cached_property def payouts(self) -> PayoutsResource: from .resources.payouts import PayoutsResource @@ -1215,6 +1224,13 @@ def financial_activity(self) -> AsyncFinancialActivityResource: return AsyncFinancialActivityResource(self) + @cached_property + def stats(self) -> AsyncStatsResource: + """Stats""" + from .resources.stats import AsyncStatsResource + + return AsyncStatsResource(self) + @cached_property def payouts(self) -> AsyncPayoutsResource: from .resources.payouts import AsyncPayoutsResource @@ -1802,6 +1818,13 @@ def financial_activity(self) -> financial_activity.FinancialActivityResourceWith return FinancialActivityResourceWithRawResponse(self._client.financial_activity) + @cached_property + def stats(self) -> stats.StatsResourceWithRawResponse: + """Stats""" + from .resources.stats import StatsResourceWithRawResponse + + return StatsResourceWithRawResponse(self._client.stats) + @cached_property def payouts(self) -> payouts.PayoutsResourceWithRawResponse: from .resources.payouts import PayoutsResourceWithRawResponse @@ -2271,6 +2294,13 @@ def financial_activity(self) -> financial_activity.AsyncFinancialActivityResourc return AsyncFinancialActivityResourceWithRawResponse(self._client.financial_activity) + @cached_property + def stats(self) -> stats.AsyncStatsResourceWithRawResponse: + """Stats""" + from .resources.stats import AsyncStatsResourceWithRawResponse + + return AsyncStatsResourceWithRawResponse(self._client.stats) + @cached_property def payouts(self) -> payouts.AsyncPayoutsResourceWithRawResponse: from .resources.payouts import AsyncPayoutsResourceWithRawResponse @@ -2742,6 +2772,13 @@ def financial_activity(self) -> financial_activity.FinancialActivityResourceWith return FinancialActivityResourceWithStreamingResponse(self._client.financial_activity) + @cached_property + def stats(self) -> stats.StatsResourceWithStreamingResponse: + """Stats""" + from .resources.stats import StatsResourceWithStreamingResponse + + return StatsResourceWithStreamingResponse(self._client.stats) + @cached_property def payouts(self) -> payouts.PayoutsResourceWithStreamingResponse: from .resources.payouts import PayoutsResourceWithStreamingResponse @@ -3215,6 +3252,13 @@ def financial_activity(self) -> financial_activity.AsyncFinancialActivityResourc return AsyncFinancialActivityResourceWithStreamingResponse(self._client.financial_activity) + @cached_property + def stats(self) -> stats.AsyncStatsResourceWithStreamingResponse: + """Stats""" + from .resources.stats import AsyncStatsResourceWithStreamingResponse + + return AsyncStatsResourceWithStreamingResponse(self._client.stats) + @cached_property def payouts(self) -> payouts.AsyncPayoutsResourceWithStreamingResponse: from .resources.payouts import AsyncPayoutsResourceWithStreamingResponse diff --git a/src/whop_sdk/resources/__init__.py b/src/whop_sdk/resources/__init__.py index 224978f6..e3013e4e 100644 --- a/src/whop_sdk/resources/__init__.py +++ b/src/whop_sdk/resources/__init__.py @@ -48,6 +48,14 @@ PlansResourceWithStreamingResponse, AsyncPlansResourceWithStreamingResponse, ) +from .stats import ( + StatsResource, + AsyncStatsResource, + StatsResourceWithRawResponse, + AsyncStatsResourceWithRawResponse, + StatsResourceWithStreamingResponse, + AsyncStatsResourceWithStreamingResponse, +) from .swaps import ( SwapsResource, AsyncSwapsResource, @@ -792,6 +800,12 @@ "AsyncFinancialActivityResourceWithRawResponse", "FinancialActivityResourceWithStreamingResponse", "AsyncFinancialActivityResourceWithStreamingResponse", + "StatsResource", + "AsyncStatsResource", + "StatsResourceWithRawResponse", + "AsyncStatsResourceWithRawResponse", + "StatsResourceWithStreamingResponse", + "AsyncStatsResourceWithStreamingResponse", "PayoutsResource", "AsyncPayoutsResource", "PayoutsResourceWithRawResponse", diff --git a/src/whop_sdk/resources/stats.py b/src/whop_sdk/resources/stats.py new file mode 100644 index 00000000..73848ba2 --- /dev/null +++ b/src/whop_sdk/resources/stats.py @@ -0,0 +1,358 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Union +from datetime import date +from typing_extensions import Literal + +import httpx + +from ..types import stat_retrieve_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 .._base_client import make_request_options +from ..types.stat_list_response import StatListResponse +from ..types.stat_retrieve_response import StatRetrieveResponse + +__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 retrieve( + self, + metric: str, + *, + account_id: str, + from_: Union[str, date], + to: Union[str, date], + breakdown_by: str | Omit = omit, + card_network: str | Omit = omit, + convert_to: str | Omit = omit, + currency: str | Omit = omit, + interval: Literal["hour", "day", "week", "month"] | Omit = omit, + payment_method: str | Omit = omit, + snapshot_window: Literal["30d"] | Omit = omit, + time_zone: 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, + ) -> StatRetrieveResponse: + """ + Retrieves a metric as a time series of points for an account over a date range. + + Args: + account_id: The account to measure, for example biz_AbC123. + + from_: Start of the date range (YYYY-MM-DD). + + to: End of the date range (YYYY-MM-DD). + + breakdown_by: Split the metric out by one of its properties — each point gets a breakdown + array. For example breakdown_by=currency returns an entry for usd, an entry for + eur, and so on. + + card_network: Filter to a single card brand, for example visa. A refinement of + payment_method=card. Available on metrics that list card_network. + + convert_to: Display currency for money metrics — every amount is converted into this ISO + currency using the exchange rate on each period's date. Defaults to usd. Ignored + when you filter or break down by currency (those report the original transaction + currency, unconverted). + + currency: Filter to transactions made in this original ISO currency, for example eur — + reported in that currency, not converted. Pair with breakdown_by=currency to + split a metric by currency. Available on metrics that list currency. + + interval: How wide each point is. Defaults to day. Snapshot metrics are day-only. + + payment_method: Filter to a single payment method, for example card or crypto. Available on + metrics that list payment_method. + + snapshot_window: Trailing window for snapshot metrics. Only accepted by snapshot metrics (each + lists its allowed windows in the catalog); defaults to the metric's first + supported window. Only 30d today. + + time_zone: IANA time zone to bucket the series in, for example America/New_York. Defaults + to UTC. Not accepted by snapshot metrics, which are UTC only. + + 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 metric: + raise ValueError(f"Expected a non-empty value for `metric` but received {metric!r}") + return self._get( + path_template("/stats/{metric}", metric=metric), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "account_id": account_id, + "from_": from_, + "to": to, + "breakdown_by": breakdown_by, + "card_network": card_network, + "convert_to": convert_to, + "currency": currency, + "interval": interval, + "payment_method": payment_method, + "snapshot_window": snapshot_window, + "time_zone": time_zone, + }, + stat_retrieve_params.StatRetrieveParams, + ), + ), + cast_to=StatRetrieveResponse, + ) + + def list( + self, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> StatListResponse: + """ + Lists every metric you can query, with its unit and the properties you can + filter or break it down by. + """ + return self._get( + "/stats", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=StatListResponse, + ) + + +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 retrieve( + self, + metric: str, + *, + account_id: str, + from_: Union[str, date], + to: Union[str, date], + breakdown_by: str | Omit = omit, + card_network: str | Omit = omit, + convert_to: str | Omit = omit, + currency: str | Omit = omit, + interval: Literal["hour", "day", "week", "month"] | Omit = omit, + payment_method: str | Omit = omit, + snapshot_window: Literal["30d"] | Omit = omit, + time_zone: 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, + ) -> StatRetrieveResponse: + """ + Retrieves a metric as a time series of points for an account over a date range. + + Args: + account_id: The account to measure, for example biz_AbC123. + + from_: Start of the date range (YYYY-MM-DD). + + to: End of the date range (YYYY-MM-DD). + + breakdown_by: Split the metric out by one of its properties — each point gets a breakdown + array. For example breakdown_by=currency returns an entry for usd, an entry for + eur, and so on. + + card_network: Filter to a single card brand, for example visa. A refinement of + payment_method=card. Available on metrics that list card_network. + + convert_to: Display currency for money metrics — every amount is converted into this ISO + currency using the exchange rate on each period's date. Defaults to usd. Ignored + when you filter or break down by currency (those report the original transaction + currency, unconverted). + + currency: Filter to transactions made in this original ISO currency, for example eur — + reported in that currency, not converted. Pair with breakdown_by=currency to + split a metric by currency. Available on metrics that list currency. + + interval: How wide each point is. Defaults to day. Snapshot metrics are day-only. + + payment_method: Filter to a single payment method, for example card or crypto. Available on + metrics that list payment_method. + + snapshot_window: Trailing window for snapshot metrics. Only accepted by snapshot metrics (each + lists its allowed windows in the catalog); defaults to the metric's first + supported window. Only 30d today. + + time_zone: IANA time zone to bucket the series in, for example America/New_York. Defaults + to UTC. Not accepted by snapshot metrics, which are UTC only. + + 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 metric: + raise ValueError(f"Expected a non-empty value for `metric` but received {metric!r}") + return await self._get( + path_template("/stats/{metric}", metric=metric), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=await async_maybe_transform( + { + "account_id": account_id, + "from_": from_, + "to": to, + "breakdown_by": breakdown_by, + "card_network": card_network, + "convert_to": convert_to, + "currency": currency, + "interval": interval, + "payment_method": payment_method, + "snapshot_window": snapshot_window, + "time_zone": time_zone, + }, + stat_retrieve_params.StatRetrieveParams, + ), + ), + cast_to=StatRetrieveResponse, + ) + + async def list( + self, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> StatListResponse: + """ + Lists every metric you can query, with its unit and the properties you can + filter or break it down by. + """ + return await self._get( + "/stats", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=StatListResponse, + ) + + +class StatsResourceWithRawResponse: + def __init__(self, stats: StatsResource) -> None: + self._stats = stats + + self.retrieve = to_raw_response_wrapper( + stats.retrieve, + ) + self.list = to_raw_response_wrapper( + stats.list, + ) + + +class AsyncStatsResourceWithRawResponse: + def __init__(self, stats: AsyncStatsResource) -> None: + self._stats = stats + + self.retrieve = async_to_raw_response_wrapper( + stats.retrieve, + ) + self.list = async_to_raw_response_wrapper( + stats.list, + ) + + +class StatsResourceWithStreamingResponse: + def __init__(self, stats: StatsResource) -> None: + self._stats = stats + + self.retrieve = to_streamed_response_wrapper( + stats.retrieve, + ) + self.list = to_streamed_response_wrapper( + stats.list, + ) + + +class AsyncStatsResourceWithStreamingResponse: + def __init__(self, stats: AsyncStatsResource) -> None: + self._stats = stats + + self.retrieve = async_to_streamed_response_wrapper( + stats.retrieve, + ) + self.list = async_to_streamed_response_wrapper( + stats.list, + ) diff --git a/src/whop_sdk/types/__init__.py b/src/whop_sdk/types/__init__.py index e8e4d43a..689b0049 100644 --- a/src/whop_sdk/types/__init__.py +++ b/src/whop_sdk/types/__init__.py @@ -140,6 +140,7 @@ from .plan_update_params import PlanUpdateParams as PlanUpdateParams from .refund_list_params import RefundListParams as RefundListParams from .review_list_params import ReviewListParams as ReviewListParams +from .stat_list_response import StatListResponse as StatListResponse from .swap_create_params import SwapCreateParams as SwapCreateParams from .swap_list_response import SwapListResponse as SwapListResponse from .user_update_params import UserUpdateParams as UserUpdateParams @@ -184,6 +185,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_retrieve_params import StatRetrieveParams as StatRetrieveParams from .swap_create_response import SwapCreateResponse as SwapCreateResponse from .transfer_list_params import TransferListParams as TransferListParams from .unwrap_webhook_event import UnwrapWebhookEvent as UnwrapWebhookEvent @@ -240,6 +242,7 @@ 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_retrieve_response import StatRetrieveResponse as StatRetrieveResponse from .swap_retrieve_response import SwapRetrieveResponse as SwapRetrieveResponse from .transfer_create_params import TransferCreateParams as TransferCreateParams from .transfer_list_response import TransferListResponse as TransferListResponse diff --git a/src/whop_sdk/types/stat_list_response.py b/src/whop_sdk/types/stat_list_response.py new file mode 100644 index 00000000..f8f50620 --- /dev/null +++ b/src/whop_sdk/types/stat_list_response.py @@ -0,0 +1,42 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Optional +from typing_extensions import Literal + +from .._models import BaseModel + +__all__ = ["StatListResponse", "Data"] + + +class Data(BaseModel): + description: str + """A short description of what the metric measures.""" + + key: str + """The metric's key. Pass it to GET /stats/{metric} to query its values.""" + + name: str + """Human-readable display name for the metric.""" + + properties: List[str] + """ + The properties you can use with this metric — pass one as a filter + (property=value) to narrow the series, or as breakdown_by=property to split it. + """ + + unit: Literal["count", "currency", "percent"] + """ + How to read the metric's values: count is an integer, currency is a decimal + amount, and percent is a number where 1.6 means 1.6%. + """ + + windows: Optional[List[str]] = None + """ + Snapshot metrics only: the trailing windows you can pass as snapshot_window, for + example 30d. Absent on live metrics, which use from/to instead. + """ + + +class StatListResponse(BaseModel): + data: List[Data] + """The available metrics.""" diff --git a/src/whop_sdk/types/stat_retrieve_params.py b/src/whop_sdk/types/stat_retrieve_params.py new file mode 100644 index 00000000..68417c71 --- /dev/null +++ b/src/whop_sdk/types/stat_retrieve_params.py @@ -0,0 +1,73 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Union +from datetime import date +from typing_extensions import Literal, Required, Annotated, TypedDict + +from .._utils import PropertyInfo + +__all__ = ["StatRetrieveParams"] + + +class StatRetrieveParams(TypedDict, total=False): + account_id: Required[str] + """The account to measure, for example biz_AbC123.""" + + from_: Required[Annotated[Union[str, date], PropertyInfo(alias="from", format="iso8601")]] + """Start of the date range (YYYY-MM-DD).""" + + to: Required[Annotated[Union[str, date], PropertyInfo(format="iso8601")]] + """End of the date range (YYYY-MM-DD).""" + + breakdown_by: str + """ + Split the metric out by one of its properties — each point gets a breakdown + array. For example breakdown_by=currency returns an entry for usd, an entry for + eur, and so on. + """ + + card_network: str + """Filter to a single card brand, for example visa. + + A refinement of payment_method=card. Available on metrics that list + card_network. + """ + + convert_to: str + """ + Display currency for money metrics — every amount is converted into this ISO + currency using the exchange rate on each period's date. Defaults to usd. Ignored + when you filter or break down by currency (those report the original transaction + currency, unconverted). + """ + + currency: str + """ + Filter to transactions made in this original ISO currency, for example eur — + reported in that currency, not converted. Pair with breakdown_by=currency to + split a metric by currency. Available on metrics that list currency. + """ + + interval: Literal["hour", "day", "week", "month"] + """How wide each point is. Defaults to day. Snapshot metrics are day-only.""" + + payment_method: str + """Filter to a single payment method, for example card or crypto. + + Available on metrics that list payment_method. + """ + + snapshot_window: Literal["30d"] + """Trailing window for snapshot metrics. + + Only accepted by snapshot metrics (each lists its allowed windows in the + catalog); defaults to the metric's first supported window. Only 30d today. + """ + + time_zone: str + """IANA time zone to bucket the series in, for example America/New_York. + + Defaults to UTC. Not accepted by snapshot metrics, which are UTC only. + """ diff --git a/src/whop_sdk/types/stat_retrieve_response.py b/src/whop_sdk/types/stat_retrieve_response.py new file mode 100644 index 00000000..72d0cfda --- /dev/null +++ b/src/whop_sdk/types/stat_retrieve_response.py @@ -0,0 +1,35 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Optional + +from .._models import BaseModel + +__all__ = ["StatRetrieveResponse", "Data", "DataPoint", "DataPointBreakdown"] + + +class DataPointBreakdown(BaseModel): + name: str + """The property value, for example usd or visa.""" + + value: Optional[float] = None + """The metric's value for this entry.""" + + +class DataPoint(BaseModel): + timestamp: int + """Unix timestamp (seconds) of the period start.""" + + value: Optional[float] = None + """The metric's value for this period, in the metric's unit.""" + + breakdown: Optional[List[DataPointBreakdown]] = None + """Present only when broken down: one entry per property value in this period.""" + + +class Data(BaseModel): + points: List[DataPoint] + """One entry per period, oldest first.""" + + +class StatRetrieveResponse(BaseModel): + data: Data diff --git a/tests/api_resources/test_stats.py b/tests/api_resources/test_stats.py new file mode 100644 index 00000000..55b0d5fb --- /dev/null +++ b/tests/api_resources/test_stats.py @@ -0,0 +1,227 @@ +# 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 StatListResponse, StatRetrieveResponse +from whop_sdk._utils import parse_date + +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_retrieve(self, client: Whop) -> None: + stat = client.stats.retrieve( + metric="metric", + account_id="account_id", + from_=parse_date("2019-12-27"), + to=parse_date("2019-12-27"), + ) + assert_matches_type(StatRetrieveResponse, stat, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_retrieve_with_all_params(self, client: Whop) -> None: + stat = client.stats.retrieve( + metric="metric", + account_id="account_id", + from_=parse_date("2019-12-27"), + to=parse_date("2019-12-27"), + breakdown_by="breakdown_by", + card_network="card_network", + convert_to="convert_to", + currency="currency", + interval="hour", + payment_method="payment_method", + snapshot_window="30d", + time_zone="time_zone", + ) + assert_matches_type(StatRetrieveResponse, stat, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_raw_response_retrieve(self, client: Whop) -> None: + response = client.stats.with_raw_response.retrieve( + metric="metric", + account_id="account_id", + from_=parse_date("2019-12-27"), + to=parse_date("2019-12-27"), + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + stat = response.parse() + assert_matches_type(StatRetrieveResponse, stat, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_streaming_response_retrieve(self, client: Whop) -> None: + with client.stats.with_streaming_response.retrieve( + metric="metric", + account_id="account_id", + from_=parse_date("2019-12-27"), + to=parse_date("2019-12-27"), + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + stat = response.parse() + assert_matches_type(StatRetrieveResponse, stat, 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 `metric` but received ''"): + client.stats.with_raw_response.retrieve( + metric="", + account_id="account_id", + from_=parse_date("2019-12-27"), + to=parse_date("2019-12-27"), + ) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_list(self, client: Whop) -> None: + stat = client.stats.list() + assert_matches_type(StatListResponse, stat, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_raw_response_list(self, client: Whop) -> None: + response = client.stats.with_raw_response.list() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + stat = response.parse() + assert_matches_type(StatListResponse, stat, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_streaming_response_list(self, client: Whop) -> None: + with client.stats.with_streaming_response.list() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + stat = response.parse() + assert_matches_type(StatListResponse, 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_retrieve(self, async_client: AsyncWhop) -> None: + stat = await async_client.stats.retrieve( + metric="metric", + account_id="account_id", + from_=parse_date("2019-12-27"), + to=parse_date("2019-12-27"), + ) + assert_matches_type(StatRetrieveResponse, stat, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_retrieve_with_all_params(self, async_client: AsyncWhop) -> None: + stat = await async_client.stats.retrieve( + metric="metric", + account_id="account_id", + from_=parse_date("2019-12-27"), + to=parse_date("2019-12-27"), + breakdown_by="breakdown_by", + card_network="card_network", + convert_to="convert_to", + currency="currency", + interval="hour", + payment_method="payment_method", + snapshot_window="30d", + time_zone="time_zone", + ) + assert_matches_type(StatRetrieveResponse, stat, 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.stats.with_raw_response.retrieve( + metric="metric", + account_id="account_id", + from_=parse_date("2019-12-27"), + to=parse_date("2019-12-27"), + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + stat = await response.parse() + assert_matches_type(StatRetrieveResponse, stat, 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.stats.with_streaming_response.retrieve( + metric="metric", + account_id="account_id", + from_=parse_date("2019-12-27"), + to=parse_date("2019-12-27"), + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + stat = await response.parse() + assert_matches_type(StatRetrieveResponse, stat, 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 `metric` but received ''"): + await async_client.stats.with_raw_response.retrieve( + metric="", + account_id="account_id", + from_=parse_date("2019-12-27"), + to=parse_date("2019-12-27"), + ) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_list(self, async_client: AsyncWhop) -> None: + stat = await async_client.stats.list() + assert_matches_type(StatListResponse, stat, 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.stats.with_raw_response.list() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + stat = await response.parse() + assert_matches_type(StatListResponse, stat, 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.stats.with_streaming_response.list() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + stat = await response.parse() + assert_matches_type(StatListResponse, stat, path=["response"]) + + assert cast(Any, response.is_closed) is True From 9aa9180271cbba2ef73d6e54bb09b900550f2c31 Mon Sep 17 00:00:00 2001 From: stlc-bot Date: Tue, 30 Jun 2026 19:46:02 +0000 Subject: [PATCH 093/109] feat(ads): campaign creation flow uses REST part 1 Stainless-Generated-From: a0b0696ad0a10daff3c6b2250d8e248f29028a9d --- .stats.yml | 2 +- api.md | 3 +- src/whop_sdk/resources/ad_campaigns.py | 153 ++++++++++++++- src/whop_sdk/resources/ad_groups.py | 178 +++++++++++++++--- src/whop_sdk/resources/ads.py | 4 +- src/whop_sdk/types/__init__.py | 1 + .../types/ad_campaign_create_params.py | 18 ++ .../types/ad_campaign_delete_response.py | 7 + .../types/ad_campaign_update_params.py | 11 +- src/whop_sdk/types/ad_group.py | 40 +++- src/whop_sdk/types/ad_group_create_params.py | 54 +++++- src/whop_sdk/types/ad_group_update_params.py | 48 ++++- tests/api_resources/test_ad_campaigns.py | 97 ++++++++++ tests/api_resources/test_ad_groups.py | 22 ++- 14 files changed, 578 insertions(+), 60 deletions(-) create mode 100644 src/whop_sdk/types/ad_campaign_delete_response.py diff --git a/.stats.yml b/.stats.yml index a78b7562..20b6c9e2 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1 +1 @@ -configured_endpoints: 256 +configured_endpoints: 257 diff --git a/api.md b/api.md index 27d8149a..db4d37ac 100644 --- a/api.md +++ b/api.md @@ -1199,7 +1199,7 @@ Methods: Types: ```python -from whop_sdk.types import AdCampaign +from whop_sdk.types import AdCampaign, AdCampaignDeleteResponse ``` Methods: @@ -1208,6 +1208,7 @@ Methods: - client.ad_campaigns.retrieve(id, \*\*params) -> AdCampaign - client.ad_campaigns.update(id, \*\*params) -> AdCampaign - client.ad_campaigns.list(\*\*params) -> SyncCursorPage[AdCampaign] +- client.ad_campaigns.delete(id) -> AdCampaignDeleteResponse - client.ad_campaigns.pause(id) -> AdCampaign - client.ad_campaigns.unpause(id) -> AdCampaign diff --git a/src/whop_sdk/resources/ad_campaigns.py b/src/whop_sdk/resources/ad_campaigns.py index 039e99b7..9dec1d82 100644 --- a/src/whop_sdk/resources/ad_campaigns.py +++ b/src/whop_sdk/resources/ad_campaigns.py @@ -26,6 +26,7 @@ from ..pagination import SyncCursorPage, AsyncCursorPage from .._base_client import AsyncPaginator, make_request_options from ..types.ad_campaign import AdCampaign +from ..types.ad_campaign_delete_response import AdCampaignDeleteResponse __all__ = ["AdCampaignsResource", "AsyncAdCampaignsResource"] @@ -57,10 +58,14 @@ def create( platform: Literal["meta"], title: str, account_id: str | Omit = omit, + bid_type: Literal["minimum_cost", "average_target", "maximum_target"] | Omit = omit, budget_amount: float | Omit = omit, budget_optimization: Literal["ad_campaign", "ad_group"] | Omit = omit, budget_type: Literal["daily", "lifetime"] | Omit = omit, + desired_cost_per_result: float | Omit = omit, + ends_at: str | Omit = omit, special_ad_categories: List[Literal["housing", "employment", "financial_products", "politics"]] | Omit = omit, + starts_at: 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, @@ -81,6 +86,9 @@ def create( account_id: The account to create the campaign under. Defaults to the account-scoped key's own account. + bid_type: CBO bid strategy: minimum_cost (lowest cost), average_target (cost cap), or + maximum_target (bid cap). CBO only. + budget_amount: The campaign budget, in USD. Required for CBO (budget_optimization: ad_campaign); omit for ABO. @@ -91,9 +99,16 @@ def create( budget_type: Whether the budget is spent per day or over the campaign's lifetime. Defaults to daily. + desired_cost_per_result: Target/cap cost per result in USD for average_target / maximum_target bidding. + CBO only. + + ends_at: Campaign schedule end (ISO 8601). CBO only. + special_ad_categories: Regulated categories the campaign falls under. Ads in these categories are subject to extra targeting restrictions. + starts_at: Campaign schedule start (ISO 8601). CBO only. + extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -110,10 +125,14 @@ def create( "platform": platform, "title": title, "account_id": account_id, + "bid_type": bid_type, "budget_amount": budget_amount, "budget_optimization": budget_optimization, "budget_type": budget_type, + "desired_cost_per_result": desired_cost_per_result, + "ends_at": ends_at, "special_ad_categories": special_ad_categories, + "starts_at": starts_at, }, ad_campaign_create_params.AdCampaignCreateParams, ), @@ -177,6 +196,8 @@ def update( id: str, *, budget_amount: float | Omit = omit, + ends_at: str | Omit = omit, + starts_at: str | Omit = omit, title: 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. @@ -185,11 +206,19 @@ def update( extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> AdCampaign: - """ - Updates an ad campaign's editable fields. + """Updates an ad campaign's editable fields (title, budget, schedule). + + Objective, + budget optimization, budget type, special ad categories, bid type and desired + cost per result are fixed at creation and cannot be changed. Args: - budget_amount: The campaign budget, in the account's currency. + budget_amount: The campaign budget, in the account's currency. Interpreted as daily or lifetime + per the campaign's existing budget type. + + ends_at: Campaign schedule end (ISO 8601). CBO only. + + starts_at: Campaign schedule start (ISO 8601). CBO only. title: The name of the campaign. @@ -208,6 +237,8 @@ def update( body=maybe_transform( { "budget_amount": budget_amount, + "ends_at": ends_at, + "starts_at": starts_at, "title": title, }, ad_campaign_update_params.AdCampaignUpdateParams, @@ -310,6 +341,40 @@ def list( model=AdCampaign, ) + 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, + ) -> AdCampaignDeleteResponse: + """ + Deletes an ad campaign and archives it on the ad platform (cascades to ad groups + and ads). Returns true on success. + + 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_campaigns/{id}", id=id), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=AdCampaignDeleteResponse, + ) + def pause( self, id: str, @@ -404,10 +469,14 @@ async def create( platform: Literal["meta"], title: str, account_id: str | Omit = omit, + bid_type: Literal["minimum_cost", "average_target", "maximum_target"] | Omit = omit, budget_amount: float | Omit = omit, budget_optimization: Literal["ad_campaign", "ad_group"] | Omit = omit, budget_type: Literal["daily", "lifetime"] | Omit = omit, + desired_cost_per_result: float | Omit = omit, + ends_at: str | Omit = omit, special_ad_categories: List[Literal["housing", "employment", "financial_products", "politics"]] | Omit = omit, + starts_at: 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, @@ -428,6 +497,9 @@ async def create( account_id: The account to create the campaign under. Defaults to the account-scoped key's own account. + bid_type: CBO bid strategy: minimum_cost (lowest cost), average_target (cost cap), or + maximum_target (bid cap). CBO only. + budget_amount: The campaign budget, in USD. Required for CBO (budget_optimization: ad_campaign); omit for ABO. @@ -438,9 +510,16 @@ async def create( budget_type: Whether the budget is spent per day or over the campaign's lifetime. Defaults to daily. + desired_cost_per_result: Target/cap cost per result in USD for average_target / maximum_target bidding. + CBO only. + + ends_at: Campaign schedule end (ISO 8601). CBO only. + special_ad_categories: Regulated categories the campaign falls under. Ads in these categories are subject to extra targeting restrictions. + starts_at: Campaign schedule start (ISO 8601). CBO only. + extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -457,10 +536,14 @@ async def create( "platform": platform, "title": title, "account_id": account_id, + "bid_type": bid_type, "budget_amount": budget_amount, "budget_optimization": budget_optimization, "budget_type": budget_type, + "desired_cost_per_result": desired_cost_per_result, + "ends_at": ends_at, "special_ad_categories": special_ad_categories, + "starts_at": starts_at, }, ad_campaign_create_params.AdCampaignCreateParams, ), @@ -524,6 +607,8 @@ async def update( id: str, *, budget_amount: float | Omit = omit, + ends_at: str | Omit = omit, + starts_at: str | Omit = omit, title: 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. @@ -532,11 +617,19 @@ async def update( extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> AdCampaign: - """ - Updates an ad campaign's editable fields. + """Updates an ad campaign's editable fields (title, budget, schedule). + + Objective, + budget optimization, budget type, special ad categories, bid type and desired + cost per result are fixed at creation and cannot be changed. Args: - budget_amount: The campaign budget, in the account's currency. + budget_amount: The campaign budget, in the account's currency. Interpreted as daily or lifetime + per the campaign's existing budget type. + + ends_at: Campaign schedule end (ISO 8601). CBO only. + + starts_at: Campaign schedule start (ISO 8601). CBO only. title: The name of the campaign. @@ -555,6 +648,8 @@ async def update( body=await async_maybe_transform( { "budget_amount": budget_amount, + "ends_at": ends_at, + "starts_at": starts_at, "title": title, }, ad_campaign_update_params.AdCampaignUpdateParams, @@ -657,6 +752,40 @@ def list( model=AdCampaign, ) + 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, + ) -> AdCampaignDeleteResponse: + """ + Deletes an ad campaign and archives it on the ad platform (cascades to ad groups + and ads). Returns true on success. + + 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_campaigns/{id}", id=id), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=AdCampaignDeleteResponse, + ) + async def pause( self, id: str, @@ -740,6 +869,9 @@ def __init__(self, ad_campaigns: AdCampaignsResource) -> None: self.list = to_raw_response_wrapper( ad_campaigns.list, ) + self.delete = to_raw_response_wrapper( + ad_campaigns.delete, + ) self.pause = to_raw_response_wrapper( ad_campaigns.pause, ) @@ -764,6 +896,9 @@ def __init__(self, ad_campaigns: AsyncAdCampaignsResource) -> None: self.list = async_to_raw_response_wrapper( ad_campaigns.list, ) + self.delete = async_to_raw_response_wrapper( + ad_campaigns.delete, + ) self.pause = async_to_raw_response_wrapper( ad_campaigns.pause, ) @@ -788,6 +923,9 @@ def __init__(self, ad_campaigns: AdCampaignsResource) -> None: self.list = to_streamed_response_wrapper( ad_campaigns.list, ) + self.delete = to_streamed_response_wrapper( + ad_campaigns.delete, + ) self.pause = to_streamed_response_wrapper( ad_campaigns.pause, ) @@ -812,6 +950,9 @@ def __init__(self, ad_campaigns: AsyncAdCampaignsResource) -> None: self.list = async_to_streamed_response_wrapper( ad_campaigns.list, ) + self.delete = async_to_streamed_response_wrapper( + ad_campaigns.delete, + ) self.pause = async_to_streamed_response_wrapper( ad_campaigns.pause, ) diff --git a/src/whop_sdk/resources/ad_groups.py b/src/whop_sdk/resources/ad_groups.py index b4ddae2a..9945fbba 100644 --- a/src/whop_sdk/resources/ad_groups.py +++ b/src/whop_sdk/resources/ad_groups.py @@ -2,13 +2,13 @@ from __future__ import annotations -from typing import Union +from typing import List, Union 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 .._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 @@ -50,7 +50,7 @@ def create( self, *, ad_campaign_id: str, - audience: object | Omit = omit, + audiences: object | Omit = omit, bid_type: Literal["minimum_cost", "average_target", "maximum_target"] | Omit = omit, budget_amount: float | Omit = omit, budget_type: Literal["daily", "lifetime"] | Omit = omit, @@ -77,11 +77,24 @@ def create( None, ] | Omit = omit, - conversion_location: Literal["website"] | Omit = omit, + conversion_location: Literal[ + "website", + "profile", + "messaging", + "on_ad", + "instant_forms", + "instant_forms_and_messenger", + "website_and_instant_forms", + ] + | Omit = omit, + demographics: object | Omit = omit, desired_cost_per_result: float | Omit = omit, devices: object | Omit = omit, + dynamic_creative: bool | Omit = omit, ends_at: str | Omit = omit, frequency_cap: object | Omit = omit, + languages: SequenceNotStr[str] | Omit = omit, + message_apps: List[Literal["messenger", "instagram", "whatsapp"]] | Omit = omit, minimum_daily_spend: float | Omit = omit, optimization_goal: str | Omit = omit, placements: object | Omit = omit, @@ -102,7 +115,8 @@ def create( Args: ad_campaign_id: The ad campaign to create the ad group in. - audience: Demographic targeting: { automatic, minimum_age, maximum_age, gender }. + audiences: Saved-audience targeting: { include, exclude } arrays of audience IDs. + Incompatible with demographics.automatic (Advantage+). bid_type: Bid strategy. @@ -112,23 +126,38 @@ def create( conversion_event: The pixel event optimized for. A standard event, or any custom pixel event name. - conversion_location: Where conversions happen. + conversion_location: Where results happen: website (conversions), profile (IG/FB engagement), + messaging (DM), on_ad (engagement on the ad, surface follows the optimization + goal), or the lead destinations (instant_forms, instant_forms_and_messenger, + website_and_instant_forms). The lead form itself is set on the ad. + + demographics: Demographic targeting: { automatic, minimum_age, maximum_age, gender }. desired_cost_per_result: Target/cap cost for average_target / maximum_target. devices: Device targeting: { platforms, operating_systems: [{ os, minimum_version }] }. + dynamic_creative: Run Meta dynamic (Advantage+) creative for this ad set. Set at creation; + immutable afterward. + ends_at: Schedule end, ISO 8601. frequency_cap: { maximum_impressions, per_days } — only valid for reach optimization. + languages: Languages to target as ISO 639 codes (e.g. en, es). Empty/omitted = all + languages. + + message_apps: Required when conversion_location is messaging: which apps to message on. + Combinations map to the matching Meta destination. + minimum_daily_spend: Daily spend floor within the budget. optimization_goal: What the ad group optimizes for (e.g. conversions, link_clicks, reach). placements: 'automatic' (Advantage+) or a list of { platform, positions }. - regions: Geo targeting: { include / exclude: { countries, cities, zips } }. + regions: Geo targeting: { include / exclude: { countries (ISO 3166-1), regions + (states/provinces as ISO 3166-2, e.g. US-CA), cities (keyed), zips } }. starts_at: Schedule start, ISO 8601. @@ -149,16 +178,20 @@ def create( body=maybe_transform( { "ad_campaign_id": ad_campaign_id, - "audience": audience, + "audiences": audiences, "bid_type": bid_type, "budget_amount": budget_amount, "budget_type": budget_type, "conversion_event": conversion_event, "conversion_location": conversion_location, + "demographics": demographics, "desired_cost_per_result": desired_cost_per_result, "devices": devices, + "dynamic_creative": dynamic_creative, "ends_at": ends_at, "frequency_cap": frequency_cap, + "languages": languages, + "message_apps": message_apps, "minimum_daily_spend": minimum_daily_spend, "optimization_goal": optimization_goal, "placements": placements, @@ -212,7 +245,7 @@ def update( self, id: str, *, - audience: object | Omit = omit, + audiences: object | Omit = omit, bid_type: Literal["minimum_cost", "average_target", "maximum_target"] | Omit = omit, budget_amount: float | Omit = omit, budget_type: Literal["daily", "lifetime"] | Omit = omit, @@ -239,11 +272,23 @@ def update( None, ] | Omit = omit, - conversion_location: Literal["website"] | Omit = omit, + conversion_location: Literal[ + "website", + "profile", + "messaging", + "on_ad", + "instant_forms", + "instant_forms_and_messenger", + "website_and_instant_forms", + ] + | Omit = omit, + demographics: object | Omit = omit, desired_cost_per_result: float | Omit = omit, devices: object | Omit = omit, ends_at: str | Omit = omit, frequency_cap: object | Omit = omit, + languages: SequenceNotStr[str] | Omit = omit, + message_apps: List[Literal["messenger", "instagram", "whatsapp"]] | Omit = omit, minimum_daily_spend: float | Omit = omit, optimization_goal: str | Omit = omit, placements: object | Omit = omit, @@ -263,7 +308,8 @@ def update( Only the keys you send are changed. Args: - audience: Demographic targeting: { automatic, minimum_age, maximum_age, gender }. + audiences: Saved-audience targeting: { include, exclude } arrays of audience IDs. + Incompatible with demographics.automatic (Advantage+). bid_type: Bid strategy. @@ -273,7 +319,12 @@ def update( conversion_event: The pixel event optimized for. A standard event, or any custom pixel event name. - conversion_location: Where conversions happen. + conversion_location: Where results happen: website (conversions), profile (IG/FB engagement), + messaging (DM), on_ad (engagement on the ad, surface follows the optimization + goal), or the lead destinations (instant_forms, instant_forms_and_messenger, + website_and_instant_forms). The lead form itself is set on the ad. + + demographics: Demographic targeting: { automatic, minimum_age, maximum_age, gender }. desired_cost_per_result: Target/cap cost for average_target / maximum_target. @@ -283,13 +334,20 @@ def update( frequency_cap: { maximum_impressions, per_days } — only valid for reach optimization. + languages: Languages to target as ISO 639 codes (e.g. en, es). Empty/omitted = all + languages. + + message_apps: Required when conversion_location is messaging: which apps to message on. + Combinations map to the matching Meta destination. + minimum_daily_spend: Daily spend floor within the budget. optimization_goal: What the ad group optimizes for (e.g. conversions, link_clicks, reach). placements: 'automatic' (Advantage+) or a list of { platform, positions }. - regions: Geo targeting: { include / exclude: { countries, cities, zips } }. + regions: Geo targeting: { include / exclude: { countries (ISO 3166-1), regions + (states/provinces as ISO 3166-2, e.g. US-CA), cities (keyed), zips } }. starts_at: Schedule start, ISO 8601. @@ -311,16 +369,19 @@ def update( path_template("/ad_groups/{id}", id=id), body=maybe_transform( { - "audience": audience, + "audiences": audiences, "bid_type": bid_type, "budget_amount": budget_amount, "budget_type": budget_type, "conversion_event": conversion_event, "conversion_location": conversion_location, + "demographics": demographics, "desired_cost_per_result": desired_cost_per_result, "devices": devices, "ends_at": ends_at, "frequency_cap": frequency_cap, + "languages": languages, + "message_apps": message_apps, "minimum_daily_spend": minimum_daily_spend, "optimization_goal": optimization_goal, "placements": placements, @@ -406,7 +467,7 @@ def delete( extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> AdGroupDeleteResponse: - """Deletes (discards) an ad group. + """Deletes an ad group. Returns true on success. @@ -520,7 +581,7 @@ async def create( self, *, ad_campaign_id: str, - audience: object | Omit = omit, + audiences: object | Omit = omit, bid_type: Literal["minimum_cost", "average_target", "maximum_target"] | Omit = omit, budget_amount: float | Omit = omit, budget_type: Literal["daily", "lifetime"] | Omit = omit, @@ -547,11 +608,24 @@ async def create( None, ] | Omit = omit, - conversion_location: Literal["website"] | Omit = omit, + conversion_location: Literal[ + "website", + "profile", + "messaging", + "on_ad", + "instant_forms", + "instant_forms_and_messenger", + "website_and_instant_forms", + ] + | Omit = omit, + demographics: object | Omit = omit, desired_cost_per_result: float | Omit = omit, devices: object | Omit = omit, + dynamic_creative: bool | Omit = omit, ends_at: str | Omit = omit, frequency_cap: object | Omit = omit, + languages: SequenceNotStr[str] | Omit = omit, + message_apps: List[Literal["messenger", "instagram", "whatsapp"]] | Omit = omit, minimum_daily_spend: float | Omit = omit, optimization_goal: str | Omit = omit, placements: object | Omit = omit, @@ -572,7 +646,8 @@ async def create( Args: ad_campaign_id: The ad campaign to create the ad group in. - audience: Demographic targeting: { automatic, minimum_age, maximum_age, gender }. + audiences: Saved-audience targeting: { include, exclude } arrays of audience IDs. + Incompatible with demographics.automatic (Advantage+). bid_type: Bid strategy. @@ -582,23 +657,38 @@ async def create( conversion_event: The pixel event optimized for. A standard event, or any custom pixel event name. - conversion_location: Where conversions happen. + conversion_location: Where results happen: website (conversions), profile (IG/FB engagement), + messaging (DM), on_ad (engagement on the ad, surface follows the optimization + goal), or the lead destinations (instant_forms, instant_forms_and_messenger, + website_and_instant_forms). The lead form itself is set on the ad. + + demographics: Demographic targeting: { automatic, minimum_age, maximum_age, gender }. desired_cost_per_result: Target/cap cost for average_target / maximum_target. devices: Device targeting: { platforms, operating_systems: [{ os, minimum_version }] }. + dynamic_creative: Run Meta dynamic (Advantage+) creative for this ad set. Set at creation; + immutable afterward. + ends_at: Schedule end, ISO 8601. frequency_cap: { maximum_impressions, per_days } — only valid for reach optimization. + languages: Languages to target as ISO 639 codes (e.g. en, es). Empty/omitted = all + languages. + + message_apps: Required when conversion_location is messaging: which apps to message on. + Combinations map to the matching Meta destination. + minimum_daily_spend: Daily spend floor within the budget. optimization_goal: What the ad group optimizes for (e.g. conversions, link_clicks, reach). placements: 'automatic' (Advantage+) or a list of { platform, positions }. - regions: Geo targeting: { include / exclude: { countries, cities, zips } }. + regions: Geo targeting: { include / exclude: { countries (ISO 3166-1), regions + (states/provinces as ISO 3166-2, e.g. US-CA), cities (keyed), zips } }. starts_at: Schedule start, ISO 8601. @@ -619,16 +709,20 @@ async def create( body=await async_maybe_transform( { "ad_campaign_id": ad_campaign_id, - "audience": audience, + "audiences": audiences, "bid_type": bid_type, "budget_amount": budget_amount, "budget_type": budget_type, "conversion_event": conversion_event, "conversion_location": conversion_location, + "demographics": demographics, "desired_cost_per_result": desired_cost_per_result, "devices": devices, + "dynamic_creative": dynamic_creative, "ends_at": ends_at, "frequency_cap": frequency_cap, + "languages": languages, + "message_apps": message_apps, "minimum_daily_spend": minimum_daily_spend, "optimization_goal": optimization_goal, "placements": placements, @@ -682,7 +776,7 @@ async def update( self, id: str, *, - audience: object | Omit = omit, + audiences: object | Omit = omit, bid_type: Literal["minimum_cost", "average_target", "maximum_target"] | Omit = omit, budget_amount: float | Omit = omit, budget_type: Literal["daily", "lifetime"] | Omit = omit, @@ -709,11 +803,23 @@ async def update( None, ] | Omit = omit, - conversion_location: Literal["website"] | Omit = omit, + conversion_location: Literal[ + "website", + "profile", + "messaging", + "on_ad", + "instant_forms", + "instant_forms_and_messenger", + "website_and_instant_forms", + ] + | Omit = omit, + demographics: object | Omit = omit, desired_cost_per_result: float | Omit = omit, devices: object | Omit = omit, ends_at: str | Omit = omit, frequency_cap: object | Omit = omit, + languages: SequenceNotStr[str] | Omit = omit, + message_apps: List[Literal["messenger", "instagram", "whatsapp"]] | Omit = omit, minimum_daily_spend: float | Omit = omit, optimization_goal: str | Omit = omit, placements: object | Omit = omit, @@ -733,7 +839,8 @@ async def update( Only the keys you send are changed. Args: - audience: Demographic targeting: { automatic, minimum_age, maximum_age, gender }. + audiences: Saved-audience targeting: { include, exclude } arrays of audience IDs. + Incompatible with demographics.automatic (Advantage+). bid_type: Bid strategy. @@ -743,7 +850,12 @@ async def update( conversion_event: The pixel event optimized for. A standard event, or any custom pixel event name. - conversion_location: Where conversions happen. + conversion_location: Where results happen: website (conversions), profile (IG/FB engagement), + messaging (DM), on_ad (engagement on the ad, surface follows the optimization + goal), or the lead destinations (instant_forms, instant_forms_and_messenger, + website_and_instant_forms). The lead form itself is set on the ad. + + demographics: Demographic targeting: { automatic, minimum_age, maximum_age, gender }. desired_cost_per_result: Target/cap cost for average_target / maximum_target. @@ -753,13 +865,20 @@ async def update( frequency_cap: { maximum_impressions, per_days } — only valid for reach optimization. + languages: Languages to target as ISO 639 codes (e.g. en, es). Empty/omitted = all + languages. + + message_apps: Required when conversion_location is messaging: which apps to message on. + Combinations map to the matching Meta destination. + minimum_daily_spend: Daily spend floor within the budget. optimization_goal: What the ad group optimizes for (e.g. conversions, link_clicks, reach). placements: 'automatic' (Advantage+) or a list of { platform, positions }. - regions: Geo targeting: { include / exclude: { countries, cities, zips } }. + regions: Geo targeting: { include / exclude: { countries (ISO 3166-1), regions + (states/provinces as ISO 3166-2, e.g. US-CA), cities (keyed), zips } }. starts_at: Schedule start, ISO 8601. @@ -781,16 +900,19 @@ async def update( path_template("/ad_groups/{id}", id=id), body=await async_maybe_transform( { - "audience": audience, + "audiences": audiences, "bid_type": bid_type, "budget_amount": budget_amount, "budget_type": budget_type, "conversion_event": conversion_event, "conversion_location": conversion_location, + "demographics": demographics, "desired_cost_per_result": desired_cost_per_result, "devices": devices, "ends_at": ends_at, "frequency_cap": frequency_cap, + "languages": languages, + "message_apps": message_apps, "minimum_daily_spend": minimum_daily_spend, "optimization_goal": optimization_goal, "placements": placements, @@ -876,7 +998,7 @@ async def delete( extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> AdGroupDeleteResponse: - """Deletes (discards) an ad group. + """Deletes an ad group. Returns true on success. diff --git a/src/whop_sdk/resources/ads.py b/src/whop_sdk/resources/ads.py index dd772a57..3879312a 100644 --- a/src/whop_sdk/resources/ads.py +++ b/src/whop_sdk/resources/ads.py @@ -369,7 +369,7 @@ def delete( extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> AdDeleteResponse: - """Deletes (discards) an ad. + """Deletes an ad. Returns true on success. @@ -802,7 +802,7 @@ async def delete( extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> AdDeleteResponse: - """Deletes (discards) an ad. + """Deletes an ad. Returns true on success. diff --git a/src/whop_sdk/types/__init__.py b/src/whop_sdk/types/__init__.py index 689b0049..7b3ede0a 100644 --- a/src/whop_sdk/types/__init__.py +++ b/src/whop_sdk/types/__init__.py @@ -339,6 +339,7 @@ from .verification_create_params import VerificationCreateParams as VerificationCreateParams from .verification_list_response import VerificationListResponse as VerificationListResponse from .verification_update_params import VerificationUpdateParams as VerificationUpdateParams +from .ad_campaign_delete_response import AdCampaignDeleteResponse as AdCampaignDeleteResponse from .ad_campaign_retrieve_params import AdCampaignRetrieveParams as AdCampaignRetrieveParams from .ad_report_retrieve_response import AdReportRetrieveResponse as AdReportRetrieveResponse from .authorized_user_list_params import AuthorizedUserListParams as AuthorizedUserListParams diff --git a/src/whop_sdk/types/ad_campaign_create_params.py b/src/whop_sdk/types/ad_campaign_create_params.py index 7240e233..1cb7db21 100644 --- a/src/whop_sdk/types/ad_campaign_create_params.py +++ b/src/whop_sdk/types/ad_campaign_create_params.py @@ -24,6 +24,12 @@ class AdCampaignCreateParams(TypedDict, total=False): Defaults to the account-scoped key's own account. """ + bid_type: Literal["minimum_cost", "average_target", "maximum_target"] + """ + CBO bid strategy: minimum_cost (lowest cost), average_target (cost cap), or + maximum_target (bid cap). CBO only. + """ + budget_amount: float """The campaign budget, in USD. @@ -42,8 +48,20 @@ class AdCampaignCreateParams(TypedDict, total=False): Defaults to daily. """ + desired_cost_per_result: float + """Target/cap cost per result in USD for average_target / maximum_target bidding. + + CBO only. + """ + + ends_at: str + """Campaign schedule end (ISO 8601). CBO only.""" + special_ad_categories: List[Literal["housing", "employment", "financial_products", "politics"]] """Regulated categories the campaign falls under. Ads in these categories are subject to extra targeting restrictions. """ + + starts_at: str + """Campaign schedule start (ISO 8601). CBO only.""" diff --git a/src/whop_sdk/types/ad_campaign_delete_response.py b/src/whop_sdk/types/ad_campaign_delete_response.py new file mode 100644 index 00000000..359d4542 --- /dev/null +++ b/src/whop_sdk/types/ad_campaign_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__ = ["AdCampaignDeleteResponse"] + +AdCampaignDeleteResponse: TypeAlias = bool diff --git a/src/whop_sdk/types/ad_campaign_update_params.py b/src/whop_sdk/types/ad_campaign_update_params.py index e4b4b010..17772ef7 100644 --- a/src/whop_sdk/types/ad_campaign_update_params.py +++ b/src/whop_sdk/types/ad_campaign_update_params.py @@ -9,7 +9,16 @@ class AdCampaignUpdateParams(TypedDict, total=False): budget_amount: float - """The campaign budget, in the account's currency.""" + """The campaign budget, in the account's currency. + + Interpreted as daily or lifetime per the campaign's existing budget type. + """ + + ends_at: str + """Campaign schedule end (ISO 8601). CBO only.""" + + starts_at: str + """Campaign schedule start (ISO 8601). CBO only.""" title: str """The name of the campaign.""" diff --git a/src/whop_sdk/types/ad_group.py b/src/whop_sdk/types/ad_group.py index 028213e3..553e0f40 100644 --- a/src/whop_sdk/types/ad_group.py +++ b/src/whop_sdk/types/ad_group.py @@ -34,8 +34,8 @@ class AdGroup(BaseModel): added_to_carts: float """Whop pixel-attributed add-to-cart events, last-click.""" - audience: object - """Demographic targeting: automatic (Advantage+), age range, gender.""" + audiences: object + """Saved-audience targeting: { include, exclude } arrays of audience IDs.""" bid_type: Optional[Literal["minimum_cost", "average_target", "maximum_target"]] = None """Bid strategy.""" @@ -85,8 +85,22 @@ class AdGroup(BaseModel): A standard event, or any custom pixel event name. """ - conversion_location: Optional[Literal["website"]] = None - """Where conversions happen.""" + conversion_location: Optional[ + Literal[ + "website", + "profile", + "messaging", + "on_ad", + "instant_forms", + "instant_forms_and_messenger", + "website_and_instant_forms", + ] + ] = None + """ + Where results happen: website, profile (IG/FB), messaging (DM), on_ad + (engagement), or the lead destinations (instant_forms, + instant_forms_and_messenger, website_and_instant_forms). + """ cost_per_added_to_cart: Optional[float] = None """ @@ -151,12 +165,21 @@ class AdGroup(BaseModel): across all custom event names. """ + demographics: object + """Demographic targeting: automatic (Advantage+), age range, gender.""" + desired_cost_per_result: Optional[float] = None """Target/cap cost for average_target / maximum_target.""" devices: object """Device targeting: platforms and operating systems.""" + dynamic_creative: bool + """ + Whether ads within this ad group have their creatives and copy dynamically AB + tested. + """ + ends_at: Optional[str] = None """Schedule end, ISO 8601.""" @@ -171,9 +194,13 @@ class AdGroup(BaseModel): issues: List[Issue] + languages: List[str] + leads: float """Whop pixel-attributed leads, last-click.""" + message_apps: List[str] + minimum_daily_spend: Optional[float] = None """Daily spend floor within the budget.""" @@ -192,7 +219,10 @@ class AdGroup(BaseModel): """The number of unique people who saw this.""" regions: object - """Geo targeting: include/exclude countries, cities, zips.""" + """Geo targeting: include/exclude countries, regions (ISO 3166-2 states, e.g. + + US-CA), cities, zips. + """ return_on_ad_spend: float """Purchase value divided by spend; 0 when there is no spend.""" diff --git a/src/whop_sdk/types/ad_group_create_params.py b/src/whop_sdk/types/ad_group_create_params.py index 1fe4109f..5cbfb890 100644 --- a/src/whop_sdk/types/ad_group_create_params.py +++ b/src/whop_sdk/types/ad_group_create_params.py @@ -2,9 +2,11 @@ from __future__ import annotations -from typing import Union +from typing import List, Union from typing_extensions import Literal, Required, TypedDict +from .._types import SequenceNotStr + __all__ = ["AdGroupCreateParams"] @@ -12,8 +14,11 @@ class AdGroupCreateParams(TypedDict, total=False): ad_campaign_id: Required[str] """The ad campaign to create the ad group in.""" - audience: object - """Demographic targeting: { automatic, minimum_age, maximum_age, gender }.""" + audiences: object + """Saved-audience targeting: { include, exclude } arrays of audience IDs. + + Incompatible with demographics.automatic (Advantage+). + """ bid_type: Literal["minimum_cost", "average_target", "maximum_target"] """Bid strategy.""" @@ -51,8 +56,24 @@ class AdGroupCreateParams(TypedDict, total=False): A standard event, or any custom pixel event name. """ - conversion_location: Literal["website"] - """Where conversions happen.""" + conversion_location: Literal[ + "website", + "profile", + "messaging", + "on_ad", + "instant_forms", + "instant_forms_and_messenger", + "website_and_instant_forms", + ] + """ + Where results happen: website (conversions), profile (IG/FB engagement), + messaging (DM), on_ad (engagement on the ad, surface follows the optimization + goal), or the lead destinations (instant_forms, instant_forms_and_messenger, + website_and_instant_forms). The lead form itself is set on the ad. + """ + + demographics: object + """Demographic targeting: { automatic, minimum_age, maximum_age, gender }.""" desired_cost_per_result: float """Target/cap cost for average_target / maximum_target.""" @@ -60,12 +81,30 @@ class AdGroupCreateParams(TypedDict, total=False): devices: object """Device targeting: { platforms, operating_systems: [{ os, minimum_version }] }.""" + dynamic_creative: bool + """Run Meta dynamic (Advantage+) creative for this ad set. + + Set at creation; immutable afterward. + """ + ends_at: str """Schedule end, ISO 8601.""" frequency_cap: object """{ maximum_impressions, per_days } — only valid for reach optimization.""" + languages: SequenceNotStr[str] + """Languages to target as ISO 639 codes (e.g. + + en, es). Empty/omitted = all languages. + """ + + message_apps: List[Literal["messenger", "instagram", "whatsapp"]] + """Required when conversion_location is messaging: which apps to message on. + + Combinations map to the matching Meta destination. + """ + minimum_daily_spend: float """Daily spend floor within the budget.""" @@ -76,7 +115,10 @@ class AdGroupCreateParams(TypedDict, total=False): """'automatic' (Advantage+) or a list of { platform, positions }.""" regions: object - """Geo targeting: { include / exclude: { countries, cities, zips } }.""" + """ + Geo targeting: { include / exclude: { countries (ISO 3166-1), regions + (states/provinces as ISO 3166-2, e.g. US-CA), cities (keyed), zips } }. + """ starts_at: str """Schedule start, ISO 8601.""" diff --git a/src/whop_sdk/types/ad_group_update_params.py b/src/whop_sdk/types/ad_group_update_params.py index 7a122b7d..8972976f 100644 --- a/src/whop_sdk/types/ad_group_update_params.py +++ b/src/whop_sdk/types/ad_group_update_params.py @@ -2,15 +2,20 @@ from __future__ import annotations -from typing import Union +from typing import List, Union from typing_extensions import Literal, TypedDict +from .._types import SequenceNotStr + __all__ = ["AdGroupUpdateParams"] class AdGroupUpdateParams(TypedDict, total=False): - audience: object - """Demographic targeting: { automatic, minimum_age, maximum_age, gender }.""" + audiences: object + """Saved-audience targeting: { include, exclude } arrays of audience IDs. + + Incompatible with demographics.automatic (Advantage+). + """ bid_type: Literal["minimum_cost", "average_target", "maximum_target"] """Bid strategy.""" @@ -48,8 +53,24 @@ class AdGroupUpdateParams(TypedDict, total=False): A standard event, or any custom pixel event name. """ - conversion_location: Literal["website"] - """Where conversions happen.""" + conversion_location: Literal[ + "website", + "profile", + "messaging", + "on_ad", + "instant_forms", + "instant_forms_and_messenger", + "website_and_instant_forms", + ] + """ + Where results happen: website (conversions), profile (IG/FB engagement), + messaging (DM), on_ad (engagement on the ad, surface follows the optimization + goal), or the lead destinations (instant_forms, instant_forms_and_messenger, + website_and_instant_forms). The lead form itself is set on the ad. + """ + + demographics: object + """Demographic targeting: { automatic, minimum_age, maximum_age, gender }.""" desired_cost_per_result: float """Target/cap cost for average_target / maximum_target.""" @@ -63,6 +84,18 @@ class AdGroupUpdateParams(TypedDict, total=False): frequency_cap: object """{ maximum_impressions, per_days } — only valid for reach optimization.""" + languages: SequenceNotStr[str] + """Languages to target as ISO 639 codes (e.g. + + en, es). Empty/omitted = all languages. + """ + + message_apps: List[Literal["messenger", "instagram", "whatsapp"]] + """Required when conversion_location is messaging: which apps to message on. + + Combinations map to the matching Meta destination. + """ + minimum_daily_spend: float """Daily spend floor within the budget.""" @@ -73,7 +106,10 @@ class AdGroupUpdateParams(TypedDict, total=False): """'automatic' (Advantage+) or a list of { platform, positions }.""" regions: object - """Geo targeting: { include / exclude: { countries, cities, zips } }.""" + """ + Geo targeting: { include / exclude: { countries (ISO 3166-1), regions + (states/provinces as ISO 3166-2, e.g. US-CA), cities (keyed), zips } }. + """ starts_at: str """Schedule start, ISO 8601.""" diff --git a/tests/api_resources/test_ad_campaigns.py b/tests/api_resources/test_ad_campaigns.py index a083a15a..c770c24d 100644 --- a/tests/api_resources/test_ad_campaigns.py +++ b/tests/api_resources/test_ad_campaigns.py @@ -11,6 +11,7 @@ from tests.utils import assert_matches_type from whop_sdk.types import ( AdCampaign, + AdCampaignDeleteResponse, ) from whop_sdk.pagination import SyncCursorPage, AsyncCursorPage @@ -38,10 +39,14 @@ def test_method_create_with_all_params(self, client: Whop) -> None: platform="meta", title="title", account_id="account_id", + bid_type="minimum_cost", budget_amount=0, budget_optimization="ad_campaign", budget_type="daily", + desired_cost_per_result=0, + ends_at="ends_at", special_ad_categories=["housing"], + starts_at="starts_at", ) assert_matches_type(AdCampaign, ad_campaign, path=["response"]) @@ -141,6 +146,8 @@ def test_method_update_with_all_params(self, client: Whop) -> None: ad_campaign = client.ad_campaigns.update( id="id", budget_amount=0, + ends_at="ends_at", + starts_at="starts_at", title="title", ) assert_matches_type(AdCampaign, ad_campaign, path=["response"]) @@ -227,6 +234,48 @@ 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_delete(self, client: Whop) -> None: + ad_campaign = client.ad_campaigns.delete( + "id", + ) + assert_matches_type(AdCampaignDeleteResponse, ad_campaign, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_raw_response_delete(self, client: Whop) -> None: + response = client.ad_campaigns.with_raw_response.delete( + "id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + ad_campaign = response.parse() + assert_matches_type(AdCampaignDeleteResponse, ad_campaign, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_streaming_response_delete(self, client: Whop) -> None: + with client.ad_campaigns.with_streaming_response.delete( + "id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + ad_campaign = response.parse() + assert_matches_type(AdCampaignDeleteResponse, 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_delete(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.delete( + "", + ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_method_pause(self, client: Whop) -> None: @@ -335,10 +384,14 @@ async def test_method_create_with_all_params(self, async_client: AsyncWhop) -> N platform="meta", title="title", account_id="account_id", + bid_type="minimum_cost", budget_amount=0, budget_optimization="ad_campaign", budget_type="daily", + desired_cost_per_result=0, + ends_at="ends_at", special_ad_categories=["housing"], + starts_at="starts_at", ) assert_matches_type(AdCampaign, ad_campaign, path=["response"]) @@ -438,6 +491,8 @@ async def test_method_update_with_all_params(self, async_client: AsyncWhop) -> N ad_campaign = await async_client.ad_campaigns.update( id="id", budget_amount=0, + ends_at="ends_at", + starts_at="starts_at", title="title", ) assert_matches_type(AdCampaign, ad_campaign, path=["response"]) @@ -524,6 +579,48 @@ async def test_streaming_response_list(self, async_client: AsyncWhop) -> None: 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_campaign = await async_client.ad_campaigns.delete( + "id", + ) + assert_matches_type(AdCampaignDeleteResponse, ad_campaign, 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_campaigns.with_raw_response.delete( + "id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + ad_campaign = await response.parse() + assert_matches_type(AdCampaignDeleteResponse, ad_campaign, 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_campaigns.with_streaming_response.delete( + "id", + ) 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(AdCampaignDeleteResponse, 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_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_campaigns.with_raw_response.delete( + "", + ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_method_pause(self, async_client: AsyncWhop) -> None: diff --git a/tests/api_resources/test_ad_groups.py b/tests/api_resources/test_ad_groups.py index 6787cc94..acad36b0 100644 --- a/tests/api_resources/test_ad_groups.py +++ b/tests/api_resources/test_ad_groups.py @@ -34,16 +34,20 @@ def test_method_create(self, client: Whop) -> None: def test_method_create_with_all_params(self, client: Whop) -> None: ad_group = client.ad_groups.create( ad_campaign_id="ad_campaign_id", - audience={}, + audiences={}, bid_type="minimum_cost", budget_amount=0, budget_type="daily", conversion_event="purchase", conversion_location="website", + demographics={}, desired_cost_per_result=0, devices={}, + dynamic_creative=True, ends_at="ends_at", frequency_cap={}, + languages=["string"], + message_apps=["messenger"], minimum_daily_spend=0, optimization_goal="optimization_goal", placements={}, @@ -135,16 +139,19 @@ def test_method_update(self, client: Whop) -> None: def test_method_update_with_all_params(self, client: Whop) -> None: ad_group = client.ad_groups.update( id="id", - audience={}, + audiences={}, bid_type="minimum_cost", budget_amount=0, budget_type="daily", conversion_event="purchase", conversion_location="website", + demographics={}, desired_cost_per_result=0, devices={}, ends_at="ends_at", frequency_cap={}, + languages=["string"], + message_apps=["messenger"], minimum_daily_spend=0, optimization_goal="optimization_goal", placements={}, @@ -374,16 +381,20 @@ async def test_method_create(self, async_client: AsyncWhop) -> None: async def test_method_create_with_all_params(self, async_client: AsyncWhop) -> None: ad_group = await async_client.ad_groups.create( ad_campaign_id="ad_campaign_id", - audience={}, + audiences={}, bid_type="minimum_cost", budget_amount=0, budget_type="daily", conversion_event="purchase", conversion_location="website", + demographics={}, desired_cost_per_result=0, devices={}, + dynamic_creative=True, ends_at="ends_at", frequency_cap={}, + languages=["string"], + message_apps=["messenger"], minimum_daily_spend=0, optimization_goal="optimization_goal", placements={}, @@ -475,16 +486,19 @@ async def test_method_update(self, async_client: AsyncWhop) -> None: async def test_method_update_with_all_params(self, async_client: AsyncWhop) -> None: ad_group = await async_client.ad_groups.update( id="id", - audience={}, + audiences={}, bid_type="minimum_cost", budget_amount=0, budget_type="daily", conversion_event="purchase", conversion_location="website", + demographics={}, desired_cost_per_result=0, devices={}, ends_at="ends_at", frequency_cap={}, + languages=["string"], + message_apps=["messenger"], minimum_daily_spend=0, optimization_goal="optimization_goal", placements={}, From e681437ab552774beb1963ca565b924fbd545be1 Mon Sep 17 00:00:00 2001 From: stlc-bot Date: Tue, 30 Jun 2026 22:28:37 +0000 Subject: [PATCH 094/109] fix: Honor "Accept local currency payments" toggle on invoices Stainless-Generated-From: 042d0889bc5c903a0d438873d2d1f488c7d941de --- src/whop_sdk/types/invoice_create_params.py | 6 ++++++ src/whop_sdk/types/invoice_update_params.py | 3 +++ tests/api_resources/test_invoices.py | 6 ++++++ 3 files changed, 15 insertions(+) diff --git a/src/whop_sdk/types/invoice_create_params.py b/src/whop_sdk/types/invoice_create_params.py index 5a55a12b..ea92792c 100644 --- a/src/whop_sdk/types/invoice_create_params.py +++ b/src/whop_sdk/types/invoice_create_params.py @@ -190,6 +190,9 @@ class CreateInvoiceInputWithProductPlan(TypedDict, total=False): The plan attributes defining the price, currency, and billing interval for this invoice. """ + adaptive_pricing_enabled: Optional[bool] + """Whether this plan accepts local currency payments via adaptive pricing.""" + billing_period: Optional[int] """The interval in days at which the plan charges (renewal plans).""" @@ -479,6 +482,9 @@ class CreateInvoiceInputWithProductIDPlan(TypedDict, total=False): The plan attributes defining the price, currency, and billing interval for this invoice. """ + adaptive_pricing_enabled: Optional[bool] + """Whether this plan accepts local currency payments via adaptive pricing.""" + billing_period: Optional[int] """The interval in days at which the plan charges (renewal plans).""" diff --git a/src/whop_sdk/types/invoice_update_params.py b/src/whop_sdk/types/invoice_update_params.py index 7889a003..88d81f55 100644 --- a/src/whop_sdk/types/invoice_update_params.py +++ b/src/whop_sdk/types/invoice_update_params.py @@ -179,6 +179,9 @@ class PlanPaymentMethodConfiguration(TypedDict, total=False): class Plan(TypedDict, total=False): """Updated plan attributes.""" + adaptive_pricing_enabled: Optional[bool] + """Whether this plan accepts local currency payments via adaptive pricing.""" + billing_period: Optional[int] """The interval in days at which the plan charges (renewal plans).""" diff --git a/tests/api_resources/test_invoices.py b/tests/api_resources/test_invoices.py index e4e13939..cfd94e2e 100644 --- a/tests/api_resources/test_invoices.py +++ b/tests/api_resources/test_invoices.py @@ -43,6 +43,7 @@ def test_method_create_with_all_params_overload_1(self, client: Whop) -> None: collection_method="send_invoice", company_id="biz_xxxxxxxxxxxxxx", plan={ + "adaptive_pricing_enabled": True, "billing_period": 42, "currency": "usd", "custom_fields": [ @@ -160,6 +161,7 @@ def test_method_create_with_all_params_overload_2(self, client: Whop) -> None: collection_method="send_invoice", company_id="biz_xxxxxxxxxxxxxx", plan={ + "adaptive_pricing_enabled": True, "billing_period": 42, "currency": "usd", "custom_fields": [ @@ -340,6 +342,7 @@ def test_method_update_with_all_params(self, client: Whop) -> None: member_id="mber_xxxxxxxxxxxxx", payment_method_id="pmt_xxxxxxxxxxxxxx", plan={ + "adaptive_pricing_enabled": True, "billing_period": 42, "currency": "usd", "custom_fields": [ @@ -648,6 +651,7 @@ async def test_method_create_with_all_params_overload_1(self, async_client: Asyn collection_method="send_invoice", company_id="biz_xxxxxxxxxxxxxx", plan={ + "adaptive_pricing_enabled": True, "billing_period": 42, "currency": "usd", "custom_fields": [ @@ -765,6 +769,7 @@ async def test_method_create_with_all_params_overload_2(self, async_client: Asyn collection_method="send_invoice", company_id="biz_xxxxxxxxxxxxxx", plan={ + "adaptive_pricing_enabled": True, "billing_period": 42, "currency": "usd", "custom_fields": [ @@ -945,6 +950,7 @@ async def test_method_update_with_all_params(self, async_client: AsyncWhop) -> N member_id="mber_xxxxxxxxxxxxx", payment_method_id="pmt_xxxxxxxxxxxxxx", plan={ + "adaptive_pricing_enabled": True, "billing_period": 42, "currency": "usd", "custom_fields": [ From 0a3469a3483a0b3953e2634335fc2435bea1b20f Mon Sep 17 00:00:00 2001 From: stlc-bot Date: Tue, 30 Jun 2026 23:29:08 +0000 Subject: [PATCH 095/109] Rename plans expiration sort key Stainless-Generated-From: bae183098d08282a1cea4f119134355eab93a6d0 --- src/whop_sdk/resources/plans.py | 4 ++-- src/whop_sdk/types/plan_list_params.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/whop_sdk/resources/plans.py b/src/whop_sdk/resources/plans.py index 16a12e9b..03519e0a 100644 --- a/src/whop_sdk/resources/plans.py +++ b/src/whop_sdk/resources/plans.py @@ -381,7 +381,7 @@ def list( direction: Literal["asc", "desc"] | Omit = omit, first: int | Omit = omit, last: int | Omit = omit, - order: Literal["id", "active_members_count", "created_at", "internal_notes", "expires_at"] | Omit = omit, + order: Literal["id", "active_members_count", "created_at", "internal_notes", "expiration_days"] | Omit = omit, plan_types: SequenceNotStr[str] | Omit = omit, product_ids: SequenceNotStr[str] | Omit = omit, release_methods: SequenceNotStr[str] | Omit = omit, @@ -903,7 +903,7 @@ def list( direction: Literal["asc", "desc"] | Omit = omit, first: int | Omit = omit, last: int | Omit = omit, - order: Literal["id", "active_members_count", "created_at", "internal_notes", "expires_at"] | Omit = omit, + order: Literal["id", "active_members_count", "created_at", "internal_notes", "expiration_days"] | Omit = omit, plan_types: SequenceNotStr[str] | Omit = omit, product_ids: SequenceNotStr[str] | Omit = omit, release_methods: SequenceNotStr[str] | Omit = omit, diff --git a/src/whop_sdk/types/plan_list_params.py b/src/whop_sdk/types/plan_list_params.py index 22581252..daaa48a7 100644 --- a/src/whop_sdk/types/plan_list_params.py +++ b/src/whop_sdk/types/plan_list_params.py @@ -34,7 +34,7 @@ class PlanListParams(TypedDict, total=False): last: int """The number of plans to return from the end of the range.""" - order: Literal["id", "active_members_count", "created_at", "internal_notes", "expires_at"] + order: Literal["id", "active_members_count", "created_at", "internal_notes", "expiration_days"] """The field to sort results by. Defaults to created_at.""" plan_types: SequenceNotStr[str] From b21527516f74054023b9c6a02e6f391f9b758f3c Mon Sep 17 00:00:00 2001 From: stlc-bot Date: Tue, 30 Jun 2026 23:37:53 +0000 Subject: [PATCH 096/109] feat(identity): render requested_information + scope verify RFIs to the default payout account Stainless-Generated-From: e275664d053d9bffc5d4aa67abcd41308a23bc7d --- src/whop_sdk/types/verification_create_response.py | 9 ++++++++- src/whop_sdk/types/verification_list_response.py | 9 ++++++++- src/whop_sdk/types/verification_retrieve_response.py | 9 ++++++++- src/whop_sdk/types/verification_update_response.py | 9 ++++++++- 4 files changed, 32 insertions(+), 4 deletions(-) diff --git a/src/whop_sdk/types/verification_create_response.py b/src/whop_sdk/types/verification_create_response.py index abb8e392..7b1ed7f6 100644 --- a/src/whop_sdk/types/verification_create_response.py +++ b/src/whop_sdk/types/verification_create_response.py @@ -44,6 +44,13 @@ class RequestedInformation(BaseModel): label: Optional[str] = None """Human-readable label for the field (e.g. "Social Security Number").""" + options: Optional[List[str]] = None + """Allowed values for a `select` field (e.g. + + account_type, business_structure) — the submitted value must be one of these; + empty for other types. + """ + requested_files: Optional[List[RequestedInformationRequestedFile]] = None """ Upload slots for a files item — always at least one when type is `files`, empty @@ -51,7 +58,7 @@ class RequestedInformation(BaseModel): """ type: Optional[str] = None - """How to render the input: text, date, phone, address, or files.""" + """How to render the input: text, date, phone, address, files, or select.""" class VerificationCreateResponse(BaseModel): diff --git a/src/whop_sdk/types/verification_list_response.py b/src/whop_sdk/types/verification_list_response.py index 12954af0..291e8248 100644 --- a/src/whop_sdk/types/verification_list_response.py +++ b/src/whop_sdk/types/verification_list_response.py @@ -44,6 +44,13 @@ class DataRequestedInformation(BaseModel): label: Optional[str] = None """Human-readable label for the field (e.g. "Social Security Number").""" + options: Optional[List[str]] = None + """Allowed values for a `select` field (e.g. + + account_type, business_structure) — the submitted value must be one of these; + empty for other types. + """ + requested_files: Optional[List[DataRequestedInformationRequestedFile]] = None """ Upload slots for a files item — always at least one when type is `files`, empty @@ -51,7 +58,7 @@ class DataRequestedInformation(BaseModel): """ type: Optional[str] = None - """How to render the input: text, date, phone, address, or files.""" + """How to render the input: text, date, phone, address, files, or select.""" class Data(BaseModel): diff --git a/src/whop_sdk/types/verification_retrieve_response.py b/src/whop_sdk/types/verification_retrieve_response.py index b03d73b4..7ece0005 100644 --- a/src/whop_sdk/types/verification_retrieve_response.py +++ b/src/whop_sdk/types/verification_retrieve_response.py @@ -44,6 +44,13 @@ class RequestedInformation(BaseModel): label: Optional[str] = None """Human-readable label for the field (e.g. "Social Security Number").""" + options: Optional[List[str]] = None + """Allowed values for a `select` field (e.g. + + account_type, business_structure) — the submitted value must be one of these; + empty for other types. + """ + requested_files: Optional[List[RequestedInformationRequestedFile]] = None """ Upload slots for a files item — always at least one when type is `files`, empty @@ -51,7 +58,7 @@ class RequestedInformation(BaseModel): """ type: Optional[str] = None - """How to render the input: text, date, phone, address, or files.""" + """How to render the input: text, date, phone, address, files, or select.""" class VerificationRetrieveResponse(BaseModel): diff --git a/src/whop_sdk/types/verification_update_response.py b/src/whop_sdk/types/verification_update_response.py index 1439f67c..3175d889 100644 --- a/src/whop_sdk/types/verification_update_response.py +++ b/src/whop_sdk/types/verification_update_response.py @@ -44,6 +44,13 @@ class RequestedInformation(BaseModel): label: Optional[str] = None """Human-readable label for the field (e.g. "Social Security Number").""" + options: Optional[List[str]] = None + """Allowed values for a `select` field (e.g. + + account_type, business_structure) — the submitted value must be one of these; + empty for other types. + """ + requested_files: Optional[List[RequestedInformationRequestedFile]] = None """ Upload slots for a files item — always at least one when type is `files`, empty @@ -51,7 +58,7 @@ class RequestedInformation(BaseModel): """ type: Optional[str] = None - """How to render the input: text, date, phone, address, or files.""" + """How to render the input: text, date, phone, address, files, or select.""" class VerificationUpdateResponse(BaseModel): From 808096a196ea78c35a21150eeff2893633450fb9 Mon Sep 17 00:00:00 2001 From: stlc-bot Date: Tue, 30 Jun 2026 23:51:48 +0000 Subject: [PATCH 097/109] feat(backend): add actions to referrals and refactor Stainless-Generated-From: 766ef98fb2dfee8593e242e6633a8e1a0b308f2c --- .../referrals/business_retrieve_response.py | 111 +++++++++++++++++- 1 file changed, 109 insertions(+), 2 deletions(-) diff --git a/src/whop_sdk/types/referrals/business_retrieve_response.py b/src/whop_sdk/types/referrals/business_retrieve_response.py index 33f98228..7198e33b 100644 --- a/src/whop_sdk/types/referrals/business_retrieve_response.py +++ b/src/whop_sdk/types/referrals/business_retrieve_response.py @@ -1,12 +1,111 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -from typing import Optional +from typing import List, Optional from datetime import datetime from typing_extensions import Literal from ..._models import BaseModel -__all__ = ["BusinessRetrieveResponse", "Account", "EarningsUsd", "VolumeUsd"] +__all__ = [ + "BusinessRetrieveResponse", + "Account", + "AccountCapabilities", + "AccountRecommendedAction", + "AccountRequiredAction", + "EarningsUsd", + "VolumeUsd", +] + + +class AccountCapabilities(BaseModel): + accept_bank_payments: Literal["active", "inactive", "pending"] + """Bank payins: debits, transfers, and local bank rails""" + + accept_bnpl_payments: Literal["active", "inactive", "pending"] + """Buy-now-pay-later payins; requires approval""" + + accept_card_payments: Literal["active", "inactive", "pending"] + """Card payins, including Apple Pay and Google Pay""" + + bank_deposit: Literal["active", "inactive", "pending"] + """Deposits by bank wire or ACH to the account's virtual bank account""" + + card_deposit: Literal["active", "inactive", "pending"] + """Balance top-ups by charging a stored payment method""" + + card_issuing: Literal["active", "inactive", "pending"] + """Issuing Whop cards; requires card application approval""" + + crypto_deposit: Literal["active", "inactive", "pending"] + """On-chain deposits to the account's crypto wallet""" + + crypto_payout: Literal["active", "inactive", "pending"] + """On-chain payouts to a crypto wallet""" + + instant_payout: Literal["active", "inactive", "pending"] + """Instant payouts to an eligible payout destination""" + + standard_payout: Literal["active", "inactive", "pending"] + """Standard payouts to an external payout destination""" + + transfer: Literal["active", "inactive", "pending"] + """Transfers to other accounts""" + + +class AccountRecommendedAction(BaseModel): + action: Literal["apply_for_financing", "migrate_from_stripe", "accept_first_payment", "join_whop_university"] + """ + The recommendation; new values may be added, so handle unknown actions + gracefully + """ + + blocked_capabilities: List[str] + + cta: str + """The URL the call-to-action links to""" + + cta_label: str + """Button label""" + + description: str + """Supporting copy, or empty""" + + icon_url: Optional[str] = None + """Illustration icon URL, or `null`""" + + status: Literal["optional"] + """Always optional — never blocking""" + + title: str + """Headline for the recommendation""" + + +class AccountRequiredAction(BaseModel): + action: Literal["deposit_funds", "submit_information_request", "verify_identity", "connect_fulfillment_tracker"] + """ + What the holder must do; new values may be added, so handle unknown actions + gracefully + """ + + blocked_capabilities: List[str] + + cta: Optional[str] = None + """The URL the call-to-action links to, or null when there is no button""" + + cta_label: str + """Button label, or empty when there is no button""" + + description: str + """Supporting copy, or empty""" + + icon_url: Optional[str] = None + """The URL of the action's illustration icon, or null if it has none""" + + status: Literal["required", "pending"] + """required (act now) or pending (under review)""" + + title: str + """Headline for the action""" class Account(BaseModel): @@ -15,9 +114,17 @@ class Account(BaseModel): id: str """Referred account ID.""" + capabilities: Optional[AccountCapabilities] = None + logo_url: Optional[str] = None """Referred account logo URL.""" + recommended_actions: Optional[List[AccountRecommendedAction]] = None + """Optional actions that unlock capabilities or grow the referred account.""" + + required_actions: Optional[List[AccountRequiredAction]] = None + """Actions the referred account owner must take to unblock capabilities.""" + route: str """Referred account route.""" From ad29592c3739f4433c12bf1c76b5db6dc201816d Mon Sep 17 00:00:00 2001 From: stlc-bot Date: Wed, 1 Jul 2026 04:19:25 +0000 Subject: [PATCH 098/109] Expose the referred account's owner on the business referral API Stainless-Generated-From: e86ec7f69585ca419db1f3d248f1d7fad3c966f7 --- .../types/referrals/business_list_response.py | 28 ++++++++++++++++++- .../referrals/business_retrieve_response.py | 28 +++++++++++++++++++ 2 files changed, 55 insertions(+), 1 deletion(-) diff --git a/src/whop_sdk/types/referrals/business_list_response.py b/src/whop_sdk/types/referrals/business_list_response.py index a71d85ef..b6346b1b 100644 --- a/src/whop_sdk/types/referrals/business_list_response.py +++ b/src/whop_sdk/types/referrals/business_list_response.py @@ -6,7 +6,7 @@ from ..._models import BaseModel -__all__ = ["BusinessListResponse", "Account", "EarningsUsd", "VolumeUsd"] +__all__ = ["BusinessListResponse", "Account", "EarningsUsd", "User", "UserProfilePicture", "VolumeUsd"] class Account(BaseModel): @@ -36,6 +36,29 @@ class EarningsUsd(BaseModel): """Pending + completed commission, in USD.""" +class UserProfilePicture(BaseModel): + """The user's profile picture.""" + + url: str + """The user's profile picture URL.""" + + +class User(BaseModel): + """Owner of the referred account.""" + + id: str + """User ID, prefixed `user_`.""" + + name: Optional[str] = None + """The user's display name.""" + + profile_picture: UserProfilePicture + """The user's profile picture.""" + + username: str + """The user's unique username.""" + + class VolumeUsd(BaseModel): attributed: str """ @@ -76,4 +99,7 @@ class BusinessListResponse(BaseModel): status: Literal["active", "removed"] """Current referral status.""" + user: Optional[User] = None + """Owner of the referred account.""" + volume_usd: VolumeUsd diff --git a/src/whop_sdk/types/referrals/business_retrieve_response.py b/src/whop_sdk/types/referrals/business_retrieve_response.py index 7198e33b..004bd319 100644 --- a/src/whop_sdk/types/referrals/business_retrieve_response.py +++ b/src/whop_sdk/types/referrals/business_retrieve_response.py @@ -13,6 +13,8 @@ "AccountRecommendedAction", "AccountRequiredAction", "EarningsUsd", + "User", + "UserProfilePicture", "VolumeUsd", ] @@ -143,6 +145,29 @@ class EarningsUsd(BaseModel): """Pending + completed commission, in USD.""" +class UserProfilePicture(BaseModel): + """The user's profile picture.""" + + url: str + """The user's profile picture URL.""" + + +class User(BaseModel): + """Owner of the referred account.""" + + id: str + """User ID, prefixed `user_`.""" + + name: Optional[str] = None + """The user's display name.""" + + profile_picture: UserProfilePicture + """The user's profile picture.""" + + username: str + """The user's unique username.""" + + class VolumeUsd(BaseModel): attributed: str """ @@ -183,4 +208,7 @@ class BusinessRetrieveResponse(BaseModel): status: Literal["active", "removed"] """Current referral status.""" + user: Optional[User] = None + """Owner of the referred account.""" + volume_usd: VolumeUsd From 5f107f70a3644cf4737fcbaf0a6ecfa1acfc299c Mon Sep 17 00:00:00 2001 From: stlc-bot Date: Wed, 1 Jul 2026 07:55:24 +0000 Subject: [PATCH 099/109] fix(backend): business referral api changes Stainless-Generated-From: 7370f3a22c52b1d70af1c409a7484667784369f3 --- README.md | 4 +- src/whop_sdk/_client.py | 4 +- .../referrals/businesses/businesses.py | 18 ++---- .../referrals/businesses/earnings.py | 10 --- .../business_list_earnings_params.py | 6 -- .../business_list_earnings_response.py | 63 ++++++------------- .../types/referrals/business_list_params.py | 4 +- .../businesses/earning_list_params.py | 6 -- .../businesses/earning_list_response.py | 63 ++++++------------- .../referrals/businesses/test_earnings.py | 2 - .../referrals/test_businesses.py | 2 - 11 files changed, 46 insertions(+), 136 deletions(-) diff --git a/README.md b/README.md index 6c5faec4..98667424 100644 --- a/README.md +++ b/README.md @@ -13,8 +13,8 @@ It is generated with [Stainless](https://www.stainless.com/). Use the Whop MCP Server to enable AI assistants to interact with this API, allowing them to explore endpoints, make test requests, and use documentation to help integrate this SDK into your application. -[![Add to Cursor](https://cursor.com/deeplink/mcp-install-dark.svg)](https://cursor.com/en-US/install-mcp?name=%40whop%2Fmcp&config=eyJjb21tYW5kIjoibnB4IiwiYXJncyI6WyIteSIsIkB3aG9wL21jcCJdLCJlbnYiOnsiV0hPUF9BUElfS0VZIjoiTXkgQVBJIEtleSIsIldIT1BfV0VCSE9PS19TRUNSRVQiOiJNeSBXZWJob29rIEtleSIsIldIT1BfQVBQX0lEIjoiYXBwX3h4eHh4eHh4eHh4eHh4IiwiV0hPUF9BUElfVkVSU0lPTiI6IjIwMjYtMDYtMjAifX0) -[![Install in VS Code](https://img.shields.io/badge/_-Add_to_VS_Code-blue?style=for-the-badge&logo=data:image/svg%2bxml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGZpbGw9Im5vbmUiIHZpZXdCb3g9IjAgMCA0MCA0MCI+PHBhdGggZmlsbD0iI0VFRSIgZmlsbC1ydWxlPSJldmVub2RkIiBkPSJNMzAuMjM1IDM5Ljg4NGEyLjQ5MSAyLjQ5MSAwIDAgMS0xLjc4MS0uNzNMMTIuNyAyNC43OGwtMy40NiAyLjYyNC0zLjQwNiAyLjU4MmExLjY2NSAxLjY2NSAwIDAgMS0xLjA4Mi4zMzggMS42NjQgMS42NjQgMCAwIDEtMS4wNDYtLjQzMWwtMi4yLTJhMS42NjYgMS42NjYgMCAwIDEgMC0yLjQ2M0w3LjQ1OCAyMCA0LjY3IDE3LjQ1MyAxLjUwNyAxNC41N2ExLjY2NSAxLjY2NSAwIDAgMSAwLTIuNDYzbDIuMi0yYTEuNjY1IDEuNjY1IDAgMCAxIDIuMTMtLjA5N2w2Ljg2MyA1LjIwOUwyOC40NTIuODQ0YTIuNDg4IDIuNDg4IDAgMCAxIDEuODQxLS43MjljLjM1MS4wMDkuNjk5LjA5MSAxLjAxOS4yNDVsOC4yMzYgMy45NjFhMi41IDIuNSAwIDAgMSAxLjQxNSAyLjI1M3YuMDk5LS4wNDVWMzMuMzd2LS4wNDUuMDk1YTIuNTAxIDIuNTAxIDAgMCAxLTEuNDE2IDIuMjU3bC04LjIzNSAzLjk2MWEyLjQ5MiAyLjQ5MiAwIDAgMS0xLjA3Ny4yNDZabS43MTYtMjguOTQ3LTExLjk0OCA5LjA2MiAxMS45NTIgOS4wNjUtLjAwNC0xOC4xMjdaIi8+PC9zdmc+)](https://vscode.stainless.com/mcp/%7B%22name%22%3A%22%40whop%2Fmcp%22%2C%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22-y%22%2C%22%40whop%2Fmcp%22%5D%2C%22env%22%3A%7B%22WHOP_API_KEY%22%3A%22My%20API%20Key%22%2C%22WHOP_WEBHOOK_SECRET%22%3A%22My%20Webhook%20Key%22%2C%22WHOP_APP_ID%22%3A%22app_xxxxxxxxxxxxxx%22%2C%22WHOP_API_VERSION%22%3A%222026-06-20%22%7D%7D) +[![Add to Cursor](https://cursor.com/deeplink/mcp-install-dark.svg)](https://cursor.com/en-US/install-mcp?name=%40whop%2Fmcp&config=eyJjb21tYW5kIjoibnB4IiwiYXJncyI6WyIteSIsIkB3aG9wL21jcCJdLCJlbnYiOnsiV0hPUF9BUElfS0VZIjoiTXkgQVBJIEtleSIsIldIT1BfV0VCSE9PS19TRUNSRVQiOiJNeSBXZWJob29rIEtleSIsIldIT1BfQVBQX0lEIjoiYXBwX3h4eHh4eHh4eHh4eHh4IiwiV0hPUF9BUElfVkVSU0lPTiI6IjIwMjYtMDctMDEifX0) +[![Install in VS Code](https://img.shields.io/badge/_-Add_to_VS_Code-blue?style=for-the-badge&logo=data:image/svg%2bxml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGZpbGw9Im5vbmUiIHZpZXdCb3g9IjAgMCA0MCA0MCI+PHBhdGggZmlsbD0iI0VFRSIgZmlsbC1ydWxlPSJldmVub2RkIiBkPSJNMzAuMjM1IDM5Ljg4NGEyLjQ5MSAyLjQ5MSAwIDAgMS0xLjc4MS0uNzNMMTIuNyAyNC43OGwtMy40NiAyLjYyNC0zLjQwNiAyLjU4MmExLjY2NSAxLjY2NSAwIDAgMS0xLjA4Mi4zMzggMS42NjQgMS42NjQgMCAwIDEtMS4wNDYtLjQzMWwtMi4yLTJhMS42NjYgMS42NjYgMCAwIDEgMC0yLjQ2M0w3LjQ1OCAyMCA0LjY3IDE3LjQ1MyAxLjUwNyAxNC41N2ExLjY2NSAxLjY2NSAwIDAgMSAwLTIuNDYzbDIuMi0yYTEuNjY1IDEuNjY1IDAgMCAxIDIuMTMtLjA5N2w2Ljg2MyA1LjIwOUwyOC40NTIuODQ0YTIuNDg4IDIuNDg4IDAgMCAxIDEuODQxLS43MjljLjM1MS4wMDkuNjk5LjA5MSAxLjAxOS4yNDVsOC4yMzYgMy45NjFhMi41IDIuNSAwIDAgMSAxLjQxNSAyLjI1M3YuMDk5LS4wNDVWMzMuMzd2LS4wNDUuMDk1YTIuNTAxIDIuNTAxIDAgMCAxLTEuNDE2IDIuMjU3bC04LjIzNSAzLjk2MWEyLjQ5MiAyLjQ5MiAwIDAgMS0xLjA3Ny4yNDZabS43MTYtMjguOTQ3LTExLjk0OCA5LjA2MiAxMS45NTIgOS4wNjUtLjAwNC0xOC4xMjdaIi8+PC9zdmc+)](https://vscode.stainless.com/mcp/%7B%22name%22%3A%22%40whop%2Fmcp%22%2C%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22-y%22%2C%22%40whop%2Fmcp%22%5D%2C%22env%22%3A%7B%22WHOP_API_KEY%22%3A%22My%20API%20Key%22%2C%22WHOP_WEBHOOK_SECRET%22%3A%22My%20Webhook%20Key%22%2C%22WHOP_APP_ID%22%3A%22app_xxxxxxxxxxxxxx%22%2C%22WHOP_API_VERSION%22%3A%222026-07-01%22%7D%7D) > Note: You may need to set environment variables in your MCP client. diff --git a/src/whop_sdk/_client.py b/src/whop_sdk/_client.py index 31a748a6..6f362873 100644 --- a/src/whop_sdk/_client.py +++ b/src/whop_sdk/_client.py @@ -243,7 +243,7 @@ def __init__( self.app_id = app_id if version is None: - version = os.environ.get("WHOP_API_VERSION") or "2026-06-20" + version = os.environ.get("WHOP_API_VERSION") or "2026-07-01" self.version = version if base_url is None: @@ -917,7 +917,7 @@ def __init__( self.app_id = app_id if version is None: - version = os.environ.get("WHOP_API_VERSION") or "2026-06-20" + version = os.environ.get("WHOP_API_VERSION") or "2026-07-01" self.version = version if base_url is None: diff --git a/src/whop_sdk/resources/referrals/businesses/businesses.py b/src/whop_sdk/resources/referrals/businesses/businesses.py index f3be6cf3..1574fe03 100644 --- a/src/whop_sdk/resources/referrals/businesses/businesses.py +++ b/src/whop_sdk/resources/referrals/businesses/businesses.py @@ -122,8 +122,8 @@ def list( first: Number of business referrals to return from the start of the window. - has_earnings: When true, only businesses that have paid out at least one earning to the - caller. + has_earnings: When true, only businesses with at least one non-canceled, non-reversed earning + paid to the caller. last: Number of business referrals to return from the end of the window. @@ -171,7 +171,6 @@ def list_earnings( before: str | Omit = omit, direction: Literal["asc", "desc"] | Omit = omit, first: int | Omit = omit, - include: Literal["receipt_fees"] | Omit = omit, last: int | Omit = omit, order: Literal["created_at", "commission_amount", "transaction_amount", "payout_at"] | Omit = omit, status: Literal["awaiting_settlement", "pending", "completed", "canceled", "reversed"] | Omit = omit, @@ -189,9 +188,6 @@ def list_earnings( Args: direction: Sort direction. - include: Comma-separated extras to embed. Supported: receipt_fees (adds amount_after_fees - and the receipt_fees breakdown). - order: The field to sort earnings by. status: Filter by earning status. @@ -218,7 +214,6 @@ def list_earnings( "before": before, "direction": direction, "first": first, - "include": include, "last": last, "order": order, "status": status, @@ -318,8 +313,8 @@ def list( first: Number of business referrals to return from the start of the window. - has_earnings: When true, only businesses that have paid out at least one earning to the - caller. + has_earnings: When true, only businesses with at least one non-canceled, non-reversed earning + paid to the caller. last: Number of business referrals to return from the end of the window. @@ -367,7 +362,6 @@ def list_earnings( before: str | Omit = omit, direction: Literal["asc", "desc"] | Omit = omit, first: int | Omit = omit, - include: Literal["receipt_fees"] | Omit = omit, last: int | Omit = omit, order: Literal["created_at", "commission_amount", "transaction_amount", "payout_at"] | Omit = omit, status: Literal["awaiting_settlement", "pending", "completed", "canceled", "reversed"] | Omit = omit, @@ -385,9 +379,6 @@ def list_earnings( Args: direction: Sort direction. - include: Comma-separated extras to embed. Supported: receipt_fees (adds amount_after_fees - and the receipt_fees breakdown). - order: The field to sort earnings by. status: Filter by earning status. @@ -414,7 +405,6 @@ def list_earnings( "before": before, "direction": direction, "first": first, - "include": include, "last": last, "order": order, "status": status, diff --git a/src/whop_sdk/resources/referrals/businesses/earnings.py b/src/whop_sdk/resources/referrals/businesses/earnings.py index 9657ee3b..a0df618c 100644 --- a/src/whop_sdk/resources/referrals/businesses/earnings.py +++ b/src/whop_sdk/resources/referrals/businesses/earnings.py @@ -52,7 +52,6 @@ def list( before: str | Omit = omit, direction: Literal["asc", "desc"] | Omit = omit, first: int | Omit = omit, - include: Literal["receipt_fees"] | Omit = omit, last: int | Omit = omit, order: Literal["created_at", "commission_amount", "transaction_amount", "payout_at"] | Omit = omit, status: Literal["awaiting_settlement", "pending", "completed", "canceled", "reversed"] | Omit = omit, @@ -70,9 +69,6 @@ def list( Args: direction: Sort direction. - include: Comma-separated extras to embed. Supported: receipt_fees (adds amount_after_fees - and the receipt_fees breakdown). - order: The field to sort earnings by. status: Filter by earning status. @@ -101,7 +97,6 @@ def list( "before": before, "direction": direction, "first": first, - "include": include, "last": last, "order": order, "status": status, @@ -141,7 +136,6 @@ def list( before: str | Omit = omit, direction: Literal["asc", "desc"] | Omit = omit, first: int | Omit = omit, - include: Literal["receipt_fees"] | Omit = omit, last: int | Omit = omit, order: Literal["created_at", "commission_amount", "transaction_amount", "payout_at"] | Omit = omit, status: Literal["awaiting_settlement", "pending", "completed", "canceled", "reversed"] | Omit = omit, @@ -159,9 +153,6 @@ def list( Args: direction: Sort direction. - include: Comma-separated extras to embed. Supported: receipt_fees (adds amount_after_fees - and the receipt_fees breakdown). - order: The field to sort earnings by. status: Filter by earning status. @@ -190,7 +181,6 @@ def list( "before": before, "direction": direction, "first": first, - "include": include, "last": last, "order": order, "status": status, diff --git a/src/whop_sdk/types/referrals/business_list_earnings_params.py b/src/whop_sdk/types/referrals/business_list_earnings_params.py index b91e8ce4..23a618a9 100644 --- a/src/whop_sdk/types/referrals/business_list_earnings_params.py +++ b/src/whop_sdk/types/referrals/business_list_earnings_params.py @@ -17,12 +17,6 @@ class BusinessListEarningsParams(TypedDict, total=False): first: int - include: Literal["receipt_fees"] - """Comma-separated extras to embed. - - Supported: receipt_fees (adds amount_after_fees and the receipt_fees breakdown). - """ - last: int order: Literal["created_at", "commission_amount", "transaction_amount", "payout_at"] diff --git a/src/whop_sdk/types/referrals/business_list_earnings_response.py b/src/whop_sdk/types/referrals/business_list_earnings_response.py index 5987818a..fec44b78 100644 --- a/src/whop_sdk/types/referrals/business_list_earnings_response.py +++ b/src/whop_sdk/types/referrals/business_list_earnings_response.py @@ -1,27 +1,12 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -from typing import List, Optional +from typing import Optional from datetime import datetime from typing_extensions import Literal from ..._models import BaseModel -__all__ = [ - "BusinessListEarningsResponse", - "AccessPass", - "Account", - "Receipt", - "ReceiptAlternativePaymentMethod", - "ReceiptReceiptFee", -] - - -class AccessPass(BaseModel): - id: str - - route: str - - title: str +__all__ = ["BusinessListEarningsResponse", "Account", "Product", "Resource", "ResourceAlternativePaymentMethod"] class Account(BaseModel): @@ -40,32 +25,26 @@ class Account(BaseModel): """Referred account display name.""" -class ReceiptAlternativePaymentMethod(BaseModel): - image_url: Optional[str] = None - - name: str - - -class ReceiptReceiptFee(BaseModel): - currency: str +class Product(BaseModel): + id: str - description: Optional[str] = None + route: str - label: str + title: str - raw_amount: float - specific_fee_origin: str +class ResourceAlternativePaymentMethod(BaseModel): + image_url: Optional[str] = None - type_of_fee: str + name: str - value: str +class Resource(BaseModel): + """The resource that generated the affiliate earning.""" -class Receipt(BaseModel): id: str - alternative_payment_method: Optional[ReceiptAlternativePaymentMethod] = None + alternative_payment_method: Optional[ResourceAlternativePaymentMethod] = None brand: Optional[str] = None @@ -75,22 +54,16 @@ class Receipt(BaseModel): last4: Optional[str] = None + object: Literal["receipt"] + payment_method_type: Optional[str] = None processor: Optional[str] = None - amount_after_fees: Optional[float] = None - """Only present when include=receipt_fees.""" - - receipt_fees: Optional[List[ReceiptReceiptFee]] = None - """Only present when include=receipt_fees.""" - class BusinessListEarningsResponse(BaseModel): id: Optional[str] = None - access_pass: Optional[AccessPass] = None - account: Optional[Account] = None """Referred account.""" @@ -112,13 +85,13 @@ class BusinessListEarningsResponse(BaseModel): Null until the earning settles. """ - receipt: Optional[Receipt] = None + product: Optional[Product] = None + + resource: Optional[Resource] = None + """The resource that generated the affiliate earning.""" status: Literal["awaiting_settlement", "pending", "completed", "canceled", "reversed"] """Current status of the earning.""" transaction_amount_usd: str """The sale amount the commission is calculated from, in USD.""" - - whop_gross_profit_usd: Optional[str] = None - """Whop's gross profit on the sale, in USD. Null until the earning settles.""" diff --git a/src/whop_sdk/types/referrals/business_list_params.py b/src/whop_sdk/types/referrals/business_list_params.py index ddad7083..814072c5 100644 --- a/src/whop_sdk/types/referrals/business_list_params.py +++ b/src/whop_sdk/types/referrals/business_list_params.py @@ -22,8 +22,8 @@ class BusinessListParams(TypedDict, total=False): has_earnings: bool """ - When true, only businesses that have paid out at least one earning to the - caller. + When true, only businesses with at least one non-canceled, non-reversed earning + paid to the caller. """ last: int diff --git a/src/whop_sdk/types/referrals/businesses/earning_list_params.py b/src/whop_sdk/types/referrals/businesses/earning_list_params.py index 091c4d8c..7992c51c 100644 --- a/src/whop_sdk/types/referrals/businesses/earning_list_params.py +++ b/src/whop_sdk/types/referrals/businesses/earning_list_params.py @@ -17,12 +17,6 @@ class EarningListParams(TypedDict, total=False): first: int - include: Literal["receipt_fees"] - """Comma-separated extras to embed. - - Supported: receipt_fees (adds amount_after_fees and the receipt_fees breakdown). - """ - last: int order: Literal["created_at", "commission_amount", "transaction_amount", "payout_at"] diff --git a/src/whop_sdk/types/referrals/businesses/earning_list_response.py b/src/whop_sdk/types/referrals/businesses/earning_list_response.py index 40ba477a..8ee59e51 100644 --- a/src/whop_sdk/types/referrals/businesses/earning_list_response.py +++ b/src/whop_sdk/types/referrals/businesses/earning_list_response.py @@ -1,27 +1,12 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -from typing import List, Optional +from typing import Optional from datetime import datetime from typing_extensions import Literal from ...._models import BaseModel -__all__ = [ - "EarningListResponse", - "AccessPass", - "Account", - "Receipt", - "ReceiptAlternativePaymentMethod", - "ReceiptReceiptFee", -] - - -class AccessPass(BaseModel): - id: str - - route: str - - title: str +__all__ = ["EarningListResponse", "Account", "Product", "Resource", "ResourceAlternativePaymentMethod"] class Account(BaseModel): @@ -40,32 +25,26 @@ class Account(BaseModel): """Referred account display name.""" -class ReceiptAlternativePaymentMethod(BaseModel): - image_url: Optional[str] = None - - name: str - - -class ReceiptReceiptFee(BaseModel): - currency: str +class Product(BaseModel): + id: str - description: Optional[str] = None + route: str - label: str + title: str - raw_amount: float - specific_fee_origin: str +class ResourceAlternativePaymentMethod(BaseModel): + image_url: Optional[str] = None - type_of_fee: str + name: str - value: str +class Resource(BaseModel): + """The resource that generated the affiliate earning.""" -class Receipt(BaseModel): id: str - alternative_payment_method: Optional[ReceiptAlternativePaymentMethod] = None + alternative_payment_method: Optional[ResourceAlternativePaymentMethod] = None brand: Optional[str] = None @@ -75,22 +54,16 @@ class Receipt(BaseModel): last4: Optional[str] = None + object: Literal["receipt"] + payment_method_type: Optional[str] = None processor: Optional[str] = None - amount_after_fees: Optional[float] = None - """Only present when include=receipt_fees.""" - - receipt_fees: Optional[List[ReceiptReceiptFee]] = None - """Only present when include=receipt_fees.""" - class EarningListResponse(BaseModel): id: Optional[str] = None - access_pass: Optional[AccessPass] = None - account: Optional[Account] = None """Referred account.""" @@ -112,13 +85,13 @@ class EarningListResponse(BaseModel): Null until the earning settles. """ - receipt: Optional[Receipt] = None + product: Optional[Product] = None + + resource: Optional[Resource] = None + """The resource that generated the affiliate earning.""" status: Literal["awaiting_settlement", "pending", "completed", "canceled", "reversed"] """Current status of the earning.""" transaction_amount_usd: str """The sale amount the commission is calculated from, in USD.""" - - whop_gross_profit_usd: Optional[str] = None - """Whop's gross profit on the sale, in USD. Null until the earning settles.""" diff --git a/tests/api_resources/referrals/businesses/test_earnings.py b/tests/api_resources/referrals/businesses/test_earnings.py index bd08246b..433278eb 100644 --- a/tests/api_resources/referrals/businesses/test_earnings.py +++ b/tests/api_resources/referrals/businesses/test_earnings.py @@ -35,7 +35,6 @@ def test_method_list_with_all_params(self, client: Whop) -> None: before="before", direction="asc", first=100, - include="receipt_fees", last=100, order="created_at", status="awaiting_settlement", @@ -99,7 +98,6 @@ async def test_method_list_with_all_params(self, async_client: AsyncWhop) -> Non before="before", direction="asc", first=100, - include="receipt_fees", last=100, order="created_at", status="awaiting_settlement", diff --git a/tests/api_resources/referrals/test_businesses.py b/tests/api_resources/referrals/test_businesses.py index 4aeb9f65..50fe7301 100644 --- a/tests/api_resources/referrals/test_businesses.py +++ b/tests/api_resources/referrals/test_businesses.py @@ -121,7 +121,6 @@ def test_method_list_earnings_with_all_params(self, client: Whop) -> None: before="before", direction="asc", first=100, - include="receipt_fees", last=100, order="created_at", status="awaiting_settlement", @@ -255,7 +254,6 @@ async def test_method_list_earnings_with_all_params(self, async_client: AsyncWho before="before", direction="asc", first=100, - include="receipt_fees", last=100, order="created_at", status="awaiting_settlement", From 6ad7b7e56f3e6a7c430f778a299f6207b96d116d Mon Sep 17 00:00:00 2001 From: stlc-bot Date: Wed, 1 Jul 2026 20:06:46 +0000 Subject: [PATCH 100/109] feat(ads): REST ad messaging, existing-post, multi-advertiser, lead forms + PATCH launch Stainless-Generated-From: 6e4aa21f1658da0a427ac89689213d0894315d15 --- src/whop_sdk/resources/ad_campaigns.py | 30 ++- src/whop_sdk/resources/ads.py | 176 ++++++++++++- src/whop_sdk/types/ad.py | 26 ++ .../types/ad_campaign_update_params.py | 8 +- src/whop_sdk/types/ad_create_params.py | 224 ++++++++++++++++- src/whop_sdk/types/ad_update_params.py | 224 ++++++++++++++++- tests/api_resources/test_ad_campaigns.py | 2 + tests/api_resources/test_ads.py | 232 +++++++++++++++++- 8 files changed, 899 insertions(+), 23 deletions(-) diff --git a/src/whop_sdk/resources/ad_campaigns.py b/src/whop_sdk/resources/ad_campaigns.py index 9dec1d82..232bb748 100644 --- a/src/whop_sdk/resources/ad_campaigns.py +++ b/src/whop_sdk/resources/ad_campaigns.py @@ -198,6 +198,7 @@ def update( budget_amount: float | Omit = omit, ends_at: str | Omit = omit, starts_at: str | Omit = omit, + status: Literal["active"] | Omit = omit, title: 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. @@ -206,11 +207,11 @@ def update( extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> AdCampaign: - """Updates an ad campaign's editable fields (title, budget, schedule). - - Objective, - budget optimization, budget type, special ad categories, bid type and desired - cost per result are fixed at creation and cannot be changed. + """ + Updates an ad campaign's editable fields (title, budget, schedule), and launches + a draft campaign by setting status to active. Objective, budget optimization, + budget type, special ad categories, bid type and desired cost per result are + fixed at creation and cannot be changed. Args: budget_amount: The campaign budget, in the account's currency. Interpreted as daily or lifetime @@ -220,6 +221,9 @@ def update( starts_at: Campaign schedule start (ISO 8601). CBO only. + status: Set to active to launch a draft campaign (moderates and pushes it live). + Live-campaign pause and resume use the pause and unpause actions. + title: The name of the campaign. extra_headers: Send extra headers @@ -239,6 +243,7 @@ def update( "budget_amount": budget_amount, "ends_at": ends_at, "starts_at": starts_at, + "status": status, "title": title, }, ad_campaign_update_params.AdCampaignUpdateParams, @@ -609,6 +614,7 @@ async def update( budget_amount: float | Omit = omit, ends_at: str | Omit = omit, starts_at: str | Omit = omit, + status: Literal["active"] | Omit = omit, title: 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. @@ -617,11 +623,11 @@ async def update( extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> AdCampaign: - """Updates an ad campaign's editable fields (title, budget, schedule). - - Objective, - budget optimization, budget type, special ad categories, bid type and desired - cost per result are fixed at creation and cannot be changed. + """ + Updates an ad campaign's editable fields (title, budget, schedule), and launches + a draft campaign by setting status to active. Objective, budget optimization, + budget type, special ad categories, bid type and desired cost per result are + fixed at creation and cannot be changed. Args: budget_amount: The campaign budget, in the account's currency. Interpreted as daily or lifetime @@ -631,6 +637,9 @@ async def update( starts_at: Campaign schedule start (ISO 8601). CBO only. + status: Set to active to launch a draft campaign (moderates and pushes it live). + Live-campaign pause and resume use the pause and unpause actions. + title: The name of the campaign. extra_headers: Send extra headers @@ -650,6 +659,7 @@ async def update( "budget_amount": budget_amount, "ends_at": ends_at, "starts_at": starts_at, + "status": status, "title": title, }, ad_campaign_update_params.AdCampaignUpdateParams, diff --git a/src/whop_sdk/resources/ads.py b/src/whop_sdk/resources/ads.py index 3879312a..94df18c6 100644 --- a/src/whop_sdk/resources/ads.py +++ b/src/whop_sdk/resources/ads.py @@ -52,12 +52,37 @@ def create( ad_group: object | Omit = omit, ad_group_id: str | Omit = omit, call_to_action: Literal[ - "shop_now", "learn_more", "sign_up", "subscribe", "order_now", "get_offer", "see_details" + "apply_now", + "book_now", + "call_now", + "contact_us", + "download", + "get_directions", + "get_offer", + "get_quote", + "learn_more", + "listen_now", + "message_page", + "no_button", + "open_link", + "order_now", + "request_time", + "see_details", + "see_menu", + "send_updates", + "shop_now", + "sign_up", + "subscribe", + "watch_more", ] | Omit = omit, creatives: Iterable[ad_create_params.Creative] | Omit = omit, descriptions: SequenceNotStr[str] | Omit = omit, headlines: SequenceNotStr[str] | Omit = omit, + lead_form: ad_create_params.LeadForm | Omit = omit, + messaging_config: ad_create_params.MessagingConfig | Omit = omit, + multi_advertiser_ads: bool | Omit = omit, + post_id: str | Omit = omit, primary_texts: SequenceNotStr[str] | Omit = omit, social_accounts: Iterable[ad_create_params.SocialAccount] | Omit = omit, title: str | Omit = omit, @@ -89,6 +114,19 @@ def create( headlines: The headline variants shown on the ad. + lead_form: Instant lead form for the ad. Only allowed when the ad group's + conversion_location is an instant-form destination (instant_forms, + instant_forms_and_messenger, website_and_instant_forms). + + messaging_config: Click-to-message welcome copy: the greeting (message) and the ice-breaker prompt + (keyword). + + multi_advertiser_ads: Whether the ad can appear alongside other advertisers' ads in the same unit. + Defaults to true. + + post_id: Promote an existing post instead of uploading creatives — a Facebook post or + Instagram media id. Mutually exclusive with creatives. + primary_texts: The primary text variants shown in the ad body. social_accounts: The social accounts (Facebook page, Instagram profile) the ad runs under. @@ -117,6 +155,10 @@ def create( "creatives": creatives, "descriptions": descriptions, "headlines": headlines, + "lead_form": lead_form, + "messaging_config": messaging_config, + "multi_advertiser_ads": multi_advertiser_ads, + "post_id": post_id, "primary_texts": primary_texts, "social_accounts": social_accounts, "title": title, @@ -185,12 +227,37 @@ def update( id: str, *, call_to_action: Literal[ - "shop_now", "learn_more", "sign_up", "subscribe", "order_now", "get_offer", "see_details" + "apply_now", + "book_now", + "call_now", + "contact_us", + "download", + "get_directions", + "get_offer", + "get_quote", + "learn_more", + "listen_now", + "message_page", + "no_button", + "open_link", + "order_now", + "request_time", + "see_details", + "see_menu", + "send_updates", + "shop_now", + "sign_up", + "subscribe", + "watch_more", ] | Omit = omit, creatives: Iterable[ad_update_params.Creative] | Omit = omit, descriptions: SequenceNotStr[str] | Omit = omit, headlines: SequenceNotStr[str] | Omit = omit, + lead_form: ad_update_params.LeadForm | Omit = omit, + messaging_config: ad_update_params.MessagingConfig | Omit = omit, + multi_advertiser_ads: bool | Omit = omit, + post_id: str | Omit = omit, primary_texts: SequenceNotStr[str] | Omit = omit, social_accounts: Iterable[ad_update_params.SocialAccount] | Omit = omit, title: str | Omit = omit, @@ -217,6 +284,19 @@ def update( headlines: The headline variants shown on the ad. + lead_form: Instant lead form for the ad. Only allowed when the ad group's + conversion_location is an instant-form destination (instant_forms, + instant_forms_and_messenger, website_and_instant_forms). + + messaging_config: Click-to-message welcome copy: the greeting (message) and the ice-breaker prompt + (keyword). + + multi_advertiser_ads: Whether the ad can appear alongside other advertisers' ads in the same unit. + Defaults to true. + + post_id: Promote an existing post instead of uploading creatives — a Facebook post or + Instagram media id. Mutually exclusive with creatives. + primary_texts: The primary text variants shown in the ad body. social_accounts: The social accounts the ad runs under. @@ -245,6 +325,10 @@ def update( "creatives": creatives, "descriptions": descriptions, "headlines": headlines, + "lead_form": lead_form, + "messaging_config": messaging_config, + "multi_advertiser_ads": multi_advertiser_ads, + "post_id": post_id, "primary_texts": primary_texts, "social_accounts": social_accounts, "title": title, @@ -485,12 +569,37 @@ async def create( ad_group: object | Omit = omit, ad_group_id: str | Omit = omit, call_to_action: Literal[ - "shop_now", "learn_more", "sign_up", "subscribe", "order_now", "get_offer", "see_details" + "apply_now", + "book_now", + "call_now", + "contact_us", + "download", + "get_directions", + "get_offer", + "get_quote", + "learn_more", + "listen_now", + "message_page", + "no_button", + "open_link", + "order_now", + "request_time", + "see_details", + "see_menu", + "send_updates", + "shop_now", + "sign_up", + "subscribe", + "watch_more", ] | Omit = omit, creatives: Iterable[ad_create_params.Creative] | Omit = omit, descriptions: SequenceNotStr[str] | Omit = omit, headlines: SequenceNotStr[str] | Omit = omit, + lead_form: ad_create_params.LeadForm | Omit = omit, + messaging_config: ad_create_params.MessagingConfig | Omit = omit, + multi_advertiser_ads: bool | Omit = omit, + post_id: str | Omit = omit, primary_texts: SequenceNotStr[str] | Omit = omit, social_accounts: Iterable[ad_create_params.SocialAccount] | Omit = omit, title: str | Omit = omit, @@ -522,6 +631,19 @@ async def create( headlines: The headline variants shown on the ad. + lead_form: Instant lead form for the ad. Only allowed when the ad group's + conversion_location is an instant-form destination (instant_forms, + instant_forms_and_messenger, website_and_instant_forms). + + messaging_config: Click-to-message welcome copy: the greeting (message) and the ice-breaker prompt + (keyword). + + multi_advertiser_ads: Whether the ad can appear alongside other advertisers' ads in the same unit. + Defaults to true. + + post_id: Promote an existing post instead of uploading creatives — a Facebook post or + Instagram media id. Mutually exclusive with creatives. + primary_texts: The primary text variants shown in the ad body. social_accounts: The social accounts (Facebook page, Instagram profile) the ad runs under. @@ -550,6 +672,10 @@ async def create( "creatives": creatives, "descriptions": descriptions, "headlines": headlines, + "lead_form": lead_form, + "messaging_config": messaging_config, + "multi_advertiser_ads": multi_advertiser_ads, + "post_id": post_id, "primary_texts": primary_texts, "social_accounts": social_accounts, "title": title, @@ -618,12 +744,37 @@ async def update( id: str, *, call_to_action: Literal[ - "shop_now", "learn_more", "sign_up", "subscribe", "order_now", "get_offer", "see_details" + "apply_now", + "book_now", + "call_now", + "contact_us", + "download", + "get_directions", + "get_offer", + "get_quote", + "learn_more", + "listen_now", + "message_page", + "no_button", + "open_link", + "order_now", + "request_time", + "see_details", + "see_menu", + "send_updates", + "shop_now", + "sign_up", + "subscribe", + "watch_more", ] | Omit = omit, creatives: Iterable[ad_update_params.Creative] | Omit = omit, descriptions: SequenceNotStr[str] | Omit = omit, headlines: SequenceNotStr[str] | Omit = omit, + lead_form: ad_update_params.LeadForm | Omit = omit, + messaging_config: ad_update_params.MessagingConfig | Omit = omit, + multi_advertiser_ads: bool | Omit = omit, + post_id: str | Omit = omit, primary_texts: SequenceNotStr[str] | Omit = omit, social_accounts: Iterable[ad_update_params.SocialAccount] | Omit = omit, title: str | Omit = omit, @@ -650,6 +801,19 @@ async def update( headlines: The headline variants shown on the ad. + lead_form: Instant lead form for the ad. Only allowed when the ad group's + conversion_location is an instant-form destination (instant_forms, + instant_forms_and_messenger, website_and_instant_forms). + + messaging_config: Click-to-message welcome copy: the greeting (message) and the ice-breaker prompt + (keyword). + + multi_advertiser_ads: Whether the ad can appear alongside other advertisers' ads in the same unit. + Defaults to true. + + post_id: Promote an existing post instead of uploading creatives — a Facebook post or + Instagram media id. Mutually exclusive with creatives. + primary_texts: The primary text variants shown in the ad body. social_accounts: The social accounts the ad runs under. @@ -678,6 +842,10 @@ async def update( "creatives": creatives, "descriptions": descriptions, "headlines": headlines, + "lead_form": lead_form, + "messaging_config": messaging_config, + "multi_advertiser_ads": multi_advertiser_ads, + "post_id": post_id, "primary_texts": primary_texts, "social_accounts": social_accounts, "title": title, diff --git a/src/whop_sdk/types/ad.py b/src/whop_sdk/types/ad.py index be0f0275..9a0cac84 100644 --- a/src/whop_sdk/types/ad.py +++ b/src/whop_sdk/types/ad.py @@ -163,9 +163,35 @@ class Ad(BaseModel): issues: List[Issue] + lead_form: Optional[object] = None + """ + The instant lead form on the ad (Meta lead ads), or null when the ad group's + conversion_location is not an instant-form destination. An object with name, + form_type (more_volume or higher_intent), an optional intro, questions, a + privacy_policy, an optional completion screen, and phone_verification. + """ + leads: float """Whop pixel-attributed leads, last-click.""" + messaging_config: Optional[object] = None + """ + The click-to-message welcome copy, an object with message and keyword, or null + when the ad has none. + """ + + multi_advertiser_ads: bool + """Whether the ad can appear alongside other advertisers' ads in the same unit. + + Defaults to true. + """ + + post_id: Optional[str] = None + """ + The existing post this ad promotes (a Facebook post or Instagram media), or null + when it uses uploaded creatives. + """ + primary_texts: List[str] purchase_value: float diff --git a/src/whop_sdk/types/ad_campaign_update_params.py b/src/whop_sdk/types/ad_campaign_update_params.py index 17772ef7..a332c7eb 100644 --- a/src/whop_sdk/types/ad_campaign_update_params.py +++ b/src/whop_sdk/types/ad_campaign_update_params.py @@ -2,7 +2,7 @@ from __future__ import annotations -from typing_extensions import TypedDict +from typing_extensions import Literal, TypedDict __all__ = ["AdCampaignUpdateParams"] @@ -20,5 +20,11 @@ class AdCampaignUpdateParams(TypedDict, total=False): starts_at: str """Campaign schedule start (ISO 8601). CBO only.""" + status: Literal["active"] + """Set to active to launch a draft campaign (moderates and pushes it live). + + Live-campaign pause and resume use the pause and unpause actions. + """ + title: str """The name of the campaign.""" diff --git a/src/whop_sdk/types/ad_create_params.py b/src/whop_sdk/types/ad_create_params.py index 8d3d2dd5..f8704f35 100644 --- a/src/whop_sdk/types/ad_create_params.py +++ b/src/whop_sdk/types/ad_create_params.py @@ -7,7 +7,21 @@ from .._types import SequenceNotStr -__all__ = ["AdCreateParams", "Creative", "SocialAccount"] +__all__ = [ + "AdCreateParams", + "Creative", + "LeadForm", + "LeadFormCompletion", + "LeadFormDisclaimer", + "LeadFormDisclaimerCheckbox", + "LeadFormIntro", + "LeadFormPrivacyPolicy", + "LeadFormQuestion", + "LeadFormQuestionOption", + "LeadFormQuestionOptionLogic", + "MessagingConfig", + "SocialAccount", +] class AdCreateParams(TypedDict, total=False): @@ -21,7 +35,30 @@ class AdCreateParams(TypedDict, total=False): ad_group_id: str """The existing ad group to create the ad in. Provide this OR ad_group, not both.""" - call_to_action: Literal["shop_now", "learn_more", "sign_up", "subscribe", "order_now", "get_offer", "see_details"] + call_to_action: Literal[ + "apply_now", + "book_now", + "call_now", + "contact_us", + "download", + "get_directions", + "get_offer", + "get_quote", + "learn_more", + "listen_now", + "message_page", + "no_button", + "open_link", + "order_now", + "request_time", + "see_details", + "see_menu", + "send_updates", + "shop_now", + "sign_up", + "subscribe", + "watch_more", + ] """The call-to-action button shown on the ad.""" creatives: Iterable[Creative] @@ -37,6 +74,32 @@ class AdCreateParams(TypedDict, total=False): headlines: SequenceNotStr[str] """The headline variants shown on the ad.""" + lead_form: LeadForm + """Instant lead form for the ad. + + Only allowed when the ad group's conversion_location is an instant-form + destination (instant_forms, instant_forms_and_messenger, + website_and_instant_forms). + """ + + messaging_config: MessagingConfig + """ + Click-to-message welcome copy: the greeting (message) and the ice-breaker prompt + (keyword). + """ + + multi_advertiser_ads: bool + """Whether the ad can appear alongside other advertisers' ads in the same unit. + + Defaults to true. + """ + + post_id: str + """ + Promote an existing post instead of uploading creatives — a Facebook post or + Instagram media id. Mutually exclusive with creatives. + """ + primary_texts: SequenceNotStr[str] """The primary text variants shown in the ad body.""" @@ -59,5 +122,162 @@ class Creative(TypedDict, total=False): format: Literal["square", "vertical", "horizontal"] +class LeadFormCompletion(TypedDict, total=False): + """ + Optional completion screen shown after submission; url sets the follow-up website button. + """ + + button_text: str + + description: str + + headline: str + + url: str + + +class LeadFormDisclaimerCheckbox(TypedDict, total=False): + checked_by_default: bool + + key: str + + required: bool + + text: str + + +class LeadFormDisclaimer(TypedDict, total=False): + """Optional custom consent disclaimer with checkboxes.""" + + body: str + + checkboxes: Iterable[LeadFormDisclaimerCheckbox] + + title: str + + +class LeadFormIntro(TypedDict, total=False): + """ + Optional intro screen shown before the questions; background_image_url sets a custom background. + """ + + background_image_url: str + + description: str + + headline: str + + +class LeadFormPrivacyPolicy(TypedDict, total=False): + """Your privacy policy. url is required by Meta.""" + + link_text: str + + url: str + + +class LeadFormQuestionOptionLogic(TypedDict, total=False): + action: Literal["go_to_question", "submit_form", "close_form"] + + target_end_page_index: int + + target_question_index: int + + +class LeadFormQuestionOption(TypedDict, total=False): + key: str + + logic: LeadFormQuestionOptionLogic + + value: str + + +class LeadFormQuestion(TypedDict, total=False): + format: Literal["short_answer", "multiple_choice", "appointment"] + + label: str + + options: Iterable[LeadFormQuestionOption] + + type: Literal[ + "email", + "phone", + "full_name", + "first_name", + "last_name", + "city", + "state", + "zip", + "country", + "street_address", + "job_title", + "company_name", + "work_email", + "work_phone_number", + "dob", + "gender", + "marital_status", + "relationship_status", + "military_status", + "date_time", + "custom", + ] + + +class LeadForm(TypedDict, total=False): + """Instant lead form for the ad. + + Only allowed when the ad group's conversion_location is an instant-form destination (instant_forms, instant_forms_and_messenger, website_and_instant_forms). + """ + + completion: LeadFormCompletion + """ + Optional completion screen shown after submission; url sets the follow-up + website button. + """ + + disclaimer: LeadFormDisclaimer + """Optional custom consent disclaimer with checkboxes.""" + + form_type: Literal["more_volume", "higher_intent"] + """ + more_volume (default) is quickest to submit; higher_intent adds a confirmation + step. + """ + + intro: LeadFormIntro + """ + Optional intro screen shown before the questions; background_image_url sets a + custom background. + """ + + name: str + """Internal name for the form. Auto-generated if omitted.""" + + phone_verification: bool + """Require SMS verification of the phone number (higher_intent forms).""" + + privacy_policy: LeadFormPrivacyPolicy + """Your privacy policy. url is required by Meta.""" + + questions: Iterable[LeadFormQuestion] + """The questions on the form. + + Standard prefill types need only a type; a custom question needs a label and a + format (plus options for multiple_choice). Options carry an optional key and + answer-routing logic. + """ + + +class MessagingConfig(TypedDict, total=False): + """ + Click-to-message welcome copy: the greeting (message) and the ice-breaker prompt (keyword). + """ + + keyword: str + + message: str + + class SocialAccount(TypedDict, total=False): id: str diff --git a/src/whop_sdk/types/ad_update_params.py b/src/whop_sdk/types/ad_update_params.py index 1e24caf3..4c20e25f 100644 --- a/src/whop_sdk/types/ad_update_params.py +++ b/src/whop_sdk/types/ad_update_params.py @@ -7,11 +7,48 @@ from .._types import SequenceNotStr -__all__ = ["AdUpdateParams", "Creative", "SocialAccount"] +__all__ = [ + "AdUpdateParams", + "Creative", + "LeadForm", + "LeadFormCompletion", + "LeadFormDisclaimer", + "LeadFormDisclaimerCheckbox", + "LeadFormIntro", + "LeadFormPrivacyPolicy", + "LeadFormQuestion", + "LeadFormQuestionOption", + "LeadFormQuestionOptionLogic", + "MessagingConfig", + "SocialAccount", +] class AdUpdateParams(TypedDict, total=False): - call_to_action: Literal["shop_now", "learn_more", "sign_up", "subscribe", "order_now", "get_offer", "see_details"] + call_to_action: Literal[ + "apply_now", + "book_now", + "call_now", + "contact_us", + "download", + "get_directions", + "get_offer", + "get_quote", + "learn_more", + "listen_now", + "message_page", + "no_button", + "open_link", + "order_now", + "request_time", + "see_details", + "see_menu", + "send_updates", + "shop_now", + "sign_up", + "subscribe", + "watch_more", + ] """The call-to-action button shown on the ad.""" creatives: Iterable[Creative] @@ -27,6 +64,32 @@ class AdUpdateParams(TypedDict, total=False): headlines: SequenceNotStr[str] """The headline variants shown on the ad.""" + lead_form: LeadForm + """Instant lead form for the ad. + + Only allowed when the ad group's conversion_location is an instant-form + destination (instant_forms, instant_forms_and_messenger, + website_and_instant_forms). + """ + + messaging_config: MessagingConfig + """ + Click-to-message welcome copy: the greeting (message) and the ice-breaker prompt + (keyword). + """ + + multi_advertiser_ads: bool + """Whether the ad can appear alongside other advertisers' ads in the same unit. + + Defaults to true. + """ + + post_id: str + """ + Promote an existing post instead of uploading creatives — a Facebook post or + Instagram media id. Mutually exclusive with creatives. + """ + primary_texts: SequenceNotStr[str] """The primary text variants shown in the ad body.""" @@ -49,5 +112,162 @@ class Creative(TypedDict, total=False): format: Literal["square", "vertical", "horizontal"] +class LeadFormCompletion(TypedDict, total=False): + """ + Optional completion screen shown after submission; url sets the follow-up website button. + """ + + button_text: str + + description: str + + headline: str + + url: str + + +class LeadFormDisclaimerCheckbox(TypedDict, total=False): + checked_by_default: bool + + key: str + + required: bool + + text: str + + +class LeadFormDisclaimer(TypedDict, total=False): + """Optional custom consent disclaimer with checkboxes.""" + + body: str + + checkboxes: Iterable[LeadFormDisclaimerCheckbox] + + title: str + + +class LeadFormIntro(TypedDict, total=False): + """ + Optional intro screen shown before the questions; background_image_url sets a custom background. + """ + + background_image_url: str + + description: str + + headline: str + + +class LeadFormPrivacyPolicy(TypedDict, total=False): + """Your privacy policy. url is required by Meta.""" + + link_text: str + + url: str + + +class LeadFormQuestionOptionLogic(TypedDict, total=False): + action: Literal["go_to_question", "submit_form", "close_form"] + + target_end_page_index: int + + target_question_index: int + + +class LeadFormQuestionOption(TypedDict, total=False): + key: str + + logic: LeadFormQuestionOptionLogic + + value: str + + +class LeadFormQuestion(TypedDict, total=False): + format: Literal["short_answer", "multiple_choice", "appointment"] + + label: str + + options: Iterable[LeadFormQuestionOption] + + type: Literal[ + "email", + "phone", + "full_name", + "first_name", + "last_name", + "city", + "state", + "zip", + "country", + "street_address", + "job_title", + "company_name", + "work_email", + "work_phone_number", + "dob", + "gender", + "marital_status", + "relationship_status", + "military_status", + "date_time", + "custom", + ] + + +class LeadForm(TypedDict, total=False): + """Instant lead form for the ad. + + Only allowed when the ad group's conversion_location is an instant-form destination (instant_forms, instant_forms_and_messenger, website_and_instant_forms). + """ + + completion: LeadFormCompletion + """ + Optional completion screen shown after submission; url sets the follow-up + website button. + """ + + disclaimer: LeadFormDisclaimer + """Optional custom consent disclaimer with checkboxes.""" + + form_type: Literal["more_volume", "higher_intent"] + """ + more_volume (default) is quickest to submit; higher_intent adds a confirmation + step. + """ + + intro: LeadFormIntro + """ + Optional intro screen shown before the questions; background_image_url sets a + custom background. + """ + + name: str + """Internal name for the form. Auto-generated if omitted.""" + + phone_verification: bool + """Require SMS verification of the phone number (higher_intent forms).""" + + privacy_policy: LeadFormPrivacyPolicy + """Your privacy policy. url is required by Meta.""" + + questions: Iterable[LeadFormQuestion] + """The questions on the form. + + Standard prefill types need only a type; a custom question needs a label and a + format (plus options for multiple_choice). Options carry an optional key and + answer-routing logic. + """ + + +class MessagingConfig(TypedDict, total=False): + """ + Click-to-message welcome copy: the greeting (message) and the ice-breaker prompt (keyword). + """ + + keyword: str + + message: str + + class SocialAccount(TypedDict, total=False): id: str diff --git a/tests/api_resources/test_ad_campaigns.py b/tests/api_resources/test_ad_campaigns.py index c770c24d..2abd73d5 100644 --- a/tests/api_resources/test_ad_campaigns.py +++ b/tests/api_resources/test_ad_campaigns.py @@ -148,6 +148,7 @@ def test_method_update_with_all_params(self, client: Whop) -> None: budget_amount=0, ends_at="ends_at", starts_at="starts_at", + status="active", title="title", ) assert_matches_type(AdCampaign, ad_campaign, path=["response"]) @@ -493,6 +494,7 @@ async def test_method_update_with_all_params(self, async_client: AsyncWhop) -> N budget_amount=0, ends_at="ends_at", starts_at="starts_at", + status="active", title="title", ) assert_matches_type(AdCampaign, ad_campaign, path=["response"]) diff --git a/tests/api_resources/test_ads.py b/tests/api_resources/test_ads.py index 7da7c43a..8fbba584 100644 --- a/tests/api_resources/test_ads.py +++ b/tests/api_resources/test_ads.py @@ -30,7 +30,7 @@ def test_method_create_with_all_params(self, client: Whop) -> None: ad = client.ads.create( ad_group={}, ad_group_id="ad_group_id", - call_to_action="shop_now", + call_to_action="apply_now", creatives=[ { "id": "id", @@ -39,6 +39,62 @@ def test_method_create_with_all_params(self, client: Whop) -> None: ], descriptions=["string"], headlines=["string"], + lead_form={ + "completion": { + "button_text": "button_text", + "description": "description", + "headline": "headline", + "url": "url", + }, + "disclaimer": { + "body": "body", + "checkboxes": [ + { + "checked_by_default": True, + "key": "key", + "required": True, + "text": "text", + } + ], + "title": "title", + }, + "form_type": "more_volume", + "intro": { + "background_image_url": "background_image_url", + "description": "description", + "headline": "headline", + }, + "name": "name", + "phone_verification": True, + "privacy_policy": { + "link_text": "link_text", + "url": "url", + }, + "questions": [ + { + "format": "short_answer", + "label": "label", + "options": [ + { + "key": "key", + "logic": { + "action": "go_to_question", + "target_end_page_index": 0, + "target_question_index": 0, + }, + "value": "value", + } + ], + "type": "email", + } + ], + }, + messaging_config={ + "keyword": "keyword", + "message": "message", + }, + multi_advertiser_ads=True, + post_id="post_id", primary_texts=["string"], social_accounts=[{"id": "id"}], title="title", @@ -134,7 +190,7 @@ def test_method_update(self, client: Whop) -> None: def test_method_update_with_all_params(self, client: Whop) -> None: ad = client.ads.update( id="id", - call_to_action="shop_now", + call_to_action="apply_now", creatives=[ { "id": "id", @@ -143,6 +199,62 @@ def test_method_update_with_all_params(self, client: Whop) -> None: ], descriptions=["string"], headlines=["string"], + lead_form={ + "completion": { + "button_text": "button_text", + "description": "description", + "headline": "headline", + "url": "url", + }, + "disclaimer": { + "body": "body", + "checkboxes": [ + { + "checked_by_default": True, + "key": "key", + "required": True, + "text": "text", + } + ], + "title": "title", + }, + "form_type": "more_volume", + "intro": { + "background_image_url": "background_image_url", + "description": "description", + "headline": "headline", + }, + "name": "name", + "phone_verification": True, + "privacy_policy": { + "link_text": "link_text", + "url": "url", + }, + "questions": [ + { + "format": "short_answer", + "label": "label", + "options": [ + { + "key": "key", + "logic": { + "action": "go_to_question", + "target_end_page_index": 0, + "target_question_index": 0, + }, + "value": "value", + } + ], + "type": "email", + } + ], + }, + messaging_config={ + "keyword": "keyword", + "message": "message", + }, + multi_advertiser_ads=True, + post_id="post_id", primary_texts=["string"], social_accounts=[{"id": "id"}], title="title", @@ -379,7 +491,7 @@ async def test_method_create_with_all_params(self, async_client: AsyncWhop) -> N ad = await async_client.ads.create( ad_group={}, ad_group_id="ad_group_id", - call_to_action="shop_now", + call_to_action="apply_now", creatives=[ { "id": "id", @@ -388,6 +500,62 @@ async def test_method_create_with_all_params(self, async_client: AsyncWhop) -> N ], descriptions=["string"], headlines=["string"], + lead_form={ + "completion": { + "button_text": "button_text", + "description": "description", + "headline": "headline", + "url": "url", + }, + "disclaimer": { + "body": "body", + "checkboxes": [ + { + "checked_by_default": True, + "key": "key", + "required": True, + "text": "text", + } + ], + "title": "title", + }, + "form_type": "more_volume", + "intro": { + "background_image_url": "background_image_url", + "description": "description", + "headline": "headline", + }, + "name": "name", + "phone_verification": True, + "privacy_policy": { + "link_text": "link_text", + "url": "url", + }, + "questions": [ + { + "format": "short_answer", + "label": "label", + "options": [ + { + "key": "key", + "logic": { + "action": "go_to_question", + "target_end_page_index": 0, + "target_question_index": 0, + }, + "value": "value", + } + ], + "type": "email", + } + ], + }, + messaging_config={ + "keyword": "keyword", + "message": "message", + }, + multi_advertiser_ads=True, + post_id="post_id", primary_texts=["string"], social_accounts=[{"id": "id"}], title="title", @@ -483,7 +651,7 @@ async def test_method_update(self, async_client: AsyncWhop) -> None: async def test_method_update_with_all_params(self, async_client: AsyncWhop) -> None: ad = await async_client.ads.update( id="id", - call_to_action="shop_now", + call_to_action="apply_now", creatives=[ { "id": "id", @@ -492,6 +660,62 @@ async def test_method_update_with_all_params(self, async_client: AsyncWhop) -> N ], descriptions=["string"], headlines=["string"], + lead_form={ + "completion": { + "button_text": "button_text", + "description": "description", + "headline": "headline", + "url": "url", + }, + "disclaimer": { + "body": "body", + "checkboxes": [ + { + "checked_by_default": True, + "key": "key", + "required": True, + "text": "text", + } + ], + "title": "title", + }, + "form_type": "more_volume", + "intro": { + "background_image_url": "background_image_url", + "description": "description", + "headline": "headline", + }, + "name": "name", + "phone_verification": True, + "privacy_policy": { + "link_text": "link_text", + "url": "url", + }, + "questions": [ + { + "format": "short_answer", + "label": "label", + "options": [ + { + "key": "key", + "logic": { + "action": "go_to_question", + "target_end_page_index": 0, + "target_question_index": 0, + }, + "value": "value", + } + ], + "type": "email", + } + ], + }, + messaging_config={ + "keyword": "keyword", + "message": "message", + }, + multi_advertiser_ads=True, + post_id="post_id", primary_texts=["string"], social_accounts=[{"id": "id"}], title="title", From 1cb4c9ff7f17bf2eeb0a48001dcdc09e28bdf638 Mon Sep 17 00:00:00 2001 From: stlc-bot Date: Wed, 1 Jul 2026 22:21:33 +0000 Subject: [PATCH 101/109] Improve Audiences API docs Stainless-Generated-From: 1507327c7dfa0fb2bffeb9aff19293dba5b03f92 --- src/whop_sdk/resources/audiences.py | 68 ++++++++++---------- src/whop_sdk/types/audience.py | 30 +++++---- src/whop_sdk/types/audience_create_params.py | 39 ++++++++--- src/whop_sdk/types/audience_list_params.py | 14 ++-- tests/api_resources/test_audiences.py | 46 +++++++++++-- 5 files changed, 125 insertions(+), 72 deletions(-) diff --git a/src/whop_sdk/resources/audiences.py b/src/whop_sdk/resources/audiences.py index 0c173533..edb747e6 100644 --- a/src/whop_sdk/resources/audiences.py +++ b/src/whop_sdk/resources/audiences.py @@ -2,8 +2,6 @@ from __future__ import annotations -from typing import Dict - import httpx from ..types import audience_list_params, audience_create_params @@ -49,7 +47,7 @@ def create( self, *, account_id: str, - column_mapping: Dict[str, str], + column_mapping: audience_create_params.ColumnMapping, file_id: str, name: str, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. @@ -60,17 +58,18 @@ def create( timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> Audience: """ - Creates a custom audience from an uploaded CSV file and starts processing it. + Creates an audience from an uploaded customer identity CSV file and starts + processing it. Args: - account_id: The ID of the account that will own the audience. + account_id: Account ID, prefixed `biz_`. - column_mapping: Map of identity field (email, phone, first_name, last_name, country) to the CSV - column header that holds it. Map at least an email or phone column. + column_mapping: Maps supported identity fields to CSV column headers. Map at least one of + `email` or `phone`. - file_id: A direct upload ID returned by the standard media upload endpoint. + file_id: Direct upload ID from the standard media upload endpoint. - name: A display name for the audience. + name: Audience display name. extra_headers: Send extra headers @@ -111,19 +110,19 @@ def list( extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> SyncCursorPage[Audience]: - """ - Lists the custom audiences (uploaded CSV customer lists) for an account. + """Lists uploaded customer-list audiences for an account. + + Pass `audience_id` to + return a specific audience. Args: - account_id: The ID of the account that owns the audiences, which will look like - biz\\__******\\********. + account_id: Account ID, prefixed `biz_`. - after: A cursor; returns audiences after this position. + after: Cursor for the next page of audiences. - audience_id: Optional audience ID to filter the response to one audience, which will look - like adaud\\__******\\********. + audience_id: Audience ID, prefixed `adaud_`, used to filter the response to one audience. - first: The number of audiences to return (default 20, max 100). + first: Number of audiences to return. Defaults to 20; maximum 100. extra_headers: Send extra headers @@ -166,7 +165,7 @@ def delete( timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> AudienceDeleteResponse: """ - Deletes (soft-discards) a custom audience. + Deletes an audience so it is no longer available for targeting. Args: extra_headers: Send extra headers @@ -212,7 +211,7 @@ async def create( self, *, account_id: str, - column_mapping: Dict[str, str], + column_mapping: audience_create_params.ColumnMapping, file_id: str, name: str, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. @@ -223,17 +222,18 @@ async def create( timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> Audience: """ - Creates a custom audience from an uploaded CSV file and starts processing it. + Creates an audience from an uploaded customer identity CSV file and starts + processing it. Args: - account_id: The ID of the account that will own the audience. + account_id: Account ID, prefixed `biz_`. - column_mapping: Map of identity field (email, phone, first_name, last_name, country) to the CSV - column header that holds it. Map at least an email or phone column. + column_mapping: Maps supported identity fields to CSV column headers. Map at least one of + `email` or `phone`. - file_id: A direct upload ID returned by the standard media upload endpoint. + file_id: Direct upload ID from the standard media upload endpoint. - name: A display name for the audience. + name: Audience display name. extra_headers: Send extra headers @@ -274,19 +274,19 @@ def list( extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> AsyncPaginator[Audience, AsyncCursorPage[Audience]]: - """ - Lists the custom audiences (uploaded CSV customer lists) for an account. + """Lists uploaded customer-list audiences for an account. + + Pass `audience_id` to + return a specific audience. Args: - account_id: The ID of the account that owns the audiences, which will look like - biz\\__******\\********. + account_id: Account ID, prefixed `biz_`. - after: A cursor; returns audiences after this position. + after: Cursor for the next page of audiences. - audience_id: Optional audience ID to filter the response to one audience, which will look - like adaud\\__******\\********. + audience_id: Audience ID, prefixed `adaud_`, used to filter the response to one audience. - first: The number of audiences to return (default 20, max 100). + first: Number of audiences to return. Defaults to 20; maximum 100. extra_headers: Send extra headers @@ -329,7 +329,7 @@ async def delete( timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> AudienceDeleteResponse: """ - Deletes (soft-discards) a custom audience. + Deletes an audience so it is no longer available for targeting. Args: extra_headers: Send extra headers diff --git a/src/whop_sdk/types/audience.py b/src/whop_sdk/types/audience.py index bb1a1686..7adf00d5 100644 --- a/src/whop_sdk/types/audience.py +++ b/src/whop_sdk/types/audience.py @@ -1,6 +1,7 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from typing import List, Optional +from typing_extensions import Literal from .._models import BaseModel @@ -9,34 +10,37 @@ class Audience(BaseModel): id: str - """The ID of the audience, which will look like adaud\\__******\\********""" + """Audience ID, prefixed `adaud_`.""" created_at: float - """When the audience was created, as a Unix timestamp""" + """Unix timestamp when the audience was created.""" error_message: Optional[str] = None - """Populated when the audience is partial or failed""" + """Processing error message. `null` unless processing is partial or failed.""" matched_rows: float - """Rows uploaded to the ad platform""" + """Rows successfully uploaded to connected ad accounts.""" name: str - """The display name of the audience""" + """Audience display name.""" - platform_audience_ids: List[object] - """External ad-platform audience IDs created for this audience""" + platform_audience_ids: List[str] processed_rows: float - """Rows ingested so far""" + """Rows processed from the uploaded CSV.""" progress_percent: float - """Processing progress from 0 to 100""" + """Processing progress from 0 to 100.""" - status: str - """Processing status: pending, processing, syncing, ready, partial, or failed""" + status: Literal["pending", "processing", "syncing", "ready", "partial", "failed"] + """Current state of the audience import. + + `syncing` means Whop is sending matched rows to connected ad accounts. When + status is `partial` or `failed`, `error_message` explains what went wrong. + """ total_rows: float - """Total data rows detected in the uploaded CSV""" + """Total rows detected in the uploaded CSV.""" updated_at: float - """When the audience was last updated, as a Unix timestamp""" + """Unix timestamp when the audience was last updated.""" diff --git a/src/whop_sdk/types/audience_create_params.py b/src/whop_sdk/types/audience_create_params.py index dea64354..b33e1550 100644 --- a/src/whop_sdk/types/audience_create_params.py +++ b/src/whop_sdk/types/audience_create_params.py @@ -2,24 +2,45 @@ from __future__ import annotations -from typing import Dict from typing_extensions import Required, TypedDict -__all__ = ["AudienceCreateParams"] +__all__ = ["AudienceCreateParams", "ColumnMapping"] class AudienceCreateParams(TypedDict, total=False): account_id: Required[str] - """The ID of the account that will own the audience.""" + """Account ID, prefixed `biz_`.""" - column_mapping: Required[Dict[str, str]] - """ - Map of identity field (email, phone, first_name, last_name, country) to the CSV - column header that holds it. Map at least an email or phone column. + column_mapping: Required[ColumnMapping] + """Maps supported identity fields to CSV column headers. + + Map at least one of `email` or `phone`. """ file_id: Required[str] - """A direct upload ID returned by the standard media upload endpoint.""" + """Direct upload ID from the standard media upload endpoint.""" name: Required[str] - """A display name for the audience.""" + """Audience display name.""" + + +class ColumnMapping(TypedDict, total=False): + """Maps supported identity fields to CSV column headers. + + Map at least one of `email` or `phone`. + """ + + country: str + """CSV header for ISO 3166-1 alpha-2 country codes, such as `US`.""" + + email: str + """CSV header for email addresses.""" + + first_name: str + """CSV header for first names.""" + + last_name: str + """CSV header for last names.""" + + phone: str + """CSV header for phone numbers.""" diff --git a/src/whop_sdk/types/audience_list_params.py b/src/whop_sdk/types/audience_list_params.py index 32b9cbd7..898c5406 100644 --- a/src/whop_sdk/types/audience_list_params.py +++ b/src/whop_sdk/types/audience_list_params.py @@ -9,19 +9,13 @@ class AudienceListParams(TypedDict, total=False): account_id: Required[str] - """ - The ID of the account that owns the audiences, which will look like - biz\\__******\\********. - """ + """Account ID, prefixed `biz_`.""" after: str - """A cursor; returns audiences after this position.""" + """Cursor for the next page of audiences.""" audience_id: str - """ - Optional audience ID to filter the response to one audience, which will look - like adaud\\__******\\********. - """ + """Audience ID, prefixed `adaud_`, used to filter the response to one audience.""" first: int - """The number of audiences to return (default 20, max 100).""" + """Number of audiences to return. Defaults to 20; maximum 100.""" diff --git a/tests/api_resources/test_audiences.py b/tests/api_resources/test_audiences.py index 21646aa1..7ca8a204 100644 --- a/tests/api_resources/test_audiences.py +++ b/tests/api_resources/test_audiences.py @@ -23,7 +23,24 @@ class TestAudiences: def test_method_create(self, client: Whop) -> None: audience = client.audiences.create( account_id="account_id", - column_mapping={"foo": "string"}, + column_mapping={}, + file_id="file_id", + name="name", + ) + assert_matches_type(Audience, audience, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_create_with_all_params(self, client: Whop) -> None: + audience = client.audiences.create( + account_id="account_id", + column_mapping={ + "country": "country", + "email": "email", + "first_name": "first_name", + "last_name": "last_name", + "phone": "phone", + }, file_id="file_id", name="name", ) @@ -34,7 +51,7 @@ def test_method_create(self, client: Whop) -> None: def test_raw_response_create(self, client: Whop) -> None: response = client.audiences.with_raw_response.create( account_id="account_id", - column_mapping={"foo": "string"}, + column_mapping={}, file_id="file_id", name="name", ) @@ -49,7 +66,7 @@ def test_raw_response_create(self, client: Whop) -> None: def test_streaming_response_create(self, client: Whop) -> None: with client.audiences.with_streaming_response.create( account_id="account_id", - column_mapping={"foo": "string"}, + column_mapping={}, file_id="file_id", name="name", ) as response: @@ -159,7 +176,24 @@ class TestAsyncAudiences: async def test_method_create(self, async_client: AsyncWhop) -> None: audience = await async_client.audiences.create( account_id="account_id", - column_mapping={"foo": "string"}, + column_mapping={}, + file_id="file_id", + name="name", + ) + assert_matches_type(Audience, audience, 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: + audience = await async_client.audiences.create( + account_id="account_id", + column_mapping={ + "country": "country", + "email": "email", + "first_name": "first_name", + "last_name": "last_name", + "phone": "phone", + }, file_id="file_id", name="name", ) @@ -170,7 +204,7 @@ async def test_method_create(self, async_client: AsyncWhop) -> None: async def test_raw_response_create(self, async_client: AsyncWhop) -> None: response = await async_client.audiences.with_raw_response.create( account_id="account_id", - column_mapping={"foo": "string"}, + column_mapping={}, file_id="file_id", name="name", ) @@ -185,7 +219,7 @@ async def test_raw_response_create(self, async_client: AsyncWhop) -> None: async def test_streaming_response_create(self, async_client: AsyncWhop) -> None: async with async_client.audiences.with_streaming_response.create( account_id="account_id", - column_mapping={"foo": "string"}, + column_mapping={}, file_id="file_id", name="name", ) as response: From 357abf7d98c6421e134467782851b757bd1b3bac Mon Sep 17 00:00:00 2001 From: stlc-bot Date: Wed, 1 Jul 2026 23:00:45 +0000 Subject: [PATCH 102/109] Require a $5 minimum total budget on workforce bounties Stainless-Generated-From: 48c2ef7be8466c2064d54dad4ab35615a5824085 --- src/whop_sdk/resources/bounties.py | 12 ++++++++---- src/whop_sdk/types/bounty_create_params.py | 6 ++++-- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/src/whop_sdk/resources/bounties.py b/src/whop_sdk/resources/bounties.py index 7f2a1f46..033c9929 100644 --- a/src/whop_sdk/resources/bounties.py +++ b/src/whop_sdk/resources/bounties.py @@ -88,7 +88,8 @@ def create( Args: base_unit_amount: The amount paid to each approved submission. The total bounty pool funded is - this amount times accepted_submissions_limit. + this amount times accepted_submissions_limit, and must be at least 5 in the + bounty's currency. currency: The currency for the bounty pool funding amount. @@ -97,7 +98,8 @@ def create( title: The title of the bounty. accepted_submissions_limit: The number of submissions that can be approved before the bounty closes. - Defaults to 1. + Defaults to 1. The total pool (base_unit_amount times this limit) must be at + least 5 in the bounty's currency. allowed_country_codes: The ISO3166 country codes where this bounty should be visible. Empty means globally visible. @@ -323,7 +325,8 @@ async def create( Args: base_unit_amount: The amount paid to each approved submission. The total bounty pool funded is - this amount times accepted_submissions_limit. + this amount times accepted_submissions_limit, and must be at least 5 in the + bounty's currency. currency: The currency for the bounty pool funding amount. @@ -332,7 +335,8 @@ async def create( title: The title of the bounty. accepted_submissions_limit: The number of submissions that can be approved before the bounty closes. - Defaults to 1. + Defaults to 1. The total pool (base_unit_amount times this limit) must be at + least 5 in the bounty's currency. allowed_country_codes: The ISO3166 country codes where this bounty should be visible. Empty means globally visible. diff --git a/src/whop_sdk/types/bounty_create_params.py b/src/whop_sdk/types/bounty_create_params.py index 277d4cc1..edc77eb9 100644 --- a/src/whop_sdk/types/bounty_create_params.py +++ b/src/whop_sdk/types/bounty_create_params.py @@ -17,7 +17,8 @@ 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. + The total bounty pool funded is this amount times accepted_submissions_limit, + and must be at least 5 in the bounty's currency. """ currency: Required[Currency] @@ -32,7 +33,8 @@ class BountyCreateParams(TypedDict, total=False): accepted_submissions_limit: Optional[int] """The number of submissions that can be approved before the bounty closes. - Defaults to 1. + Defaults to 1. The total pool (base_unit_amount times this limit) must be at + least 5 in the bounty's currency. """ allowed_country_codes: Optional[SequenceNotStr[str]] From 2a577b6c449b9d4cc6108f3e927654c425802e0a Mon Sep 17 00:00:00 2001 From: stlc-bot Date: Wed, 1 Jul 2026 23:44:28 +0000 Subject: [PATCH 103/109] Fix lead form ad creation by removing the undeliverable cover image and rich creative options Stainless-Generated-From: 1924c818742da26f8168240ac69a5a94d4a2baf1 --- src/whop_sdk/types/ad_create_params.py | 11 ++--------- src/whop_sdk/types/ad_update_params.py | 11 ++--------- tests/api_resources/test_ads.py | 4 ---- 3 files changed, 4 insertions(+), 22 deletions(-) diff --git a/src/whop_sdk/types/ad_create_params.py b/src/whop_sdk/types/ad_create_params.py index f8704f35..25f3e452 100644 --- a/src/whop_sdk/types/ad_create_params.py +++ b/src/whop_sdk/types/ad_create_params.py @@ -157,11 +157,7 @@ class LeadFormDisclaimer(TypedDict, total=False): class LeadFormIntro(TypedDict, total=False): - """ - Optional intro screen shown before the questions; background_image_url sets a custom background. - """ - - background_image_url: str + """Optional intro screen shown before the questions.""" description: str @@ -246,10 +242,7 @@ class LeadForm(TypedDict, total=False): """ intro: LeadFormIntro - """ - Optional intro screen shown before the questions; background_image_url sets a - custom background. - """ + """Optional intro screen shown before the questions.""" name: str """Internal name for the form. Auto-generated if omitted.""" diff --git a/src/whop_sdk/types/ad_update_params.py b/src/whop_sdk/types/ad_update_params.py index 4c20e25f..653bf6dd 100644 --- a/src/whop_sdk/types/ad_update_params.py +++ b/src/whop_sdk/types/ad_update_params.py @@ -147,11 +147,7 @@ class LeadFormDisclaimer(TypedDict, total=False): class LeadFormIntro(TypedDict, total=False): - """ - Optional intro screen shown before the questions; background_image_url sets a custom background. - """ - - background_image_url: str + """Optional intro screen shown before the questions.""" description: str @@ -236,10 +232,7 @@ class LeadForm(TypedDict, total=False): """ intro: LeadFormIntro - """ - Optional intro screen shown before the questions; background_image_url sets a - custom background. - """ + """Optional intro screen shown before the questions.""" name: str """Internal name for the form. Auto-generated if omitted.""" diff --git a/tests/api_resources/test_ads.py b/tests/api_resources/test_ads.py index 8fbba584..69bc5284 100644 --- a/tests/api_resources/test_ads.py +++ b/tests/api_resources/test_ads.py @@ -60,7 +60,6 @@ def test_method_create_with_all_params(self, client: Whop) -> None: }, "form_type": "more_volume", "intro": { - "background_image_url": "background_image_url", "description": "description", "headline": "headline", }, @@ -220,7 +219,6 @@ def test_method_update_with_all_params(self, client: Whop) -> None: }, "form_type": "more_volume", "intro": { - "background_image_url": "background_image_url", "description": "description", "headline": "headline", }, @@ -521,7 +519,6 @@ async def test_method_create_with_all_params(self, async_client: AsyncWhop) -> N }, "form_type": "more_volume", "intro": { - "background_image_url": "background_image_url", "description": "description", "headline": "headline", }, @@ -681,7 +678,6 @@ async def test_method_update_with_all_params(self, async_client: AsyncWhop) -> N }, "form_type": "more_volume", "intro": { - "background_image_url": "background_image_url", "description": "description", "headline": "headline", }, From beb3bf47a2f8b2a3929aa2ea969eb7318768f05a Mon Sep 17 00:00:00 2001 From: stlc-bot Date: Thu, 2 Jul 2026 00:42:43 +0000 Subject: [PATCH 104/109] Show creators specific, actionable ad issue messages Stainless-Generated-From: 7fe961088e3fdeea044a8094b747a1df705b76d9 --- src/whop_sdk/types/ad.py | 4 ++-- src/whop_sdk/types/ad_campaign.py | 4 ++-- src/whop_sdk/types/ad_group.py | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/whop_sdk/types/ad.py b/src/whop_sdk/types/ad.py index 9a0cac84..3b80d2bc 100644 --- a/src/whop_sdk/types/ad.py +++ b/src/whop_sdk/types/ad.py @@ -14,8 +14,8 @@ class Issue(BaseModel): id: str """Unique identifier for the issue.""" - category: Optional[Literal["policy_rejection", "creative_media", "audience_targeting", "ad_volume_limit"]] = None - """The kind of problem the issue represents.""" + message: str + """A description of what the issue is and how it can be resolved.""" resource_id: Optional[str] = None """The ID of the campaign, ad group, or ad the issue is attached to.""" diff --git a/src/whop_sdk/types/ad_campaign.py b/src/whop_sdk/types/ad_campaign.py index 84902b72..1547da81 100644 --- a/src/whop_sdk/types/ad_campaign.py +++ b/src/whop_sdk/types/ad_campaign.py @@ -14,8 +14,8 @@ class Issue(BaseModel): id: str """Unique identifier for the issue.""" - category: Optional[Literal["policy_rejection", "creative_media", "audience_targeting", "ad_volume_limit"]] = None - """The kind of problem the issue represents.""" + message: str + """A description of what the issue is and how it can be resolved.""" resource_id: Optional[str] = None """The ID of the campaign, ad group, or ad the issue is attached to.""" diff --git a/src/whop_sdk/types/ad_group.py b/src/whop_sdk/types/ad_group.py index 553e0f40..c25a0c51 100644 --- a/src/whop_sdk/types/ad_group.py +++ b/src/whop_sdk/types/ad_group.py @@ -14,8 +14,8 @@ class Issue(BaseModel): id: str """Unique identifier for the issue.""" - category: Optional[Literal["policy_rejection", "creative_media", "audience_targeting", "ad_volume_limit"]] = None - """The kind of problem the issue represents.""" + message: str + """A description of what the issue is and how it can be resolved.""" resource_id: Optional[str] = None """The ID of the campaign, ad group, or ad the issue is attached to.""" From c9517286cd4dc640e9d169872a8bf65e192a2ddb Mon Sep 17 00:00:00 2001 From: stlc-bot Date: Thu, 2 Jul 2026 07:13:46 +0000 Subject: [PATCH 105/109] feat(ads): migrate dashboard to REST end to end Stainless-Generated-From: e93951b886f3230be791bd50fc4ebea1f7fb3f2c --- api.md | 6 +- src/whop_sdk/resources/ad_campaigns.py | 44 ++++- src/whop_sdk/resources/ad_groups.py | 176 ++++++++++++++++-- src/whop_sdk/resources/ads.py | 44 ++++- src/whop_sdk/types/__init__.py | 2 +- src/whop_sdk/types/ad.py | 72 ++++++- src/whop_sdk/types/ad_campaign.py | 39 +++- src/whop_sdk/types/ad_campaign_list_params.py | 23 ++- src/whop_sdk/types/ad_group.py | 42 ++++- src/whop_sdk/types/ad_group_list_params.py | 50 ++++- src/whop_sdk/types/ad_group_list_response.py | 14 -- .../types/ad_group_retrieve_params.py | 15 ++ src/whop_sdk/types/ad_list_params.py | 23 ++- tests/api_resources/test_ad_groups.py | 72 +++++-- 14 files changed, 549 insertions(+), 73 deletions(-) delete mode 100644 src/whop_sdk/types/ad_group_list_response.py create mode 100644 src/whop_sdk/types/ad_group_retrieve_params.py diff --git a/api.md b/api.md index db4d37ac..d0a919a3 100644 --- a/api.md +++ b/api.md @@ -1217,15 +1217,15 @@ Methods: Types: ```python -from whop_sdk.types import AdGroup, AdGroupListResponse, AdGroupDeleteResponse +from whop_sdk.types import AdGroup, AdGroupDeleteResponse ``` Methods: - client.ad_groups.create(\*\*params) -> AdGroup -- client.ad_groups.retrieve(id) -> AdGroup +- client.ad_groups.retrieve(id, \*\*params) -> AdGroup - client.ad_groups.update(id, \*\*params) -> AdGroup -- client.ad_groups.list(\*\*params) -> AdGroupListResponse +- client.ad_groups.list(\*\*params) -> SyncCursorPage[AdGroup] - client.ad_groups.delete(id) -> AdGroupDeleteResponse - client.ad_groups.pause(id) -> AdGroup - client.ad_groups.unpause(id) -> AdGroup diff --git a/src/whop_sdk/resources/ad_campaigns.py b/src/whop_sdk/resources/ad_campaigns.py index 232bb748..f8cfbce1 100644 --- a/src/whop_sdk/resources/ad_campaigns.py +++ b/src/whop_sdk/resources/ad_campaigns.py @@ -265,7 +265,23 @@ def list( direction: Literal["asc", "desc"] | Omit = omit, first: int | Omit = omit, last: int | Omit = omit, - order: Literal["created_at", "updated_at"] | Omit = omit, + order: Literal[ + "created_at", + "updated_at", + "spend", + "impressions", + "clicks", + "reach", + "unique_clicks", + "results", + "click_through_rate", + "cost_per_click", + "cost_per_mille", + "cost_per_result", + "frequency", + "return_on_ad_spend", + ] + | Omit = omit, query: str | Omit = omit, stats_from: str | Omit = omit, stats_to: str | Omit = omit, @@ -298,7 +314,9 @@ def list( last: The number of campaigns to return from the end of the range. - order: The field to sort by. Defaults to created_at. + order: The field to sort by. Defaults to created_at. Stat columns (spend, impressions, + …) rank over the stats_from/stats_to window across the whole list, not just the + current page. query: Filter campaigns by a title or ID substring. @@ -681,7 +699,23 @@ def list( direction: Literal["asc", "desc"] | Omit = omit, first: int | Omit = omit, last: int | Omit = omit, - order: Literal["created_at", "updated_at"] | Omit = omit, + order: Literal[ + "created_at", + "updated_at", + "spend", + "impressions", + "clicks", + "reach", + "unique_clicks", + "results", + "click_through_rate", + "cost_per_click", + "cost_per_mille", + "cost_per_result", + "frequency", + "return_on_ad_spend", + ] + | Omit = omit, query: str | Omit = omit, stats_from: str | Omit = omit, stats_to: str | Omit = omit, @@ -714,7 +748,9 @@ def list( last: The number of campaigns to return from the end of the range. - order: The field to sort by. Defaults to created_at. + order: The field to sort by. Defaults to created_at. Stat columns (spend, impressions, + …) rank over the stats_from/stats_to window across the whole list, not just the + current page. query: Filter campaigns by a title or ID substring. diff --git a/src/whop_sdk/resources/ad_groups.py b/src/whop_sdk/resources/ad_groups.py index 9945fbba..1ebaf668 100644 --- a/src/whop_sdk/resources/ad_groups.py +++ b/src/whop_sdk/resources/ad_groups.py @@ -7,7 +7,7 @@ import httpx -from ..types import ad_group_list_params, ad_group_create_params, ad_group_update_params +from ..types import ad_group_list_params, ad_group_create_params, ad_group_update_params, ad_group_retrieve_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 @@ -18,9 +18,9 @@ async_to_raw_response_wrapper, async_to_streamed_response_wrapper, ) -from .._base_client import make_request_options +from ..pagination import SyncCursorPage, AsyncCursorPage +from .._base_client import AsyncPaginator, make_request_options from ..types.ad_group import AdGroup -from ..types.ad_group_list_response import AdGroupListResponse from ..types.ad_group_delete_response import AdGroupDeleteResponse __all__ = ["AdGroupsResource", "AsyncAdGroupsResource"] @@ -212,6 +212,8 @@ def retrieve( self, id: str, *, + stats_from: str | Omit = omit, + stats_to: 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, @@ -223,6 +225,10 @@ def retrieve( Retrieves a single ad group. Args: + stats_from: Start of the stats window. + + stats_to: End of the stats window. + extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -236,7 +242,17 @@ def retrieve( 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 + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "stats_from": stats_from, + "stats_to": stats_to, + }, + ad_group_retrieve_params.AdGroupRetrieveParams, + ), ), cast_to=AdGroup, ) @@ -403,8 +419,33 @@ def list( *, account_id: str | Omit = omit, ad_campaign_id: str | Omit = omit, + after: str | Omit = omit, + before: str | Omit = omit, + created_after: str | Omit = omit, + created_before: str | Omit = omit, direction: Literal["asc", "desc"] | Omit = omit, - order: Literal["created_at", "updated_at"] | Omit = omit, + first: int | Omit = omit, + last: int | Omit = omit, + order: Literal[ + "created_at", + "updated_at", + "spend", + "impressions", + "clicks", + "reach", + "unique_clicks", + "results", + "click_through_rate", + "cost_per_click", + "cost_per_mille", + "cost_per_result", + "frequency", + "return_on_ad_spend", + ] + | Omit = omit, + query: str | Omit = omit, + stats_from: str | Omit = omit, + stats_to: str | Omit = omit, status: 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. @@ -412,7 +453,7 @@ def list( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> AdGroupListResponse: + ) -> SyncCursorPage[AdGroup]: """ Lists ad groups for the account, newest first. @@ -421,9 +462,29 @@ def list( ad_campaign_id: Filter to ad groups in this campaign. + after: Cursor to fetch the page after (from page_info.end_cursor). + + before: Cursor to fetch the page before (from page_info.start_cursor). + + created_after: Only return ad groups created after this timestamp. + + created_before: Only return ad groups created before this timestamp. + direction: The sort direction. Defaults to desc. - order: The field to sort by. Defaults to created_at. + first: The number of ad groups to return. + + last: The number of ad groups to return from the end of the range. + + order: The field to sort by. Defaults to created_at. Stat columns (spend, impressions, + …) rank over the stats_from/stats_to window across the whole list, not just the + current page. + + query: Filter ad groups by a title or ID substring. + + stats_from: Start of the stats window. Defaults to all-time. + + stats_to: End of the stats window. Defaults to now. status: Filter to a status (active, paused, in_review, rejected). @@ -435,8 +496,9 @@ def list( timeout: Override the client-level default timeout for this request, in seconds """ - return self._get( + return self._get_api_list( "/ad_groups", + page=SyncCursorPage[AdGroup], options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, @@ -446,14 +508,23 @@ def list( { "account_id": account_id, "ad_campaign_id": ad_campaign_id, + "after": after, + "before": before, + "created_after": created_after, + "created_before": created_before, "direction": direction, + "first": first, + "last": last, "order": order, + "query": query, + "stats_from": stats_from, + "stats_to": stats_to, "status": status, }, ad_group_list_params.AdGroupListParams, ), ), - cast_to=AdGroupListResponse, + model=AdGroup, ) def delete( @@ -743,6 +814,8 @@ async def retrieve( self, id: str, *, + stats_from: str | Omit = omit, + stats_to: 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, @@ -754,6 +827,10 @@ async def retrieve( Retrieves a single ad group. Args: + stats_from: Start of the stats window. + + stats_to: End of the stats window. + extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -767,7 +844,17 @@ async def retrieve( 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 + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=await async_maybe_transform( + { + "stats_from": stats_from, + "stats_to": stats_to, + }, + ad_group_retrieve_params.AdGroupRetrieveParams, + ), ), cast_to=AdGroup, ) @@ -929,13 +1016,38 @@ async def update( cast_to=AdGroup, ) - async def list( + def list( self, *, account_id: str | Omit = omit, ad_campaign_id: str | Omit = omit, + after: str | Omit = omit, + before: str | Omit = omit, + created_after: str | Omit = omit, + created_before: str | Omit = omit, direction: Literal["asc", "desc"] | Omit = omit, - order: Literal["created_at", "updated_at"] | Omit = omit, + first: int | Omit = omit, + last: int | Omit = omit, + order: Literal[ + "created_at", + "updated_at", + "spend", + "impressions", + "clicks", + "reach", + "unique_clicks", + "results", + "click_through_rate", + "cost_per_click", + "cost_per_mille", + "cost_per_result", + "frequency", + "return_on_ad_spend", + ] + | Omit = omit, + query: str | Omit = omit, + stats_from: str | Omit = omit, + stats_to: str | Omit = omit, status: 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. @@ -943,7 +1055,7 @@ async def list( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> AdGroupListResponse: + ) -> AsyncPaginator[AdGroup, AsyncCursorPage[AdGroup]]: """ Lists ad groups for the account, newest first. @@ -952,9 +1064,29 @@ async def list( ad_campaign_id: Filter to ad groups in this campaign. + after: Cursor to fetch the page after (from page_info.end_cursor). + + before: Cursor to fetch the page before (from page_info.start_cursor). + + created_after: Only return ad groups created after this timestamp. + + created_before: Only return ad groups created before this timestamp. + direction: The sort direction. Defaults to desc. - order: The field to sort by. Defaults to created_at. + first: The number of ad groups to return. + + last: The number of ad groups to return from the end of the range. + + order: The field to sort by. Defaults to created_at. Stat columns (spend, impressions, + …) rank over the stats_from/stats_to window across the whole list, not just the + current page. + + query: Filter ad groups by a title or ID substring. + + stats_from: Start of the stats window. Defaults to all-time. + + stats_to: End of the stats window. Defaults to now. status: Filter to a status (active, paused, in_review, rejected). @@ -966,25 +1098,35 @@ async def list( timeout: Override the client-level default timeout for this request, in seconds """ - return await self._get( + return self._get_api_list( "/ad_groups", + page=AsyncCursorPage[AdGroup], options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout, - query=await async_maybe_transform( + query=maybe_transform( { "account_id": account_id, "ad_campaign_id": ad_campaign_id, + "after": after, + "before": before, + "created_after": created_after, + "created_before": created_before, "direction": direction, + "first": first, + "last": last, "order": order, + "query": query, + "stats_from": stats_from, + "stats_to": stats_to, "status": status, }, ad_group_list_params.AdGroupListParams, ), ), - cast_to=AdGroupListResponse, + model=AdGroup, ) async def delete( diff --git a/src/whop_sdk/resources/ads.py b/src/whop_sdk/resources/ads.py index 94df18c6..1a938f28 100644 --- a/src/whop_sdk/resources/ads.py +++ b/src/whop_sdk/resources/ads.py @@ -356,7 +356,23 @@ def list( direction: Literal["asc", "desc"] | Omit = omit, first: int | Omit = omit, last: int | Omit = omit, - order: Literal["created_at", "updated_at"] | Omit = omit, + order: Literal[ + "created_at", + "updated_at", + "spend", + "impressions", + "clicks", + "reach", + "unique_clicks", + "results", + "click_through_rate", + "cost_per_click", + "cost_per_mille", + "cost_per_result", + "frequency", + "return_on_ad_spend", + ] + | Omit = omit, query: str | Omit = omit, stats_from: str | Omit = omit, stats_to: str | Omit = omit, @@ -392,7 +408,9 @@ def list( last: The number of ads to return from the end of the range. - order: The field to sort by. Defaults to created_at. + order: The field to sort by. Defaults to created_at. Stat columns (spend, impressions, + …) rank over the stats_from/stats_to window across the whole list, not just the + current page. query: Filter ads by a title or ID substring. @@ -873,7 +891,23 @@ def list( direction: Literal["asc", "desc"] | Omit = omit, first: int | Omit = omit, last: int | Omit = omit, - order: Literal["created_at", "updated_at"] | Omit = omit, + order: Literal[ + "created_at", + "updated_at", + "spend", + "impressions", + "clicks", + "reach", + "unique_clicks", + "results", + "click_through_rate", + "cost_per_click", + "cost_per_mille", + "cost_per_result", + "frequency", + "return_on_ad_spend", + ] + | Omit = omit, query: str | Omit = omit, stats_from: str | Omit = omit, stats_to: str | Omit = omit, @@ -909,7 +943,9 @@ def list( last: The number of ads to return from the end of the range. - order: The field to sort by. Defaults to created_at. + order: The field to sort by. Defaults to created_at. Stat columns (spend, impressions, + …) rank over the stats_from/stats_to window across the whole list, not just the + current page. query: Filter ads by a title or ID substring. diff --git a/src/whop_sdk/types/__init__.py b/src/whop_sdk/types/__init__.py index 7b3ede0a..c9bcbf9b 100644 --- a/src/whop_sdk/types/__init__.py +++ b/src/whop_sdk/types/__init__.py @@ -223,7 +223,6 @@ 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 .audience_create_params import AudienceCreateParams as AudienceCreateParams from .bounty_create_response import BountyCreateResponse as BountyCreateResponse @@ -267,6 +266,7 @@ from .webhook_create_response import WebhookCreateResponse as WebhookCreateResponse from .webhook_delete_response import WebhookDeleteResponse as WebhookDeleteResponse from .ad_group_delete_response import AdGroupDeleteResponse as AdGroupDeleteResponse +from .ad_group_retrieve_params import AdGroupRetrieveParams as AdGroupRetrieveParams from .audience_delete_response import AudienceDeleteResponse as AudienceDeleteResponse from .bounty_retrieve_response import BountyRetrieveResponse as BountyRetrieveResponse from .chat_channel_list_params import ChatChannelListParams as ChatChannelListParams diff --git a/src/whop_sdk/types/ad.py b/src/whop_sdk/types/ad.py index 3b80d2bc..0722be96 100644 --- a/src/whop_sdk/types/ad.py +++ b/src/whop_sdk/types/ad.py @@ -5,7 +5,40 @@ from .._models import BaseModel -__all__ = ["Ad", "Issue"] +__all__ = ["Ad", "AdCampaign", "AdGroup", "Creative", "Issue"] + + +class AdCampaign(BaseModel): + """The ad campaign this ad belongs to, an object with an id.""" + + id: str + """The referenced entity's id.""" + + +class AdGroup(BaseModel): + """The ad group this ad belongs to, an object with an id.""" + + id: str + """The referenced entity's id.""" + + +class Creative(BaseModel): + """The creatives used by this ad. + + The original/uncropped asset has a null format; square, vertical, and horizontal entries are its per-placement crops. + """ + + id: str + """The creative attachment's file id.""" + + format: Optional[Literal["square", "vertical", "horizontal"]] = None + """The placement crop this asset covers, or null for the original/uncropped asset.""" + + media_type: Optional[str] = None + """The kind of asset, image or video.""" + + url: Optional[str] = None + """CDN url of the asset.""" class Issue(BaseModel): @@ -28,10 +61,10 @@ class Ad(BaseModel): id: str """Unique identifier for the ad.""" - ad_campaign: object + ad_campaign: AdCampaign """The ad campaign this ad belongs to, an object with an id.""" - ad_group: object + ad_group: AdGroup """The ad group this ad belongs to, an object with an id.""" added_to_carts: float @@ -122,6 +155,12 @@ class Ad(BaseModel): none are attributed. """ + cost_per_result: Optional[float] = None + """ + Spend divided by Whop pixel-attributed results; null when nothing + Whop-attributable is being optimized for. + """ + cost_per_schedule: Optional[float] = None """ Spend divided by attributed schedule events; null when schedules are not the @@ -143,7 +182,7 @@ class Ad(BaseModel): created_at: str """When the ad was created, as an ISO 8601 timestamp.""" - creatives: List[object] + creatives: List[Creative] custom_conversions: float """ @@ -203,6 +242,31 @@ class Ad(BaseModel): reach: float """The number of unique people who saw this.""" + result_event: Optional[ + Literal[ + "purchase", + "lead", + "schedule", + "submit_application", + "contact", + "complete_registration", + "view_content", + "add_to_cart", + "custom", + ] + ] = None + """ + The Whop pixel conversion event whose attributed count represents results — the + optimization goal, or the highest-volume attributed event for campaigns that + budget per ad group. Null when the goal isn't a Whop-attributed event. + """ + + result_event_name: Optional[str] = None + """ + The merchant-defined event name when result_event is custom; null for the + standard events. + """ + return_on_ad_spend: float """Purchase value divided by spend; 0 when there is no spend.""" diff --git a/src/whop_sdk/types/ad_campaign.py b/src/whop_sdk/types/ad_campaign.py index 1547da81..f04c1c84 100644 --- a/src/whop_sdk/types/ad_campaign.py +++ b/src/whop_sdk/types/ad_campaign.py @@ -160,6 +160,31 @@ class AdCampaign(BaseModel): reach: float """The number of unique people who saw this.""" + result_event: Optional[ + Literal[ + "purchase", + "lead", + "schedule", + "submit_application", + "contact", + "complete_registration", + "view_content", + "add_to_cart", + "custom", + ] + ] = None + """ + The Whop pixel conversion event whose attributed count represents results — the + optimization goal, or the highest-volume attributed event for campaigns that + budget per ad group. Null when the goal isn't a Whop-attributed event. + """ + + result_event_name: Optional[str] = None + """ + The merchant-defined event name when result_event is custom; null for the + standard events. + """ + return_on_ad_spend: float """Purchase value divided by spend; 0 when there is no spend.""" @@ -174,7 +199,19 @@ class AdCampaign(BaseModel): spend_currency: Optional[str] = None """The ISO 4217 currency code of all monetary metrics.""" - status: Literal["draft", "active", "paused", "payment_failed"] + status: Literal[ + "active", + "paused", + "inactive", + "stale", + "pending_refund", + "payment_failed", + "draft", + "in_review", + "flagged", + "importing", + "imported", + ] """The lifecycle status of the ad campaign.""" submitted_applications: float diff --git a/src/whop_sdk/types/ad_campaign_list_params.py b/src/whop_sdk/types/ad_campaign_list_params.py index 2c0ad3c3..c46323a4 100644 --- a/src/whop_sdk/types/ad_campaign_list_params.py +++ b/src/whop_sdk/types/ad_campaign_list_params.py @@ -35,8 +35,27 @@ class AdCampaignListParams(TypedDict, total=False): last: int """The number of campaigns to return from the end of the range.""" - order: Literal["created_at", "updated_at"] - """The field to sort by. Defaults to created_at.""" + order: Literal[ + "created_at", + "updated_at", + "spend", + "impressions", + "clicks", + "reach", + "unique_clicks", + "results", + "click_through_rate", + "cost_per_click", + "cost_per_mille", + "cost_per_result", + "frequency", + "return_on_ad_spend", + ] + """The field to sort by. + + Defaults to created_at. Stat columns (spend, impressions, …) rank over the + stats_from/stats_to window across the whole list, not just the current page. + """ query: str """Filter campaigns by a title or ID substring.""" diff --git a/src/whop_sdk/types/ad_group.py b/src/whop_sdk/types/ad_group.py index c25a0c51..18790628 100644 --- a/src/whop_sdk/types/ad_group.py +++ b/src/whop_sdk/types/ad_group.py @@ -5,7 +5,14 @@ from .._models import BaseModel -__all__ = ["AdGroup", "Issue"] +__all__ = ["AdGroup", "AdCampaign", "Issue"] + + +class AdCampaign(BaseModel): + """The ad campaign this ad group belongs to, an object with an id.""" + + id: str + """The referenced entity's id.""" class Issue(BaseModel): @@ -28,7 +35,7 @@ class AdGroup(BaseModel): id: str """Unique identifier for the ad group.""" - ad_campaign: object + ad_campaign: AdCampaign """The ad campaign this ad group belongs to, an object with an id.""" added_to_carts: float @@ -138,6 +145,12 @@ class AdGroup(BaseModel): none are attributed. """ + cost_per_result: Optional[float] = None + """ + Spend divided by Whop pixel-attributed results; null when nothing + Whop-attributable is being optimized for. + """ + cost_per_schedule: Optional[float] = None """ Spend divided by attributed schedule events; null when schedules are not the @@ -224,6 +237,31 @@ class AdGroup(BaseModel): US-CA), cities, zips. """ + result_event: Optional[ + Literal[ + "purchase", + "lead", + "schedule", + "submit_application", + "contact", + "complete_registration", + "view_content", + "add_to_cart", + "custom", + ] + ] = None + """ + The Whop pixel conversion event whose attributed count represents results — the + optimization goal, or the highest-volume attributed event for campaigns that + budget per ad group. Null when the goal isn't a Whop-attributed event. + """ + + result_event_name: Optional[str] = None + """ + The merchant-defined event name when result_event is custom; null for the + standard events. + """ + return_on_ad_spend: float """Purchase value divided by spend; 0 when there is no spend.""" diff --git a/src/whop_sdk/types/ad_group_list_params.py b/src/whop_sdk/types/ad_group_list_params.py index 2478acfa..856d60d4 100644 --- a/src/whop_sdk/types/ad_group_list_params.py +++ b/src/whop_sdk/types/ad_group_list_params.py @@ -14,11 +14,57 @@ class AdGroupListParams(TypedDict, total=False): ad_campaign_id: str """Filter to ad groups in this campaign.""" + after: str + """Cursor to fetch the page after (from page_info.end_cursor).""" + + before: str + """Cursor to fetch the page before (from page_info.start_cursor).""" + + created_after: str + """Only return ad groups created after this timestamp.""" + + created_before: str + """Only return ad groups created before this timestamp.""" + direction: Literal["asc", "desc"] """The sort direction. Defaults to desc.""" - order: Literal["created_at", "updated_at"] - """The field to sort by. Defaults to created_at.""" + first: int + """The number of ad groups to return.""" + + last: int + """The number of ad groups to return from the end of the range.""" + + order: Literal[ + "created_at", + "updated_at", + "spend", + "impressions", + "clicks", + "reach", + "unique_clicks", + "results", + "click_through_rate", + "cost_per_click", + "cost_per_mille", + "cost_per_result", + "frequency", + "return_on_ad_spend", + ] + """The field to sort by. + + Defaults to created_at. Stat columns (spend, impressions, …) rank over the + stats_from/stats_to window across the whole list, not just the current page. + """ + + query: str + """Filter ad groups by a title or ID substring.""" + + stats_from: str + """Start of the stats window. Defaults to all-time.""" + + stats_to: str + """End of the stats window. Defaults to now.""" status: str """Filter to a status (active, paused, in_review, rejected).""" diff --git a/src/whop_sdk/types/ad_group_list_response.py b/src/whop_sdk/types/ad_group_list_response.py deleted file mode 100644 index dc42b3d4..00000000 --- a/src/whop_sdk/types/ad_group_list_response.py +++ /dev/null @@ -1,14 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from typing import List, Optional - -from .._models import BaseModel -from .ad_group import AdGroup - -__all__ = ["AdGroupListResponse"] - - -class AdGroupListResponse(BaseModel): - data: Optional[List[AdGroup]] = None - - page_info: Optional[object] = None diff --git a/src/whop_sdk/types/ad_group_retrieve_params.py b/src/whop_sdk/types/ad_group_retrieve_params.py new file mode 100644 index 00000000..f3982cec --- /dev/null +++ b/src/whop_sdk/types/ad_group_retrieve_params.py @@ -0,0 +1,15 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import TypedDict + +__all__ = ["AdGroupRetrieveParams"] + + +class AdGroupRetrieveParams(TypedDict, total=False): + stats_from: str + """Start of the stats window.""" + + stats_to: str + """End of the stats window.""" diff --git a/src/whop_sdk/types/ad_list_params.py b/src/whop_sdk/types/ad_list_params.py index 905944a3..dcc24878 100644 --- a/src/whop_sdk/types/ad_list_params.py +++ b/src/whop_sdk/types/ad_list_params.py @@ -41,8 +41,27 @@ class AdListParams(TypedDict, total=False): last: int """The number of ads to return from the end of the range.""" - order: Literal["created_at", "updated_at"] - """The field to sort by. Defaults to created_at.""" + order: Literal[ + "created_at", + "updated_at", + "spend", + "impressions", + "clicks", + "reach", + "unique_clicks", + "results", + "click_through_rate", + "cost_per_click", + "cost_per_mille", + "cost_per_result", + "frequency", + "return_on_ad_spend", + ] + """The field to sort by. + + Defaults to created_at. Stat columns (spend, impressions, …) rank over the + stats_from/stats_to window across the whole list, not just the current page. + """ query: str """Filter ads by a title or ID substring.""" diff --git a/tests/api_resources/test_ad_groups.py b/tests/api_resources/test_ad_groups.py index acad36b0..5d6965f6 100644 --- a/tests/api_resources/test_ad_groups.py +++ b/tests/api_resources/test_ad_groups.py @@ -11,9 +11,9 @@ from tests.utils import assert_matches_type from whop_sdk.types import ( AdGroup, - AdGroupListResponse, AdGroupDeleteResponse, ) +from whop_sdk.pagination import SyncCursorPage, AsyncCursorPage base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") @@ -88,7 +88,17 @@ def test_streaming_response_create(self, client: Whop) -> None: @parametrize def test_method_retrieve(self, client: Whop) -> None: ad_group = client.ad_groups.retrieve( - "id", + id="id", + ) + assert_matches_type(AdGroup, ad_group, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_retrieve_with_all_params(self, client: Whop) -> None: + ad_group = client.ad_groups.retrieve( + id="id", + stats_from="stats_from", + stats_to="stats_to", ) assert_matches_type(AdGroup, ad_group, path=["response"]) @@ -96,7 +106,7 @@ def test_method_retrieve(self, client: Whop) -> None: @parametrize def test_raw_response_retrieve(self, client: Whop) -> None: response = client.ad_groups.with_raw_response.retrieve( - "id", + id="id", ) assert response.is_closed is True @@ -108,7 +118,7 @@ def test_raw_response_retrieve(self, client: Whop) -> None: @parametrize def test_streaming_response_retrieve(self, client: Whop) -> None: with client.ad_groups.with_streaming_response.retrieve( - "id", + id="id", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -123,7 +133,7 @@ def test_streaming_response_retrieve(self, client: Whop) -> None: 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( - "", + id="", ) @pytest.mark.skip(reason="Mock server tests are disabled") @@ -200,7 +210,7 @@ def test_path_params_update(self, client: Whop) -> None: @parametrize def test_method_list(self, client: Whop) -> None: ad_group = client.ad_groups.list() - assert_matches_type(AdGroupListResponse, ad_group, path=["response"]) + assert_matches_type(SyncCursorPage[AdGroup], ad_group, path=["response"]) @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize @@ -208,11 +218,20 @@ def test_method_list_with_all_params(self, client: Whop) -> None: ad_group = client.ad_groups.list( account_id="account_id", ad_campaign_id="ad_campaign_id", + after="after", + before="before", + created_after="created_after", + created_before="created_before", direction="asc", + first=100, + last=100, order="created_at", + query="query", + stats_from="stats_from", + stats_to="stats_to", status="status", ) - assert_matches_type(AdGroupListResponse, ad_group, path=["response"]) + assert_matches_type(SyncCursorPage[AdGroup], ad_group, path=["response"]) @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize @@ -222,7 +241,7 @@ def test_raw_response_list(self, client: Whop) -> None: assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" ad_group = response.parse() - assert_matches_type(AdGroupListResponse, ad_group, path=["response"]) + assert_matches_type(SyncCursorPage[AdGroup], ad_group, path=["response"]) @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize @@ -232,7 +251,7 @@ def test_streaming_response_list(self, client: Whop) -> None: assert response.http_request.headers.get("X-Stainless-Lang") == "python" ad_group = response.parse() - assert_matches_type(AdGroupListResponse, ad_group, path=["response"]) + assert_matches_type(SyncCursorPage[AdGroup], ad_group, path=["response"]) assert cast(Any, response.is_closed) is True @@ -435,7 +454,17 @@ async def test_streaming_response_create(self, async_client: AsyncWhop) -> None: @parametrize async def test_method_retrieve(self, async_client: AsyncWhop) -> None: ad_group = await async_client.ad_groups.retrieve( - "id", + id="id", + ) + assert_matches_type(AdGroup, ad_group, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_retrieve_with_all_params(self, async_client: AsyncWhop) -> None: + ad_group = await async_client.ad_groups.retrieve( + id="id", + stats_from="stats_from", + stats_to="stats_to", ) assert_matches_type(AdGroup, ad_group, path=["response"]) @@ -443,7 +472,7 @@ async def test_method_retrieve(self, async_client: AsyncWhop) -> None: @parametrize async def test_raw_response_retrieve(self, async_client: AsyncWhop) -> None: response = await async_client.ad_groups.with_raw_response.retrieve( - "id", + id="id", ) assert response.is_closed is True @@ -455,7 +484,7 @@ async def test_raw_response_retrieve(self, async_client: AsyncWhop) -> None: @parametrize async def test_streaming_response_retrieve(self, async_client: AsyncWhop) -> None: async with async_client.ad_groups.with_streaming_response.retrieve( - "id", + id="id", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -470,7 +499,7 @@ async def test_streaming_response_retrieve(self, async_client: AsyncWhop) -> Non 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( - "", + id="", ) @pytest.mark.skip(reason="Mock server tests are disabled") @@ -547,7 +576,7 @@ async def test_path_params_update(self, async_client: AsyncWhop) -> None: @parametrize async def test_method_list(self, async_client: AsyncWhop) -> None: ad_group = await async_client.ad_groups.list() - assert_matches_type(AdGroupListResponse, ad_group, path=["response"]) + assert_matches_type(AsyncCursorPage[AdGroup], ad_group, path=["response"]) @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize @@ -555,11 +584,20 @@ async def test_method_list_with_all_params(self, async_client: AsyncWhop) -> Non ad_group = await async_client.ad_groups.list( account_id="account_id", ad_campaign_id="ad_campaign_id", + after="after", + before="before", + created_after="created_after", + created_before="created_before", direction="asc", + first=100, + last=100, order="created_at", + query="query", + stats_from="stats_from", + stats_to="stats_to", status="status", ) - assert_matches_type(AdGroupListResponse, ad_group, path=["response"]) + assert_matches_type(AsyncCursorPage[AdGroup], ad_group, path=["response"]) @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize @@ -569,7 +607,7 @@ async def test_raw_response_list(self, async_client: AsyncWhop) -> None: assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" ad_group = await response.parse() - assert_matches_type(AdGroupListResponse, ad_group, path=["response"]) + assert_matches_type(AsyncCursorPage[AdGroup], ad_group, path=["response"]) @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize @@ -579,7 +617,7 @@ async def test_streaming_response_list(self, async_client: AsyncWhop) -> None: assert response.http_request.headers.get("X-Stainless-Lang") == "python" ad_group = await response.parse() - assert_matches_type(AdGroupListResponse, ad_group, path=["response"]) + assert_matches_type(AsyncCursorPage[AdGroup], ad_group, path=["response"]) assert cast(Any, response.is_closed) is True From 0d6b9d70f43a8c2e2b43617b503b410c0b5f2314 Mon Sep 17 00:00:00 2001 From: stlc-bot Date: Thu, 2 Jul 2026 08:19:22 +0000 Subject: [PATCH 106/109] feat(backend): backfill and update referral caches Stainless-Generated-From: fc8f41a194f8413f2539de7356507f7bb6fa9ba0 --- .../referrals/businesses/businesses.py | 20 +++++++++++++++++-- .../types/referrals/business_list_params.py | 4 +++- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/src/whop_sdk/resources/referrals/businesses/businesses.py b/src/whop_sdk/resources/referrals/businesses/businesses.py index 1574fe03..daadc73a 100644 --- a/src/whop_sdk/resources/referrals/businesses/businesses.py +++ b/src/whop_sdk/resources/referrals/businesses/businesses.py @@ -100,7 +100,15 @@ def list( first: int | Omit = omit, has_earnings: bool | Omit = omit, last: int | Omit = omit, - order: Literal["created_at", "referral_started_at", "referral_expires_at", "payout_percentage"] | Omit = omit, + order: Literal[ + "created_at", + "referral_started_at", + "referral_expires_at", + "payout_percentage", + "volume_usd", + "earnings_usd", + ] + | Omit = omit, status: Literal["active", "removed"] | 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. @@ -291,7 +299,15 @@ def list( first: int | Omit = omit, has_earnings: bool | Omit = omit, last: int | Omit = omit, - order: Literal["created_at", "referral_started_at", "referral_expires_at", "payout_percentage"] | Omit = omit, + order: Literal[ + "created_at", + "referral_started_at", + "referral_expires_at", + "payout_percentage", + "volume_usd", + "earnings_usd", + ] + | Omit = omit, status: Literal["active", "removed"] | 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. diff --git a/src/whop_sdk/types/referrals/business_list_params.py b/src/whop_sdk/types/referrals/business_list_params.py index 814072c5..e471a3d3 100644 --- a/src/whop_sdk/types/referrals/business_list_params.py +++ b/src/whop_sdk/types/referrals/business_list_params.py @@ -29,7 +29,9 @@ class BusinessListParams(TypedDict, total=False): last: int """Number of business referrals to return from the end of the window.""" - order: Literal["created_at", "referral_started_at", "referral_expires_at", "payout_percentage"] + order: Literal[ + "created_at", "referral_started_at", "referral_expires_at", "payout_percentage", "volume_usd", "earnings_usd" + ] """The field to sort business referrals by.""" status: Literal["active", "removed"] From 7c0483fc5f7df5499011e5057af54b43732751a6 Mon Sep 17 00:00:00 2001 From: stlc-bot Date: Thu, 2 Jul 2026 08:38:27 +0000 Subject: [PATCH 107/109] feat(checkout): per-embed shipping address collection (embedded + express) Stainless-Generated-From: a899bb7b7900cd3d9378d2d34a600c8f55c65d3e --- src/whop_sdk/types/payment_list_response.py | 35 +++++++++++++++++++++ src/whop_sdk/types/shared/payment.py | 35 +++++++++++++++++++++ 2 files changed, 70 insertions(+) diff --git a/src/whop_sdk/types/payment_list_response.py b/src/whop_sdk/types/payment_list_response.py index edddf651..7199461b 100644 --- a/src/whop_sdk/types/payment_list_response.py +++ b/src/whop_sdk/types/payment_list_response.py @@ -26,6 +26,7 @@ "Plan", "Product", "PromoCode", + "ShippingAddress", "User", ] @@ -225,6 +226,34 @@ class PromoCode(BaseModel): """The type (% or flat amount) of the promo.""" +class ShippingAddress(BaseModel): + """The shipping address provided by the customer for physical goods. + + Null if no shipping address was collected. + """ + + city: Optional[str] = None + """The city of the address.""" + + country: Optional[str] = None + """The country of the address.""" + + line1: Optional[str] = None + """The line 1 of the address.""" + + line2: Optional[str] = None + """The line 2 of the address.""" + + name: Optional[str] = None + """The name of the customer.""" + + postal_code: Optional[str] = None + """The postal code of the address.""" + + state: Optional[str] = None + """The state of the address.""" + + class User(BaseModel): """The user that made this payment.""" @@ -366,6 +395,12 @@ class PaymentListResponse(BaseModel): settlement_currency: Currency """The three-letter ISO currency code for this payment (e.g., 'usd', 'eur').""" + shipping_address: Optional[ShippingAddress] = None + """The shipping address provided by the customer for physical goods. + + Null if no shipping address was collected. + """ + status: Optional[ReceiptStatus] = None """The status of a receipt""" diff --git a/src/whop_sdk/types/shared/payment.py b/src/whop_sdk/types/shared/payment.py index c75635a5..72cd5b3c 100644 --- a/src/whop_sdk/types/shared/payment.py +++ b/src/whop_sdk/types/shared/payment.py @@ -36,6 +36,7 @@ "Product", "PromoCode", "Resolution", + "ShippingAddress", "User", ] @@ -360,6 +361,34 @@ class Resolution(BaseModel): """ +class ShippingAddress(BaseModel): + """The shipping address provided by the customer for physical goods. + + Null if no shipping address was collected. + """ + + city: Optional[str] = None + """The city of the address.""" + + country: Optional[str] = None + """The country of the address.""" + + line1: Optional[str] = None + """The line 1 of the address.""" + + line2: Optional[str] = None + """The line 2 of the address.""" + + name: Optional[str] = None + """The name of the customer.""" + + postal_code: Optional[str] = None + """The postal code of the address.""" + + state: Optional[str] = None + """The state of the address.""" + + class User(BaseModel): """The user that made this payment.""" @@ -549,6 +578,12 @@ class Payment(BaseModel): settlement_exchange_rate: Optional[float] = None """Deprecated. Always returns null.""" + shipping_address: Optional[ShippingAddress] = None + """The shipping address provided by the customer for physical goods. + + Null if no shipping address was collected. + """ + status: Optional[ReceiptStatus] = None """The status of a receipt""" From 8452acbf80da14e2c747ba47288db95cf5eb10ab Mon Sep 17 00:00:00 2001 From: stlc-bot Date: Thu, 2 Jul 2026 08:46:58 +0000 Subject: [PATCH 108/109] add email otp to enhanced session Stainless-Generated-From: afade4cf55d7e64696247caabe2abd3ac04376ef --- src/whop_sdk/types/authorized_user_create_params.py | 3 +++ tests/api_resources/test_authorized_users.py | 2 ++ 2 files changed, 5 insertions(+) diff --git a/src/whop_sdk/types/authorized_user_create_params.py b/src/whop_sdk/types/authorized_user_create_params.py index a412352b..853bb219 100644 --- a/src/whop_sdk/types/authorized_user_create_params.py +++ b/src/whop_sdk/types/authorized_user_create_params.py @@ -42,6 +42,9 @@ class Elevation(TypedDict, total=False): credential_id: Optional[str] """The WebAuthn credential ID (base64).""" + email_code: Optional[str] + """The 6-digit code emailed to the user.""" + signature: Optional[str] """The WebAuthn signature (base64).""" diff --git a/tests/api_resources/test_authorized_users.py b/tests/api_resources/test_authorized_users.py index 95bb59b6..96ad67ce 100644 --- a/tests/api_resources/test_authorized_users.py +++ b/tests/api_resources/test_authorized_users.py @@ -44,6 +44,7 @@ def test_method_create_with_all_params(self, client: Whop) -> None: "authenticator_data": "authenticator_data", "client_data_json": "client_data_json", "credential_id": "credential_id", + "email_code": "email_code", "signature": "signature", "totp_code": "totp_code", "use_finance_session": True, @@ -246,6 +247,7 @@ async def test_method_create_with_all_params(self, async_client: AsyncWhop) -> N "authenticator_data": "authenticator_data", "client_data_json": "client_data_json", "credential_id": "credential_id", + "email_code": "email_code", "signature": "signature", "totp_code": "totp_code", "use_finance_session": True, From d61026fa0e2ed055de1a509548dea1ec023d4b29 Mon Sep 17 00:00:00 2001 From: stlc-bot Date: Thu, 2 Jul 2026 09:11:07 +0000 Subject: [PATCH 109/109] Show visitor attribution in dashboard users Stainless-Generated-From: b5f19b1361e800ff449f235d532255eaf5ed5f42 --- .stats.yml | 2 +- api.md | 25 ++ src/whop_sdk/_client.py | 76 ++++ src/whop_sdk/resources/__init__.py | 28 ++ src/whop_sdk/resources/events.py | 221 +++++++++++ src/whop_sdk/resources/people.py | 370 ++++++++++++++++++ src/whop_sdk/types/__init__.py | 6 + src/whop_sdk/types/event_list_params.py | 30 ++ src/whop_sdk/types/event_list_response.py | 75 ++++ src/whop_sdk/types/person_list_params.py | 42 ++ src/whop_sdk/types/person_list_response.py | 67 ++++ src/whop_sdk/types/person_retrieve_params.py | 24 ++ .../types/person_retrieve_response.py | 65 +++ tests/api_resources/test_events.py | 116 ++++++ tests/api_resources/test_people.py | 216 ++++++++++ 15 files changed, 1362 insertions(+), 1 deletion(-) create mode 100644 src/whop_sdk/resources/events.py create mode 100644 src/whop_sdk/resources/people.py create mode 100644 src/whop_sdk/types/event_list_params.py create mode 100644 src/whop_sdk/types/event_list_response.py create mode 100644 src/whop_sdk/types/person_list_params.py create mode 100644 src/whop_sdk/types/person_list_response.py create mode 100644 src/whop_sdk/types/person_retrieve_params.py create mode 100644 src/whop_sdk/types/person_retrieve_response.py create mode 100644 tests/api_resources/test_events.py create mode 100644 tests/api_resources/test_people.py diff --git a/.stats.yml b/.stats.yml index 20b6c9e2..61c2bc47 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1 +1 @@ -configured_endpoints: 257 +configured_endpoints: 260 diff --git a/api.md b/api.md index d0a919a3..ef677de4 100644 --- a/api.md +++ b/api.md @@ -160,6 +160,31 @@ Methods: - client.audiences.list(\*\*params) -> SyncCursorPage[Audience] - client.audiences.delete(audience_id) -> AudienceDeleteResponse +# People + +Types: + +```python +from whop_sdk.types import PersonRetrieveResponse, PersonListResponse +``` + +Methods: + +- client.people.retrieve(person_id, \*\*params) -> PersonRetrieveResponse +- client.people.list(\*\*params) -> PersonListResponse + +# Events + +Types: + +```python +from whop_sdk.types import EventListResponse +``` + +Methods: + +- client.events.list(\*\*params) -> EventListResponse + # Companies Types: diff --git a/src/whop_sdk/_client.py b/src/whop_sdk/_client.py index 6f362873..8ff06c15 100644 --- a/src/whop_sdk/_client.py +++ b/src/whop_sdk/_client.py @@ -45,7 +45,9 @@ stats, swaps, users, + events, forums, + people, topups, courses, entries, @@ -115,7 +117,9 @@ from .resources.stats import StatsResource, AsyncStatsResource from .resources.swaps import SwapsResource, AsyncSwapsResource from .resources.users import UsersResource, AsyncUsersResource + from .resources.events import EventsResource, AsyncEventsResource from .resources.forums import ForumsResource, AsyncForumsResource + from .resources.people import PeopleResource, AsyncPeopleResource from .resources.topups import TopupsResource, AsyncTopupsResource from .resources.courses import CoursesResource, AsyncCoursesResource from .resources.entries import EntriesResource, AsyncEntriesResource @@ -311,6 +315,18 @@ def audiences(self) -> AudiencesResource: return AudiencesResource(self) + @cached_property + def people(self) -> PeopleResource: + from .resources.people import PeopleResource + + return PeopleResource(self) + + @cached_property + def events(self) -> EventsResource: + from .resources.events import EventsResource + + return EventsResource(self) + @cached_property def companies(self) -> CompaniesResource: """Companies""" @@ -985,6 +1001,18 @@ def audiences(self) -> AsyncAudiencesResource: return AsyncAudiencesResource(self) + @cached_property + def people(self) -> AsyncPeopleResource: + from .resources.people import AsyncPeopleResource + + return AsyncPeopleResource(self) + + @cached_property + def events(self) -> AsyncEventsResource: + from .resources.events import AsyncEventsResource + + return AsyncEventsResource(self) + @cached_property def companies(self) -> AsyncCompaniesResource: """Companies""" @@ -1579,6 +1607,18 @@ def audiences(self) -> audiences.AudiencesResourceWithRawResponse: return AudiencesResourceWithRawResponse(self._client.audiences) + @cached_property + def people(self) -> people.PeopleResourceWithRawResponse: + from .resources.people import PeopleResourceWithRawResponse + + return PeopleResourceWithRawResponse(self._client.people) + + @cached_property + def events(self) -> events.EventsResourceWithRawResponse: + from .resources.events import EventsResourceWithRawResponse + + return EventsResourceWithRawResponse(self._client.events) + @cached_property def companies(self) -> companies.CompaniesResourceWithRawResponse: """Companies""" @@ -2055,6 +2095,18 @@ def audiences(self) -> audiences.AsyncAudiencesResourceWithRawResponse: return AsyncAudiencesResourceWithRawResponse(self._client.audiences) + @cached_property + def people(self) -> people.AsyncPeopleResourceWithRawResponse: + from .resources.people import AsyncPeopleResourceWithRawResponse + + return AsyncPeopleResourceWithRawResponse(self._client.people) + + @cached_property + def events(self) -> events.AsyncEventsResourceWithRawResponse: + from .resources.events import AsyncEventsResourceWithRawResponse + + return AsyncEventsResourceWithRawResponse(self._client.events) + @cached_property def companies(self) -> companies.AsyncCompaniesResourceWithRawResponse: """Companies""" @@ -2533,6 +2585,18 @@ def audiences(self) -> audiences.AudiencesResourceWithStreamingResponse: return AudiencesResourceWithStreamingResponse(self._client.audiences) + @cached_property + def people(self) -> people.PeopleResourceWithStreamingResponse: + from .resources.people import PeopleResourceWithStreamingResponse + + return PeopleResourceWithStreamingResponse(self._client.people) + + @cached_property + def events(self) -> events.EventsResourceWithStreamingResponse: + from .resources.events import EventsResourceWithStreamingResponse + + return EventsResourceWithStreamingResponse(self._client.events) + @cached_property def companies(self) -> companies.CompaniesResourceWithStreamingResponse: """Companies""" @@ -3011,6 +3075,18 @@ def audiences(self) -> audiences.AsyncAudiencesResourceWithStreamingResponse: return AsyncAudiencesResourceWithStreamingResponse(self._client.audiences) + @cached_property + def people(self) -> people.AsyncPeopleResourceWithStreamingResponse: + from .resources.people import AsyncPeopleResourceWithStreamingResponse + + return AsyncPeopleResourceWithStreamingResponse(self._client.people) + + @cached_property + def events(self) -> events.AsyncEventsResourceWithStreamingResponse: + from .resources.events import AsyncEventsResourceWithStreamingResponse + + return AsyncEventsResourceWithStreamingResponse(self._client.events) + @cached_property def companies(self) -> companies.AsyncCompaniesResourceWithStreamingResponse: """Companies""" diff --git a/src/whop_sdk/resources/__init__.py b/src/whop_sdk/resources/__init__.py index e3013e4e..6aa67330 100644 --- a/src/whop_sdk/resources/__init__.py +++ b/src/whop_sdk/resources/__init__.py @@ -72,6 +72,14 @@ UsersResourceWithStreamingResponse, AsyncUsersResourceWithStreamingResponse, ) +from .events import ( + EventsResource, + AsyncEventsResource, + EventsResourceWithRawResponse, + AsyncEventsResourceWithRawResponse, + EventsResourceWithStreamingResponse, + AsyncEventsResourceWithStreamingResponse, +) from .forums import ( ForumsResource, AsyncForumsResource, @@ -80,6 +88,14 @@ ForumsResourceWithStreamingResponse, AsyncForumsResourceWithStreamingResponse, ) +from .people import ( + PeopleResource, + AsyncPeopleResource, + PeopleResourceWithRawResponse, + AsyncPeopleResourceWithRawResponse, + PeopleResourceWithStreamingResponse, + AsyncPeopleResourceWithStreamingResponse, +) from .topups import ( TopupsResource, AsyncTopupsResource, @@ -590,6 +606,18 @@ "AsyncAudiencesResourceWithRawResponse", "AudiencesResourceWithStreamingResponse", "AsyncAudiencesResourceWithStreamingResponse", + "PeopleResource", + "AsyncPeopleResource", + "PeopleResourceWithRawResponse", + "AsyncPeopleResourceWithRawResponse", + "PeopleResourceWithStreamingResponse", + "AsyncPeopleResourceWithStreamingResponse", + "EventsResource", + "AsyncEventsResource", + "EventsResourceWithRawResponse", + "AsyncEventsResourceWithRawResponse", + "EventsResourceWithStreamingResponse", + "AsyncEventsResourceWithStreamingResponse", "CompaniesResource", "AsyncCompaniesResource", "CompaniesResourceWithRawResponse", diff --git a/src/whop_sdk/resources/events.py b/src/whop_sdk/resources/events.py new file mode 100644 index 00000000..c7c061b8 --- /dev/null +++ b/src/whop_sdk/resources/events.py @@ -0,0 +1,221 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import httpx + +from ..types import event_list_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.event_list_response import EventListResponse + +__all__ = ["EventsResource", "AsyncEventsResource"] + + +class EventsResource(SyncAPIResource): + @cached_property + def with_raw_response(self) -> EventsResourceWithRawResponse: + """ + 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 EventsResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> EventsResourceWithStreamingResponse: + """ + 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 EventsResourceWithStreamingResponse(self) + + def list( + self, + *, + person_id: str, + account_id: str | Omit = omit, + first: int | Omit = omit, + from_: int | Omit = omit, + to: int | 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, + ) -> EventListResponse: + """Lists pixel events for a person, most recent first. + + Events are shaped like the + POST /conversions intake: attribution in context, identity in user. + + Args: + person_id: The ID of the person. + + account_id: The ID of the account, which will look like biz\\__******\\********. Optional for + account API keys; required for credentials that can access multiple accounts. + + first: The number of events to return. + + from_: Start of the time range as a Unix timestamp. + + to: End of the time range as a Unix timestamp. Defaults to now. + + 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( + "/events", + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "person_id": person_id, + "account_id": account_id, + "first": first, + "from_": from_, + "to": to, + }, + event_list_params.EventListParams, + ), + ), + cast_to=EventListResponse, + ) + + +class AsyncEventsResource(AsyncAPIResource): + @cached_property + def with_raw_response(self) -> AsyncEventsResourceWithRawResponse: + """ + 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 AsyncEventsResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncEventsResourceWithStreamingResponse: + """ + 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 AsyncEventsResourceWithStreamingResponse(self) + + async def list( + self, + *, + person_id: str, + account_id: str | Omit = omit, + first: int | Omit = omit, + from_: int | Omit = omit, + to: int | 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, + ) -> EventListResponse: + """Lists pixel events for a person, most recent first. + + Events are shaped like the + POST /conversions intake: attribution in context, identity in user. + + Args: + person_id: The ID of the person. + + account_id: The ID of the account, which will look like biz\\__******\\********. Optional for + account API keys; required for credentials that can access multiple accounts. + + first: The number of events to return. + + from_: Start of the time range as a Unix timestamp. + + to: End of the time range as a Unix timestamp. Defaults to now. + + 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( + "/events", + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=await async_maybe_transform( + { + "person_id": person_id, + "account_id": account_id, + "first": first, + "from_": from_, + "to": to, + }, + event_list_params.EventListParams, + ), + ), + cast_to=EventListResponse, + ) + + +class EventsResourceWithRawResponse: + def __init__(self, events: EventsResource) -> None: + self._events = events + + self.list = to_raw_response_wrapper( + events.list, + ) + + +class AsyncEventsResourceWithRawResponse: + def __init__(self, events: AsyncEventsResource) -> None: + self._events = events + + self.list = async_to_raw_response_wrapper( + events.list, + ) + + +class EventsResourceWithStreamingResponse: + def __init__(self, events: EventsResource) -> None: + self._events = events + + self.list = to_streamed_response_wrapper( + events.list, + ) + + +class AsyncEventsResourceWithStreamingResponse: + def __init__(self, events: AsyncEventsResource) -> None: + self._events = events + + self.list = async_to_streamed_response_wrapper( + events.list, + ) diff --git a/src/whop_sdk/resources/people.py b/src/whop_sdk/resources/people.py new file mode 100644 index 00000000..9b3236da --- /dev/null +++ b/src/whop_sdk/resources/people.py @@ -0,0 +1,370 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Literal + +import httpx + +from ..types import person_list_params, person_retrieve_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 .._base_client import make_request_options +from ..types.person_list_response import PersonListResponse +from ..types.person_retrieve_response import PersonRetrieveResponse + +__all__ = ["PeopleResource", "AsyncPeopleResource"] + + +class PeopleResource(SyncAPIResource): + @cached_property + def with_raw_response(self) -> PeopleResourceWithRawResponse: + """ + 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 PeopleResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> PeopleResourceWithStreamingResponse: + """ + 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 PeopleResourceWithStreamingResponse(self) + + def retrieve( + self, + person_id: str, + *, + account_id: str | Omit = omit, + from_: int | Omit = omit, + to: int | 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, + ) -> PersonRetrieveResponse: + """ + Retrieves one person for an account, aggregated from pixel events. + + Args: + account_id: The ID of the account, which will look like biz\\__******\\********. Optional for + account API keys; required for credentials that can access multiple accounts. + + from_: Start of the time range as a Unix timestamp. + + to: End of the time range as a Unix timestamp. Defaults to now. + + 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 person_id: + raise ValueError(f"Expected a non-empty value for `person_id` but received {person_id!r}") + return self._get( + path_template("/people/{person_id}", person_id=person_id), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "account_id": account_id, + "from_": from_, + "to": to, + }, + person_retrieve_params.PersonRetrieveParams, + ), + ), + cast_to=PersonRetrieveResponse, + ) + + def list( + self, + *, + account_id: str | Omit = omit, + direction: Literal["asc", "desc"] | Omit = omit, + filters: str | Omit = omit, + first: int | Omit = omit, + from_: int | Omit = omit, + offset: int | Omit = omit, + sort: str | Omit = omit, + to: int | 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, + ) -> PersonListResponse: + """ + Lists the people (visitors and customers) of an account, aggregated from pixel + events. The account is inferred from an account API key; other credentials must + pass account_id. + + Args: + account_id: The ID of the account, which will look like biz\\__******\\********. Optional for + account API keys; required for credentials that can access multiple accounts. + + direction: Sort direction. Defaults to desc. + + filters: A JSON-encoded array of filters, each with field, operator, and value keys. + + first: The number of people to return (default 100, max 101). + + from_: Start of the time range as a Unix timestamp. Defaults to 366 days before `to`. + + offset: The number of people to skip, for offset pagination. + + sort: Column to sort by (e.g. last_seen_at, ltv, purchase_count). Defaults to + last_seen_at. + + to: End of the time range as a Unix timestamp. Defaults to now. + + 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( + "/people", + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "account_id": account_id, + "direction": direction, + "filters": filters, + "first": first, + "from_": from_, + "offset": offset, + "sort": sort, + "to": to, + }, + person_list_params.PersonListParams, + ), + ), + cast_to=PersonListResponse, + ) + + +class AsyncPeopleResource(AsyncAPIResource): + @cached_property + def with_raw_response(self) -> AsyncPeopleResourceWithRawResponse: + """ + 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 AsyncPeopleResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncPeopleResourceWithStreamingResponse: + """ + 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 AsyncPeopleResourceWithStreamingResponse(self) + + async def retrieve( + self, + person_id: str, + *, + account_id: str | Omit = omit, + from_: int | Omit = omit, + to: int | 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, + ) -> PersonRetrieveResponse: + """ + Retrieves one person for an account, aggregated from pixel events. + + Args: + account_id: The ID of the account, which will look like biz\\__******\\********. Optional for + account API keys; required for credentials that can access multiple accounts. + + from_: Start of the time range as a Unix timestamp. + + to: End of the time range as a Unix timestamp. Defaults to now. + + 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 person_id: + raise ValueError(f"Expected a non-empty value for `person_id` but received {person_id!r}") + return await self._get( + path_template("/people/{person_id}", person_id=person_id), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=await async_maybe_transform( + { + "account_id": account_id, + "from_": from_, + "to": to, + }, + person_retrieve_params.PersonRetrieveParams, + ), + ), + cast_to=PersonRetrieveResponse, + ) + + async def list( + self, + *, + account_id: str | Omit = omit, + direction: Literal["asc", "desc"] | Omit = omit, + filters: str | Omit = omit, + first: int | Omit = omit, + from_: int | Omit = omit, + offset: int | Omit = omit, + sort: str | Omit = omit, + to: int | 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, + ) -> PersonListResponse: + """ + Lists the people (visitors and customers) of an account, aggregated from pixel + events. The account is inferred from an account API key; other credentials must + pass account_id. + + Args: + account_id: The ID of the account, which will look like biz\\__******\\********. Optional for + account API keys; required for credentials that can access multiple accounts. + + direction: Sort direction. Defaults to desc. + + filters: A JSON-encoded array of filters, each with field, operator, and value keys. + + first: The number of people to return (default 100, max 101). + + from_: Start of the time range as a Unix timestamp. Defaults to 366 days before `to`. + + offset: The number of people to skip, for offset pagination. + + sort: Column to sort by (e.g. last_seen_at, ltv, purchase_count). Defaults to + last_seen_at. + + to: End of the time range as a Unix timestamp. Defaults to now. + + 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( + "/people", + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=await async_maybe_transform( + { + "account_id": account_id, + "direction": direction, + "filters": filters, + "first": first, + "from_": from_, + "offset": offset, + "sort": sort, + "to": to, + }, + person_list_params.PersonListParams, + ), + ), + cast_to=PersonListResponse, + ) + + +class PeopleResourceWithRawResponse: + def __init__(self, people: PeopleResource) -> None: + self._people = people + + self.retrieve = to_raw_response_wrapper( + people.retrieve, + ) + self.list = to_raw_response_wrapper( + people.list, + ) + + +class AsyncPeopleResourceWithRawResponse: + def __init__(self, people: AsyncPeopleResource) -> None: + self._people = people + + self.retrieve = async_to_raw_response_wrapper( + people.retrieve, + ) + self.list = async_to_raw_response_wrapper( + people.list, + ) + + +class PeopleResourceWithStreamingResponse: + def __init__(self, people: PeopleResource) -> None: + self._people = people + + self.retrieve = to_streamed_response_wrapper( + people.retrieve, + ) + self.list = to_streamed_response_wrapper( + people.list, + ) + + +class AsyncPeopleResourceWithStreamingResponse: + def __init__(self, people: AsyncPeopleResource) -> None: + self._people = people + + self.retrieve = async_to_streamed_response_wrapper( + people.retrieve, + ) + self.list = async_to_streamed_response_wrapper( + people.list, + ) diff --git a/src/whop_sdk/types/__init__.py b/src/whop_sdk/types/__init__.py index c9bcbf9b..e7bc92ff 100644 --- a/src/whop_sdk/types/__init__.py +++ b/src/whop_sdk/types/__init__.py @@ -117,6 +117,7 @@ from .app_list_response import AppListResponse as AppListResponse from .app_update_params import AppUpdateParams as AppUpdateParams from .entry_list_params import EntryListParams as EntryListParams +from .event_list_params import EventListParams as EventListParams from .forum_list_params import ForumListParams as ForumListParams from .promo_code_status import PromoCodeStatus as PromoCodeStatus from .result_label_keys import ResultLabelKeys as ResultLabelKeys @@ -135,6 +136,7 @@ from .lead_update_params import LeadUpdateParams as LeadUpdateParams from .member_list_params import MemberListParams as MemberListParams from .payout_list_params import PayoutListParams as PayoutListParams +from .person_list_params import PersonListParams as PersonListParams from .plan_create_params import PlanCreateParams as PlanCreateParams from .plan_list_response import PlanListResponse as PlanListResponse from .plan_update_params import PlanUpdateParams as PlanUpdateParams @@ -152,6 +154,7 @@ from .deposit_list_params import DepositListParams as DepositListParams from .dispute_list_params import DisputeListParams as DisputeListParams from .entry_list_response import EntryListResponse as EntryListResponse +from .event_list_response import EventListResponse as EventListResponse from .forum_list_response import ForumListResponse as ForumListResponse from .forum_update_params import ForumUpdateParams as ForumUpdateParams from .invoice_list_params import InvoiceListParams as InvoiceListParams @@ -178,6 +181,7 @@ from .member_list_response import MemberListResponse as MemberListResponse from .payment_method_types import PaymentMethodTypes as PaymentMethodTypes from .payout_list_response import PayoutListResponse as PayoutListResponse +from .person_list_response import PersonListResponse as PersonListResponse from .plan_delete_response import PlanDeleteResponse as PlanDeleteResponse from .reaction_list_params import ReactionListParams as ReactionListParams from .receipt_tax_behavior import ReceiptTaxBehavior as ReceiptTaxBehavior @@ -235,6 +239,7 @@ from .file_retrieve_response import FileRetrieveResponse as FileRetrieveResponse from .forum_post_list_params import ForumPostListParams as ForumPostListParams from .membership_list_params import MembershipListParams as MembershipListParams +from .person_retrieve_params import PersonRetrieveParams as PersonRetrieveParams from .promo_code_list_params import PromoCodeListParams as PromoCodeListParams from .reaction_create_params import ReactionCreateParams as ReactionCreateParams from .reaction_delete_params import ReactionDeleteParams as ReactionDeleteParams @@ -290,6 +295,7 @@ from .membership_update_params import MembershipUpdateParams as MembershipUpdateParams from .notification_preferences import NotificationPreferences as NotificationPreferences from .payment_list_fees_params import PaymentListFeesParams as PaymentListFeesParams +from .person_retrieve_response import PersonRetrieveResponse as PersonRetrieveResponse from .promo_code_create_params import PromoCodeCreateParams as PromoCodeCreateParams from .promo_code_list_response import PromoCodeListResponse as PromoCodeListResponse from .reaction_delete_response import ReactionDeleteResponse as ReactionDeleteResponse diff --git a/src/whop_sdk/types/event_list_params.py b/src/whop_sdk/types/event_list_params.py new file mode 100644 index 00000000..1781c509 --- /dev/null +++ b/src/whop_sdk/types/event_list_params.py @@ -0,0 +1,30 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Required, Annotated, TypedDict + +from .._utils import PropertyInfo + +__all__ = ["EventListParams"] + + +class EventListParams(TypedDict, total=False): + person_id: Required[str] + """The ID of the person.""" + + account_id: str + """The ID of the account, which will look like biz\\__******\\********. + + Optional for account API keys; required for credentials that can access multiple + accounts. + """ + + first: int + """The number of events to return.""" + + from_: Annotated[int, PropertyInfo(alias="from")] + """Start of the time range as a Unix timestamp.""" + + to: int + """End of the time range as a Unix timestamp. Defaults to now.""" diff --git a/src/whop_sdk/types/event_list_response.py b/src/whop_sdk/types/event_list_response.py new file mode 100644 index 00000000..b5fbbe18 --- /dev/null +++ b/src/whop_sdk/types/event_list_response.py @@ -0,0 +1,75 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Optional + +from .._models import BaseModel + +__all__ = ["EventListResponse", "Data", "DataContext", "DataUser"] + + +class DataContext(BaseModel): + ad_campaign_id: Optional[str] = None + + ad_id: Optional[str] = None + + ad_set_id: Optional[str] = None + + utm_campaign: Optional[str] = None + + utm_content: Optional[str] = None + + utm_medium: Optional[str] = None + + utm_source: Optional[str] = None + + utm_term: Optional[str] = None + + +class DataUser(BaseModel): + city: Optional[str] = None + + country: Optional[str] = None + + email: Optional[str] = None + + first_name: Optional[str] = None + + last_name: Optional[str] = None + + name: Optional[str] = None + + phone: Optional[str] = None + + state: Optional[str] = None + + +class Data(BaseModel): + id: str + + event_id: str + + event_name: str + + event_time: int + + context: Optional[DataContext] = None + + currency: Optional[str] = None + + custom_name: Optional[str] = None + + path: Optional[str] = None + + referrer_url: Optional[str] = None + + total_usd_amount: Optional[float] = None + + url: Optional[str] = None + + user: Optional[DataUser] = None + + value: Optional[float] = None + + +class EventListResponse(BaseModel): + data: List[Data] diff --git a/src/whop_sdk/types/person_list_params.py b/src/whop_sdk/types/person_list_params.py new file mode 100644 index 00000000..af89c9be --- /dev/null +++ b/src/whop_sdk/types/person_list_params.py @@ -0,0 +1,42 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Literal, Annotated, TypedDict + +from .._utils import PropertyInfo + +__all__ = ["PersonListParams"] + + +class PersonListParams(TypedDict, total=False): + account_id: str + """The ID of the account, which will look like biz\\__******\\********. + + Optional for account API keys; required for credentials that can access multiple + accounts. + """ + + direction: Literal["asc", "desc"] + """Sort direction. Defaults to desc.""" + + filters: str + """A JSON-encoded array of filters, each with field, operator, and value keys.""" + + first: int + """The number of people to return (default 100, max 101).""" + + from_: Annotated[int, PropertyInfo(alias="from")] + """Start of the time range as a Unix timestamp. Defaults to 366 days before `to`.""" + + offset: int + """The number of people to skip, for offset pagination.""" + + sort: str + """Column to sort by (e.g. + + last_seen_at, ltv, purchase_count). Defaults to last_seen_at. + """ + + to: int + """End of the time range as a Unix timestamp. Defaults to now.""" diff --git a/src/whop_sdk/types/person_list_response.py b/src/whop_sdk/types/person_list_response.py new file mode 100644 index 00000000..3d5d8ad4 --- /dev/null +++ b/src/whop_sdk/types/person_list_response.py @@ -0,0 +1,67 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Optional + +from .._models import BaseModel + +__all__ = ["PersonListResponse", "Data", "DataAdSet", "DataAd", "DataCampaign"] + + +class DataAdSet(BaseModel): + id: str + + name: Optional[str] = None + + thumbnail_url: Optional[str] = None + + +class DataAd(BaseModel): + id: str + + name: Optional[str] = None + + thumbnail_url: Optional[str] = None + + +class DataCampaign(BaseModel): + id: str + + name: Optional[str] = None + + thumbnail_url: Optional[str] = None + + +class Data(BaseModel): + id: str + + first_seen_at: int + + last_seen_at: int + + person_id: str + + purchase_count: int + + ad_sets: Optional[List[DataAdSet]] = None + + ads: Optional[List[DataAd]] = None + + aov: Optional[float] = None + + campaigns: Optional[List[DataCampaign]] = None + + email: Optional[str] = None + + has_failed_payment: Optional[bool] = None + + ltv: Optional[float] = None + + name: Optional[str] = None + + phone: Optional[str] = None + + +class PersonListResponse(BaseModel): + data: List[Data] + + total_count: int diff --git a/src/whop_sdk/types/person_retrieve_params.py b/src/whop_sdk/types/person_retrieve_params.py new file mode 100644 index 00000000..eb58b6db --- /dev/null +++ b/src/whop_sdk/types/person_retrieve_params.py @@ -0,0 +1,24 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Annotated, TypedDict + +from .._utils import PropertyInfo + +__all__ = ["PersonRetrieveParams"] + + +class PersonRetrieveParams(TypedDict, total=False): + account_id: str + """The ID of the account, which will look like biz\\__******\\********. + + Optional for account API keys; required for credentials that can access multiple + accounts. + """ + + from_: Annotated[int, PropertyInfo(alias="from")] + """Start of the time range as a Unix timestamp.""" + + to: int + """End of the time range as a Unix timestamp. Defaults to now.""" diff --git a/src/whop_sdk/types/person_retrieve_response.py b/src/whop_sdk/types/person_retrieve_response.py new file mode 100644 index 00000000..359b1949 --- /dev/null +++ b/src/whop_sdk/types/person_retrieve_response.py @@ -0,0 +1,65 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Optional + +from .._models import BaseModel + +__all__ = ["PersonRetrieveResponse", "Data", "DataAdSet", "DataAd", "DataCampaign"] + + +class DataAdSet(BaseModel): + id: str + + name: Optional[str] = None + + thumbnail_url: Optional[str] = None + + +class DataAd(BaseModel): + id: str + + name: Optional[str] = None + + thumbnail_url: Optional[str] = None + + +class DataCampaign(BaseModel): + id: str + + name: Optional[str] = None + + thumbnail_url: Optional[str] = None + + +class Data(BaseModel): + id: str + + first_seen_at: int + + last_seen_at: int + + person_id: str + + purchase_count: int + + ad_sets: Optional[List[DataAdSet]] = None + + ads: Optional[List[DataAd]] = None + + aov: Optional[float] = None + + campaigns: Optional[List[DataCampaign]] = None + + email: Optional[str] = None + + has_failed_payment: Optional[bool] = None + + ltv: Optional[float] = None + + name: Optional[str] = None + + phone: Optional[str] = None + + +class PersonRetrieveResponse(BaseModel): + data: Data diff --git a/tests/api_resources/test_events.py b/tests/api_resources/test_events.py new file mode 100644 index 00000000..f6807a2f --- /dev/null +++ b/tests/api_resources/test_events.py @@ -0,0 +1,116 @@ +# 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 EventListResponse + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestEvents: + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_list(self, client: Whop) -> None: + event = client.events.list( + person_id="person_id", + ) + assert_matches_type(EventListResponse, event, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_list_with_all_params(self, client: Whop) -> None: + event = client.events.list( + person_id="person_id", + account_id="account_id", + first=0, + from_=0, + to=0, + ) + assert_matches_type(EventListResponse, event, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_raw_response_list(self, client: Whop) -> None: + response = client.events.with_raw_response.list( + person_id="person_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + event = response.parse() + assert_matches_type(EventListResponse, event, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_streaming_response_list(self, client: Whop) -> None: + with client.events.with_streaming_response.list( + person_id="person_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + event = response.parse() + assert_matches_type(EventListResponse, event, path=["response"]) + + assert cast(Any, response.is_closed) is True + + +class TestAsyncEvents: + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_list(self, async_client: AsyncWhop) -> None: + event = await async_client.events.list( + person_id="person_id", + ) + assert_matches_type(EventListResponse, event, 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: + event = await async_client.events.list( + person_id="person_id", + account_id="account_id", + first=0, + from_=0, + to=0, + ) + assert_matches_type(EventListResponse, event, 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.events.with_raw_response.list( + person_id="person_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + event = await response.parse() + assert_matches_type(EventListResponse, event, 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.events.with_streaming_response.list( + person_id="person_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + event = await response.parse() + assert_matches_type(EventListResponse, event, path=["response"]) + + assert cast(Any, response.is_closed) is True diff --git a/tests/api_resources/test_people.py b/tests/api_resources/test_people.py new file mode 100644 index 00000000..8bf092ec --- /dev/null +++ b/tests/api_resources/test_people.py @@ -0,0 +1,216 @@ +# 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 PersonListResponse, PersonRetrieveResponse + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestPeople: + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_retrieve(self, client: Whop) -> None: + person = client.people.retrieve( + person_id="person_id", + ) + assert_matches_type(PersonRetrieveResponse, person, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_retrieve_with_all_params(self, client: Whop) -> None: + person = client.people.retrieve( + person_id="person_id", + account_id="account_id", + from_=0, + to=0, + ) + assert_matches_type(PersonRetrieveResponse, person, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_raw_response_retrieve(self, client: Whop) -> None: + response = client.people.with_raw_response.retrieve( + person_id="person_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + person = response.parse() + assert_matches_type(PersonRetrieveResponse, person, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_streaming_response_retrieve(self, client: Whop) -> None: + with client.people.with_streaming_response.retrieve( + person_id="person_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + person = response.parse() + assert_matches_type(PersonRetrieveResponse, person, 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 `person_id` but received ''"): + client.people.with_raw_response.retrieve( + person_id="", + ) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_list(self, client: Whop) -> None: + person = client.people.list() + assert_matches_type(PersonListResponse, person, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_list_with_all_params(self, client: Whop) -> None: + person = client.people.list( + account_id="account_id", + direction="asc", + filters="filters", + first=0, + from_=0, + offset=0, + sort="sort", + to=0, + ) + assert_matches_type(PersonListResponse, person, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_raw_response_list(self, client: Whop) -> None: + response = client.people.with_raw_response.list() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + person = response.parse() + assert_matches_type(PersonListResponse, person, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_streaming_response_list(self, client: Whop) -> None: + with client.people.with_streaming_response.list() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + person = response.parse() + assert_matches_type(PersonListResponse, person, path=["response"]) + + assert cast(Any, response.is_closed) is True + + +class TestAsyncPeople: + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_retrieve(self, async_client: AsyncWhop) -> None: + person = await async_client.people.retrieve( + person_id="person_id", + ) + assert_matches_type(PersonRetrieveResponse, person, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_retrieve_with_all_params(self, async_client: AsyncWhop) -> None: + person = await async_client.people.retrieve( + person_id="person_id", + account_id="account_id", + from_=0, + to=0, + ) + assert_matches_type(PersonRetrieveResponse, person, 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.people.with_raw_response.retrieve( + person_id="person_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + person = await response.parse() + assert_matches_type(PersonRetrieveResponse, person, 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.people.with_streaming_response.retrieve( + person_id="person_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + person = await response.parse() + assert_matches_type(PersonRetrieveResponse, person, 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 `person_id` but received ''"): + await async_client.people.with_raw_response.retrieve( + person_id="", + ) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_list(self, async_client: AsyncWhop) -> None: + person = await async_client.people.list() + assert_matches_type(PersonListResponse, person, 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: + person = await async_client.people.list( + account_id="account_id", + direction="asc", + filters="filters", + first=0, + from_=0, + offset=0, + sort="sort", + to=0, + ) + assert_matches_type(PersonListResponse, person, 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.people.with_raw_response.list() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + person = await response.parse() + assert_matches_type(PersonListResponse, person, 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.people.with_streaming_response.list() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + person = await response.parse() + assert_matches_type(PersonListResponse, person, path=["response"]) + + assert cast(Any, response.is_closed) is True