From 86cf6c6ccac9c53556cc33a9fc9d9c2b822fa9e3 Mon Sep 17 00:00:00 2001 From: stlc-bot Date: Wed, 3 Jun 2026 19:08:29 +0000 Subject: [PATCH 01/54] 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 02/54] 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 03/54] 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 04/54] 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 05/54] 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 06/54] 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 07/54] 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 08/54] 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 09/54] 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 10/54] 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 11/54] 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 12/54] 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 13/54] 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 14/54] 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 15/54] 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 16/54] 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 17/54] 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 18/54] 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 19/54] 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 20/54] 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 21/54] 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 22/54] 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 23/54] 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 24/54] 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 25/54] 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 26/54] 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 27/54] 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 28/54] 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 29/54] 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 30/54] 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 31/54] 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 32/54] 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 33/54] 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 34/54] 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 35/54] 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 36/54] 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 37/54] 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 38/54] 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 39/54] 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 40/54] 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 41/54] 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 42/54] 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 43/54] 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 44/54] 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 45/54] 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 46/54] 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 47/54] 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 48/54] 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 49/54] 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 50/54] 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 51/54] 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 52/54] 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 53/54] 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 54/54] 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"])