diff --git a/.release-please-manifest.json b/.release-please-manifest.json index e3fea219..3d2ac0bd 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "0.0.38" + ".": "0.1.0" } \ No newline at end of file diff --git a/.stats.yml b/.stats.yml index 8ca1285c..5bb99ec1 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ -configured_endpoints: 195 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/frostedinc%2Fwhopsdk-c40acc9d58677fb284fa990c261b2494d9a7f1f1e4e9cf613546972e23255cb8.yml -openapi_spec_hash: adcf3faf651ee3eb5af6e1c0fe9d2b65 -config_hash: 6ad5a913fda410def47bf2ed841e2064 +configured_endpoints: 218 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/frostedinc/whopsdk-3baab19728698b8f16db370c217ff197d7c46c74fcc80a6e7df160e35204e1d7.yml +openapi_spec_hash: c7b665366889f267a0f10c6389c36cf3 +config_hash: 82b4c2cb9b7d1707feb0f11c7c2426e9 diff --git a/CHANGELOG.md b/CHANGELOG.md index d61b0a19..6df9d322 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,40 @@ # Changelog +## 0.1.0 (2026-05-05) + +Full Changelog: [v0.0.38...v0.1.0](https://github.com/whopio/whopsdk-python/compare/v0.0.38...v0.1.0) + +### Features + +* **api:** api update ([cb4b06e](https://github.com/whopio/whopsdk-python/commit/cb4b06e28716bf53ed67ca7ac411f5e1d17496e9)) +* **api:** api update ([95abdfe](https://github.com/whopio/whopsdk-python/commit/95abdfe89b150735daa9d0caaed4834d47cc3762)) +* **api:** api update ([07e70b5](https://github.com/whopio/whopsdk-python/commit/07e70b5f3043647dd06dd54a435ac5db55eae06d)) +* **api:** api update ([0f7ed96](https://github.com/whopio/whopsdk-python/commit/0f7ed96e61417a26bf2075b687e9c0f60cf0f1ba)) +* **api:** api update ([b61b0ee](https://github.com/whopio/whopsdk-python/commit/b61b0ee58364761c8c904d82e8d195b2d5f73efb)) +* **api:** api update ([da275a3](https://github.com/whopio/whopsdk-python/commit/da275a3907434c24f3a3433d38e4c883bfa1207d)) +* **api:** api update ([b671eaa](https://github.com/whopio/whopsdk-python/commit/b671eaa7a5fd7c67ee29af64eaad905b690d9f0f)) +* **api:** api update ([44f542f](https://github.com/whopio/whopsdk-python/commit/44f542f1c1083fda63e974e60177a4bbc992b29e)) +* **api:** api update ([3189146](https://github.com/whopio/whopsdk-python/commit/3189146ef827f1e6db20cbcb38e08ff0632fc031)) +* **api:** api update ([b3f3ebf](https://github.com/whopio/whopsdk-python/commit/b3f3ebf63f5400817e222df485260eb1e6d972ff)) +* **api:** api update ([b5be3d5](https://github.com/whopio/whopsdk-python/commit/b5be3d54f1884ecdaacaa2ec0de13d961e5e3ab6)) +* **api:** api update ([1e5a3f2](https://github.com/whopio/whopsdk-python/commit/1e5a3f2d0314c3905ce099b004f4855794e05082)) +* **api:** api update ([f1d06ff](https://github.com/whopio/whopsdk-python/commit/f1d06ff881c5d7667edde20d8bd2ee7246807173)) +* **api:** api update ([eea02e9](https://github.com/whopio/whopsdk-python/commit/eea02e91bfcb142e308c88d7cdc5232b3a2c419e)) +* **api:** api update ([0449c0b](https://github.com/whopio/whopsdk-python/commit/0449c0b3e63ac52b829408c1e92d1c9495c235c4)) +* **api:** manual updates ([69d6c5c](https://github.com/whopio/whopsdk-python/commit/69d6c5cadc4d7a1bb7f2bb44b06e8f424d8e7bd0)) +* support setting headers via env ([c943353](https://github.com/whopio/whopsdk-python/commit/c943353561b164a19d00b96933fe17ecee3a1601)) + + +### Bug Fixes + +* use correct field name format for multipart file arrays ([f30e380](https://github.com/whopio/whopsdk-python/commit/f30e380baa7acbf33ccdf33e5d14d477018e3428)) + + +### Chores + +* **internal:** more robust bootstrap script ([6b442f0](https://github.com/whopio/whopsdk-python/commit/6b442f0c46d096523edfe775f8bb0c602b8f9bd3)) +* **internal:** reformat pyproject.toml ([eb865b0](https://github.com/whopio/whopsdk-python/commit/eb865b00e455c264ee6d9a3ccc99b68372e868da)) + ## 0.0.38 (2026-04-21) Full Changelog: [v0.0.37...v0.0.38](https://github.com/whopio/whopsdk-python/compare/v0.0.37...v0.0.38) diff --git a/api.md b/api.md index 11bd5e62..5a56f6af 100644 --- a/api.md +++ b/api.md @@ -131,7 +131,7 @@ Methods: Types: ```python -from whop_sdk.types import SocialLinkWebsites, CompanyListResponse +from whop_sdk.types import SocialLinkWebsites, CompanyListResponse, CompanyCreateAPIKeyResponse ``` Methods: @@ -140,6 +140,7 @@ Methods: - client.companies.retrieve(id) -> Company - client.companies.update(id, \*\*params) -> Company - client.companies.list(\*\*params) -> SyncCursorPage[CompanyListResponse] +- client.companies.create_api_key(parent_company_id, \*\*params) -> CompanyCreateAPIKeyResponse # Webhooks @@ -984,3 +985,109 @@ Methods: - client.affiliates.overrides.update(override_id, \*, id, \*\*params) -> OverrideUpdateResponse - client.affiliates.overrides.list(id, \*\*params) -> SyncCursorPage[OverrideListResponse] - client.affiliates.overrides.delete(override_id, \*, id) -> OverrideDeleteResponse + +# Bounties + +Types: + +```python +from whop_sdk.types import BountyCreateResponse, BountyRetrieveResponse, BountyListResponse +``` + +Methods: + +- client.bounties.create(\*\*params) -> BountyCreateResponse +- client.bounties.retrieve(id) -> BountyRetrieveResponse +- client.bounties.list(\*\*params) -> SyncCursorPage[BountyListResponse] + +# Stats + +Types: + +```python +from whop_sdk.types import ( + StatDescribeResponse, + StatQueryMetricResponse, + StatQueryRawResponse, + StatRunSqlResponse, +) +``` + +Methods: + +- client.stats.describe(\*\*params) -> StatDescribeResponse +- client.stats.query_metric(\*\*params) -> StatQueryMetricResponse +- client.stats.query_raw(\*\*params) -> StatQueryRawResponse +- client.stats.run_sql(\*\*params) -> StatRunSqlResponse + +# AdCampaigns + +Types: + +```python +from whop_sdk.types import ( + AdCampaignCreateResponse, + AdCampaignRetrieveResponse, + AdCampaignUpdateResponse, + AdCampaignListResponse, + AdCampaignPauseResponse, + AdCampaignUnpauseResponse, +) +``` + +Methods: + +- client.ad_campaigns.create(\*\*params) -> AdCampaignCreateResponse +- client.ad_campaigns.retrieve(id) -> AdCampaignRetrieveResponse +- client.ad_campaigns.update(id, \*\*params) -> AdCampaignUpdateResponse +- client.ad_campaigns.list(\*\*params) -> SyncCursorPage[AdCampaignListResponse] +- client.ad_campaigns.pause(id) -> AdCampaignPauseResponse +- client.ad_campaigns.unpause(id) -> AdCampaignUnpauseResponse + +# AdGroups + +Types: + +```python +from whop_sdk.types import ( + AdGroupCreateResponse, + AdGroupRetrieveResponse, + AdGroupUpdateResponse, + AdGroupListResponse, + AdGroupDeleteResponse, +) +``` + +Methods: + +- client.ad_groups.create(\*\*params) -> AdGroupCreateResponse +- client.ad_groups.retrieve(id) -> AdGroupRetrieveResponse +- client.ad_groups.update(id, \*\*params) -> AdGroupUpdateResponse +- client.ad_groups.list(\*\*params) -> SyncCursorPage[AdGroupListResponse] +- client.ad_groups.delete(id) -> AdGroupDeleteResponse + +# Ads + +Types: + +```python +from whop_sdk.types import AdCreateResponse, AdRetrieveResponse, AdListResponse +``` + +Methods: + +- client.ads.create(\*\*params) -> AdCreateResponse +- client.ads.retrieve(id) -> AdRetrieveResponse +- client.ads.list(\*\*params) -> SyncCursorPage[AdListResponse] + +# Conversions + +Types: + +```python +from whop_sdk.types import ConversionCreateResponse +``` + +Methods: + +- client.conversions.create(\*\*params) -> ConversionCreateResponse diff --git a/pyproject.toml b/pyproject.toml index fe705c71..9212ce8f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "whop-sdk" -version = "0.0.38" +version = "0.1.0" description = "The official Python library for the Whop API" dynamic = ["readme"] license = "Apache-2.0" @@ -170,7 +170,7 @@ show_error_codes = true # # We also exclude our `tests` as mypy doesn't always infer # types correctly and Pyright will still catch any type errors. -exclude = ['src/whop_sdk/_files.py', '_dev/.*.py', 'tests/.*'] +exclude = ["src/whop_sdk/_files.py", "_dev/.*.py", "tests/.*"] strict_equality = true implicit_reexport = true diff --git a/scripts/bootstrap b/scripts/bootstrap index b430fee3..fe8451e4 100755 --- a/scripts/bootstrap +++ b/scripts/bootstrap @@ -4,7 +4,7 @@ set -e cd "$(dirname "$0")/.." -if [ -f "Brewfile" ] && [ "$(uname -s)" = "Darwin" ] && [ "$SKIP_BREW" != "1" ] && [ -t 0 ]; then +if [ -f "Brewfile" ] && [ "$(uname -s)" = "Darwin" ] && [ "${SKIP_BREW:-}" != "1" ] && [ -t 0 ]; then brew bundle check >/dev/null 2>&1 || { echo -n "==> Install Homebrew dependencies? (y/N): " read -r response diff --git a/src/whop_sdk/_client.py b/src/whop_sdk/_client.py index 98fa7acd..8da7e48b 100644 --- a/src/whop_sdk/_client.py +++ b/src/whop_sdk/_client.py @@ -19,7 +19,11 @@ RequestOptions, not_given, ) -from ._utils import is_given, get_async_library +from ._utils import ( + is_given, + is_mapping_t, + get_async_library, +) from ._compat import cached_property from ._version import __version__ from ._streaming import Stream as Stream, AsyncStream as AsyncStream @@ -32,10 +36,12 @@ if TYPE_CHECKING: from .resources import ( + ads, apps, files, leads, plans, + stats, users, forums, topups, @@ -45,12 +51,14 @@ refunds, reviews, ai_chats, + bounties, disputes, invoices, messages, payments, products, webhooks, + ad_groups, companies, reactions, shipments, @@ -58,6 +66,7 @@ affiliates, app_builds, dm_members, + conversions, dm_channels, experiences, fee_markups, @@ -65,6 +74,7 @@ memberships, promo_codes, withdrawals, + ad_campaigns, access_tokens, account_links, chat_channels, @@ -86,10 +96,12 @@ company_token_transactions, course_lesson_interactions, ) + from .resources.ads import AdsResource, AsyncAdsResource from .resources.apps import AppsResource, AsyncAppsResource from .resources.files import FilesResource, AsyncFilesResource from .resources.leads import LeadsResource, AsyncLeadsResource from .resources.plans import PlansResource, AsyncPlansResource + from .resources.stats import StatsResource, AsyncStatsResource from .resources.users import UsersResource, AsyncUsersResource from .resources.forums import ForumsResource, AsyncForumsResource from .resources.topups import TopupsResource, AsyncTopupsResource @@ -99,18 +111,21 @@ from .resources.refunds import RefundsResource, AsyncRefundsResource from .resources.reviews import ReviewsResource, AsyncReviewsResource from .resources.ai_chats import AIChatsResource, AsyncAIChatsResource + from .resources.bounties import BountiesResource, AsyncBountiesResource from .resources.disputes import DisputesResource, AsyncDisputesResource from .resources.invoices import InvoicesResource, AsyncInvoicesResource from .resources.messages import MessagesResource, AsyncMessagesResource from .resources.payments import PaymentsResource, AsyncPaymentsResource from .resources.products import ProductsResource, AsyncProductsResource from .resources.webhooks import WebhooksResource, AsyncWebhooksResource + from .resources.ad_groups import AdGroupsResource, AsyncAdGroupsResource from .resources.companies import CompaniesResource, AsyncCompaniesResource from .resources.reactions import ReactionsResource, AsyncReactionsResource from .resources.shipments import ShipmentsResource, AsyncShipmentsResource from .resources.transfers import TransfersResource, AsyncTransfersResource from .resources.app_builds import AppBuildsResource, AsyncAppBuildsResource from .resources.dm_members import DmMembersResource, AsyncDmMembersResource + from .resources.conversions import ConversionsResource, AsyncConversionsResource from .resources.dm_channels import DmChannelsResource, AsyncDmChannelsResource from .resources.experiences import ExperiencesResource, AsyncExperiencesResource from .resources.fee_markups import FeeMarkupsResource, AsyncFeeMarkupsResource @@ -118,6 +133,7 @@ from .resources.memberships import MembershipsResource, AsyncMembershipsResource from .resources.promo_codes import PromoCodesResource, AsyncPromoCodesResource from .resources.withdrawals import WithdrawalsResource, AsyncWithdrawalsResource + from .resources.ad_campaigns import AdCampaignsResource, AsyncAdCampaignsResource from .resources.access_tokens import AccessTokensResource, AsyncAccessTokensResource from .resources.account_links import AccountLinksResource, AsyncAccountLinksResource from .resources.chat_channels import ChatChannelsResource, AsyncChatChannelsResource @@ -208,6 +224,15 @@ def __init__( if base_url is None: base_url = f"https://api.whop.com/api/v1" + custom_headers_env = os.environ.get("WHOP_CUSTOM_HEADERS") + if custom_headers_env is not None: + parsed: dict[str, str] = {} + for line in custom_headers_env.split("\n"): + colon = line.find(":") + if colon >= 0: + parsed[line[:colon].strip()] = line[colon + 1 :].strip() + default_headers = {**parsed, **(default_headers if is_mapping_t(default_headers) else {})} + super().__init__( version=__version__, base_url=base_url, @@ -540,6 +565,48 @@ def affiliates(self) -> AffiliatesResource: return AffiliatesResource(self) + @cached_property + def bounties(self) -> BountiesResource: + """Bounties""" + from .resources.bounties import BountiesResource + + return BountiesResource(self) + + @cached_property + def stats(self) -> StatsResource: + """Stats""" + from .resources.stats import StatsResource + + return StatsResource(self) + + @cached_property + def ad_campaigns(self) -> AdCampaignsResource: + """Ad campaigns""" + from .resources.ad_campaigns import AdCampaignsResource + + return AdCampaignsResource(self) + + @cached_property + def ad_groups(self) -> AdGroupsResource: + """Ad groups""" + from .resources.ad_groups import AdGroupsResource + + return AdGroupsResource(self) + + @cached_property + def ads(self) -> AdsResource: + """Ads""" + from .resources.ads import AdsResource + + return AdsResource(self) + + @cached_property + def conversions(self) -> ConversionsResource: + """Conversions""" + from .resources.conversions import ConversionsResource + + return ConversionsResource(self) + @cached_property def with_raw_response(self) -> WhopWithRawResponse: return WhopWithRawResponse(self) @@ -717,6 +784,15 @@ def __init__( if base_url is None: base_url = f"https://api.whop.com/api/v1" + custom_headers_env = os.environ.get("WHOP_CUSTOM_HEADERS") + if custom_headers_env is not None: + parsed: dict[str, str] = {} + for line in custom_headers_env.split("\n"): + colon = line.find(":") + if colon >= 0: + parsed[line[:colon].strip()] = line[colon + 1 :].strip() + default_headers = {**parsed, **(default_headers if is_mapping_t(default_headers) else {})} + super().__init__( version=__version__, base_url=base_url, @@ -1049,6 +1125,48 @@ def affiliates(self) -> AsyncAffiliatesResource: return AsyncAffiliatesResource(self) + @cached_property + def bounties(self) -> AsyncBountiesResource: + """Bounties""" + from .resources.bounties import AsyncBountiesResource + + return AsyncBountiesResource(self) + + @cached_property + def stats(self) -> AsyncStatsResource: + """Stats""" + from .resources.stats import AsyncStatsResource + + return AsyncStatsResource(self) + + @cached_property + def ad_campaigns(self) -> AsyncAdCampaignsResource: + """Ad campaigns""" + from .resources.ad_campaigns import AsyncAdCampaignsResource + + return AsyncAdCampaignsResource(self) + + @cached_property + def ad_groups(self) -> AsyncAdGroupsResource: + """Ad groups""" + from .resources.ad_groups import AsyncAdGroupsResource + + return AsyncAdGroupsResource(self) + + @cached_property + def ads(self) -> AsyncAdsResource: + """Ads""" + from .resources.ads import AsyncAdsResource + + return AsyncAdsResource(self) + + @cached_property + def conversions(self) -> AsyncConversionsResource: + """Conversions""" + from .resources.conversions import AsyncConversionsResource + + return AsyncConversionsResource(self) + @cached_property def with_raw_response(self) -> AsyncWhopWithRawResponse: return AsyncWhopWithRawResponse(self) @@ -1494,6 +1612,48 @@ def affiliates(self) -> affiliates.AffiliatesResourceWithRawResponse: return AffiliatesResourceWithRawResponse(self._client.affiliates) + @cached_property + def bounties(self) -> bounties.BountiesResourceWithRawResponse: + """Bounties""" + from .resources.bounties import BountiesResourceWithRawResponse + + return BountiesResourceWithRawResponse(self._client.bounties) + + @cached_property + def stats(self) -> stats.StatsResourceWithRawResponse: + """Stats""" + from .resources.stats import StatsResourceWithRawResponse + + return StatsResourceWithRawResponse(self._client.stats) + + @cached_property + def ad_campaigns(self) -> ad_campaigns.AdCampaignsResourceWithRawResponse: + """Ad campaigns""" + from .resources.ad_campaigns import AdCampaignsResourceWithRawResponse + + return AdCampaignsResourceWithRawResponse(self._client.ad_campaigns) + + @cached_property + def ad_groups(self) -> ad_groups.AdGroupsResourceWithRawResponse: + """Ad groups""" + from .resources.ad_groups import AdGroupsResourceWithRawResponse + + return AdGroupsResourceWithRawResponse(self._client.ad_groups) + + @cached_property + def ads(self) -> ads.AdsResourceWithRawResponse: + """Ads""" + from .resources.ads import AdsResourceWithRawResponse + + return AdsResourceWithRawResponse(self._client.ads) + + @cached_property + def conversions(self) -> conversions.ConversionsResourceWithRawResponse: + """Conversions""" + from .resources.conversions import ConversionsResourceWithRawResponse + + return ConversionsResourceWithRawResponse(self._client.conversions) + class AsyncWhopWithRawResponse: _client: AsyncWhop @@ -1826,6 +1986,48 @@ def affiliates(self) -> affiliates.AsyncAffiliatesResourceWithRawResponse: return AsyncAffiliatesResourceWithRawResponse(self._client.affiliates) + @cached_property + def bounties(self) -> bounties.AsyncBountiesResourceWithRawResponse: + """Bounties""" + from .resources.bounties import AsyncBountiesResourceWithRawResponse + + return AsyncBountiesResourceWithRawResponse(self._client.bounties) + + @cached_property + def stats(self) -> stats.AsyncStatsResourceWithRawResponse: + """Stats""" + from .resources.stats import AsyncStatsResourceWithRawResponse + + return AsyncStatsResourceWithRawResponse(self._client.stats) + + @cached_property + def ad_campaigns(self) -> ad_campaigns.AsyncAdCampaignsResourceWithRawResponse: + """Ad campaigns""" + from .resources.ad_campaigns import AsyncAdCampaignsResourceWithRawResponse + + return AsyncAdCampaignsResourceWithRawResponse(self._client.ad_campaigns) + + @cached_property + def ad_groups(self) -> ad_groups.AsyncAdGroupsResourceWithRawResponse: + """Ad groups""" + from .resources.ad_groups import AsyncAdGroupsResourceWithRawResponse + + return AsyncAdGroupsResourceWithRawResponse(self._client.ad_groups) + + @cached_property + def ads(self) -> ads.AsyncAdsResourceWithRawResponse: + """Ads""" + from .resources.ads import AsyncAdsResourceWithRawResponse + + return AsyncAdsResourceWithRawResponse(self._client.ads) + + @cached_property + def conversions(self) -> conversions.AsyncConversionsResourceWithRawResponse: + """Conversions""" + from .resources.conversions import AsyncConversionsResourceWithRawResponse + + return AsyncConversionsResourceWithRawResponse(self._client.conversions) + class WhopWithStreamedResponse: _client: Whop @@ -2158,6 +2360,48 @@ def affiliates(self) -> affiliates.AffiliatesResourceWithStreamingResponse: return AffiliatesResourceWithStreamingResponse(self._client.affiliates) + @cached_property + def bounties(self) -> bounties.BountiesResourceWithStreamingResponse: + """Bounties""" + from .resources.bounties import BountiesResourceWithStreamingResponse + + return BountiesResourceWithStreamingResponse(self._client.bounties) + + @cached_property + def stats(self) -> stats.StatsResourceWithStreamingResponse: + """Stats""" + from .resources.stats import StatsResourceWithStreamingResponse + + return StatsResourceWithStreamingResponse(self._client.stats) + + @cached_property + def ad_campaigns(self) -> ad_campaigns.AdCampaignsResourceWithStreamingResponse: + """Ad campaigns""" + from .resources.ad_campaigns import AdCampaignsResourceWithStreamingResponse + + return AdCampaignsResourceWithStreamingResponse(self._client.ad_campaigns) + + @cached_property + def ad_groups(self) -> ad_groups.AdGroupsResourceWithStreamingResponse: + """Ad groups""" + from .resources.ad_groups import AdGroupsResourceWithStreamingResponse + + return AdGroupsResourceWithStreamingResponse(self._client.ad_groups) + + @cached_property + def ads(self) -> ads.AdsResourceWithStreamingResponse: + """Ads""" + from .resources.ads import AdsResourceWithStreamingResponse + + return AdsResourceWithStreamingResponse(self._client.ads) + + @cached_property + def conversions(self) -> conversions.ConversionsResourceWithStreamingResponse: + """Conversions""" + from .resources.conversions import ConversionsResourceWithStreamingResponse + + return ConversionsResourceWithStreamingResponse(self._client.conversions) + class AsyncWhopWithStreamedResponse: _client: AsyncWhop @@ -2494,6 +2738,48 @@ def affiliates(self) -> affiliates.AsyncAffiliatesResourceWithStreamingResponse: return AsyncAffiliatesResourceWithStreamingResponse(self._client.affiliates) + @cached_property + def bounties(self) -> bounties.AsyncBountiesResourceWithStreamingResponse: + """Bounties""" + from .resources.bounties import AsyncBountiesResourceWithStreamingResponse + + return AsyncBountiesResourceWithStreamingResponse(self._client.bounties) + + @cached_property + def stats(self) -> stats.AsyncStatsResourceWithStreamingResponse: + """Stats""" + from .resources.stats import AsyncStatsResourceWithStreamingResponse + + return AsyncStatsResourceWithStreamingResponse(self._client.stats) + + @cached_property + def ad_campaigns(self) -> ad_campaigns.AsyncAdCampaignsResourceWithStreamingResponse: + """Ad campaigns""" + from .resources.ad_campaigns import AsyncAdCampaignsResourceWithStreamingResponse + + return AsyncAdCampaignsResourceWithStreamingResponse(self._client.ad_campaigns) + + @cached_property + def ad_groups(self) -> ad_groups.AsyncAdGroupsResourceWithStreamingResponse: + """Ad groups""" + from .resources.ad_groups import AsyncAdGroupsResourceWithStreamingResponse + + return AsyncAdGroupsResourceWithStreamingResponse(self._client.ad_groups) + + @cached_property + def ads(self) -> ads.AsyncAdsResourceWithStreamingResponse: + """Ads""" + from .resources.ads import AsyncAdsResourceWithStreamingResponse + + return AsyncAdsResourceWithStreamingResponse(self._client.ads) + + @cached_property + def conversions(self) -> conversions.AsyncConversionsResourceWithStreamingResponse: + """Conversions""" + from .resources.conversions import AsyncConversionsResourceWithStreamingResponse + + return AsyncConversionsResourceWithStreamingResponse(self._client.conversions) + Client = Whop diff --git a/src/whop_sdk/_qs.py b/src/whop_sdk/_qs.py index de8c99bc..4127c19c 100644 --- a/src/whop_sdk/_qs.py +++ b/src/whop_sdk/_qs.py @@ -2,17 +2,13 @@ from typing import Any, List, Tuple, Union, Mapping, TypeVar from urllib.parse import parse_qs, urlencode -from typing_extensions import Literal, get_args +from typing_extensions import get_args -from ._types import NotGiven, not_given +from ._types import NotGiven, ArrayFormat, NestedFormat, not_given from ._utils import flatten _T = TypeVar("_T") - -ArrayFormat = Literal["comma", "repeat", "indices", "brackets"] -NestedFormat = Literal["dots", "brackets"] - PrimitiveData = Union[str, int, float, bool, None] # this should be Data = Union[PrimitiveData, "List[Data]", "Tuple[Data]", "Mapping[str, Data]"] # https://github.com/microsoft/pyright/issues/3555 diff --git a/src/whop_sdk/_types.py b/src/whop_sdk/_types.py index 66da6e5e..05b7ccfe 100644 --- a/src/whop_sdk/_types.py +++ b/src/whop_sdk/_types.py @@ -47,6 +47,9 @@ ModelT = TypeVar("ModelT", bound=pydantic.BaseModel) _T = TypeVar("_T") +ArrayFormat = Literal["comma", "repeat", "indices", "brackets"] +NestedFormat = Literal["dots", "brackets"] + # Approximates httpx internal ProxiesTypes and RequestFiles types # while adding support for `PathLike` instances diff --git a/src/whop_sdk/_utils/_utils.py b/src/whop_sdk/_utils/_utils.py index 771859f5..199cd231 100644 --- a/src/whop_sdk/_utils/_utils.py +++ b/src/whop_sdk/_utils/_utils.py @@ -17,11 +17,11 @@ ) from pathlib import Path from datetime import date, datetime -from typing_extensions import TypeGuard +from typing_extensions import TypeGuard, get_args import sniffio -from .._types import Omit, NotGiven, FileTypes, HeadersLike +from .._types import Omit, NotGiven, FileTypes, ArrayFormat, HeadersLike _T = TypeVar("_T") _TupleT = TypeVar("_TupleT", bound=Tuple[object, ...]) @@ -40,25 +40,45 @@ def extract_files( query: Mapping[str, object], *, paths: Sequence[Sequence[str]], + array_format: ArrayFormat = "brackets", ) -> list[tuple[str, FileTypes]]: """Recursively extract files from the given dictionary based on specified paths. A path may look like this ['foo', 'files', '', 'data']. + ``array_format`` controls how ```` segments contribute to the emitted + field name. Supported values: ``"brackets"`` (``foo[]``), ``"repeat"`` and + ``"comma"`` (``foo``), ``"indices"`` (``foo[0]``, ``foo[1]``). + Note: this mutates the given dictionary. """ files: list[tuple[str, FileTypes]] = [] for path in paths: - files.extend(_extract_items(query, path, index=0, flattened_key=None)) + files.extend(_extract_items(query, path, index=0, flattened_key=None, array_format=array_format)) return files +def _array_suffix(array_format: ArrayFormat, array_index: int) -> str: + if array_format == "brackets": + return "[]" + if array_format == "indices": + return f"[{array_index}]" + if array_format == "repeat" or array_format == "comma": + # Both repeat the bare field name for each file part; there is no + # meaningful way to comma-join binary parts. + return "" + raise NotImplementedError( + f"Unknown array_format value: {array_format}, choose from {', '.join(get_args(ArrayFormat))}" + ) + + def _extract_items( obj: object, path: Sequence[str], *, index: int, flattened_key: str | None, + array_format: ArrayFormat, ) -> list[tuple[str, FileTypes]]: try: key = path[index] @@ -75,9 +95,11 @@ def _extract_items( if is_list(obj): files: list[tuple[str, FileTypes]] = [] - for entry in obj: - assert_is_file_content(entry, key=flattened_key + "[]" if flattened_key else "") - files.append((flattened_key + "[]", cast(FileTypes, entry))) + for array_index, entry in enumerate(obj): + suffix = _array_suffix(array_format, array_index) + emitted_key = (flattened_key + suffix) if flattened_key else suffix + assert_is_file_content(entry, key=emitted_key) + files.append((emitted_key, cast(FileTypes, entry))) return files assert_is_file_content(obj, key=flattened_key) @@ -106,6 +128,7 @@ def _extract_items( path, index=index, flattened_key=flattened_key, + array_format=array_format, ) elif is_list(obj): if key != "": @@ -117,9 +140,12 @@ def _extract_items( item, path, index=index, - flattened_key=flattened_key + "[]" if flattened_key is not None else "[]", + flattened_key=( + (flattened_key if flattened_key is not None else "") + _array_suffix(array_format, array_index) + ), + array_format=array_format, ) - for item in obj + for array_index, item in enumerate(obj) ] ) diff --git a/src/whop_sdk/_version.py b/src/whop_sdk/_version.py index dececef3..162053b4 100644 --- a/src/whop_sdk/_version.py +++ b/src/whop_sdk/_version.py @@ -1,4 +1,4 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. __title__ = "whop_sdk" -__version__ = "0.0.38" # x-release-please-version +__version__ = "0.1.0" # x-release-please-version diff --git a/src/whop_sdk/resources/__init__.py b/src/whop_sdk/resources/__init__.py index e95bf535..cfbbf5fb 100644 --- a/src/whop_sdk/resources/__init__.py +++ b/src/whop_sdk/resources/__init__.py @@ -1,5 +1,13 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. +from .ads import ( + AdsResource, + AsyncAdsResource, + AdsResourceWithRawResponse, + AsyncAdsResourceWithRawResponse, + AdsResourceWithStreamingResponse, + AsyncAdsResourceWithStreamingResponse, +) from .apps import ( AppsResource, AsyncAppsResource, @@ -32,6 +40,14 @@ PlansResourceWithStreamingResponse, AsyncPlansResourceWithStreamingResponse, ) +from .stats import ( + StatsResource, + AsyncStatsResource, + StatsResourceWithRawResponse, + AsyncStatsResourceWithRawResponse, + StatsResourceWithStreamingResponse, + AsyncStatsResourceWithStreamingResponse, +) from .users import ( UsersResource, AsyncUsersResource, @@ -104,6 +120,14 @@ AIChatsResourceWithStreamingResponse, AsyncAIChatsResourceWithStreamingResponse, ) +from .bounties import ( + BountiesResource, + AsyncBountiesResource, + BountiesResourceWithRawResponse, + AsyncBountiesResourceWithRawResponse, + BountiesResourceWithStreamingResponse, + AsyncBountiesResourceWithStreamingResponse, +) from .disputes import ( DisputesResource, AsyncDisputesResource, @@ -152,6 +176,14 @@ WebhooksResourceWithStreamingResponse, AsyncWebhooksResourceWithStreamingResponse, ) +from .ad_groups import ( + AdGroupsResource, + AsyncAdGroupsResource, + AdGroupsResourceWithRawResponse, + AsyncAdGroupsResourceWithRawResponse, + AdGroupsResourceWithStreamingResponse, + AsyncAdGroupsResourceWithStreamingResponse, +) from .companies import ( CompaniesResource, AsyncCompaniesResource, @@ -208,6 +240,14 @@ DmMembersResourceWithStreamingResponse, AsyncDmMembersResourceWithStreamingResponse, ) +from .conversions import ( + ConversionsResource, + AsyncConversionsResource, + ConversionsResourceWithRawResponse, + AsyncConversionsResourceWithRawResponse, + ConversionsResourceWithStreamingResponse, + AsyncConversionsResourceWithStreamingResponse, +) from .dm_channels import ( DmChannelsResource, AsyncDmChannelsResource, @@ -264,6 +304,14 @@ WithdrawalsResourceWithStreamingResponse, AsyncWithdrawalsResourceWithStreamingResponse, ) +from .ad_campaigns import ( + AdCampaignsResource, + AsyncAdCampaignsResource, + AdCampaignsResourceWithRawResponse, + AsyncAdCampaignsResourceWithRawResponse, + AdCampaignsResourceWithStreamingResponse, + AsyncAdCampaignsResourceWithStreamingResponse, +) from .access_tokens import ( AccessTokensResource, AsyncAccessTokensResource, @@ -744,4 +792,40 @@ "AsyncAffiliatesResourceWithRawResponse", "AffiliatesResourceWithStreamingResponse", "AsyncAffiliatesResourceWithStreamingResponse", + "BountiesResource", + "AsyncBountiesResource", + "BountiesResourceWithRawResponse", + "AsyncBountiesResourceWithRawResponse", + "BountiesResourceWithStreamingResponse", + "AsyncBountiesResourceWithStreamingResponse", + "StatsResource", + "AsyncStatsResource", + "StatsResourceWithRawResponse", + "AsyncStatsResourceWithRawResponse", + "StatsResourceWithStreamingResponse", + "AsyncStatsResourceWithStreamingResponse", + "AdCampaignsResource", + "AsyncAdCampaignsResource", + "AdCampaignsResourceWithRawResponse", + "AsyncAdCampaignsResourceWithRawResponse", + "AdCampaignsResourceWithStreamingResponse", + "AsyncAdCampaignsResourceWithStreamingResponse", + "AdGroupsResource", + "AsyncAdGroupsResource", + "AdGroupsResourceWithRawResponse", + "AsyncAdGroupsResourceWithRawResponse", + "AdGroupsResourceWithStreamingResponse", + "AsyncAdGroupsResourceWithStreamingResponse", + "AdsResource", + "AsyncAdsResource", + "AdsResourceWithRawResponse", + "AsyncAdsResourceWithRawResponse", + "AdsResourceWithStreamingResponse", + "AsyncAdsResourceWithStreamingResponse", + "ConversionsResource", + "AsyncConversionsResource", + "ConversionsResourceWithRawResponse", + "AsyncConversionsResourceWithRawResponse", + "ConversionsResourceWithStreamingResponse", + "AsyncConversionsResourceWithStreamingResponse", ] diff --git a/src/whop_sdk/resources/ad_campaigns.py b/src/whop_sdk/resources/ad_campaigns.py new file mode 100644 index 00000000..b14aa719 --- /dev/null +++ b/src/whop_sdk/resources/ad_campaigns.py @@ -0,0 +1,911 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Union, Optional +from datetime import datetime +from typing_extensions import Literal + +import httpx + +from ..types import ad_campaign_list_params, ad_campaign_create_params, ad_campaign_update_params +from .._types import Body, Omit, Query, Headers, NotGiven, SequenceNotStr, omit, not_given +from .._utils import path_template, maybe_transform, async_maybe_transform +from .._compat import cached_property +from .._resource import SyncAPIResource, AsyncAPIResource +from .._response import ( + to_raw_response_wrapper, + to_streamed_response_wrapper, + async_to_raw_response_wrapper, + async_to_streamed_response_wrapper, +) +from ..pagination import SyncCursorPage, AsyncCursorPage +from .._base_client import AsyncPaginator, make_request_options +from ..types.ad_campaign_list_response import AdCampaignListResponse +from ..types.ad_campaign_pause_response import AdCampaignPauseResponse +from ..types.ad_campaign_create_response import AdCampaignCreateResponse +from ..types.ad_campaign_update_response import AdCampaignUpdateResponse +from ..types.ad_campaign_unpause_response import AdCampaignUnpauseResponse +from ..types.ad_campaign_retrieve_response import AdCampaignRetrieveResponse + +__all__ = ["AdCampaignsResource", "AsyncAdCampaignsResource"] + + +class AdCampaignsResource(SyncAPIResource): + """Ad campaigns""" + + @cached_property + def with_raw_response(self) -> AdCampaignsResourceWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/whopio/whopsdk-python#accessing-raw-response-data-eg-headers + """ + return AdCampaignsResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AdCampaignsResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/whopio/whopsdk-python#with_streaming_response + """ + return AdCampaignsResourceWithStreamingResponse(self) + + def create( + self, + *, + company_id: str, + config: ad_campaign_create_params.Config, + platform: Literal["meta", "tiktok"], + title: str, + ad_creative_set_ids: Optional[SequenceNotStr[str]] | Omit = omit, + budget: Optional[float] | Omit = omit, + budget_type: Optional[Literal["daily", "lifetime"]] | Omit = omit, + daily_budget: Optional[float] | Omit = omit, + product_id: Optional[str] | Omit = omit, + target_country_codes: Optional[SequenceNotStr[str]] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> AdCampaignCreateResponse: + """ + Creates a new ad campaign for a product. + + Required permissions: + + - `ad_campaign:create` + - `access_pass:basic:read` + - `company:balance:read` + + Args: + company_id: The company ID to create this ad campaign for. + + config: Unified campaign configuration (conversion goal, budget, bidding, etc.). + + platform: The ad platform to run on (e.g., meta, tiktok). + + title: The title of the ad campaign. Must be max 100 characters. + + ad_creative_set_ids: Array of creative set IDs to link to this campaign. + + budget: Budget amount in dollars. + + budget_type: The budget type for an ad campaign or ad group. + + daily_budget: Daily budget in dollars (minimum $5). Required unless lifetime_budget is set in + config. + + product_id: The unique identifier of the product to promote. + + target_country_codes: Array of ISO3166 country codes for territory targeting. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return self._post( + "/ad_campaigns", + body=maybe_transform( + { + "company_id": company_id, + "config": config, + "platform": platform, + "title": title, + "ad_creative_set_ids": ad_creative_set_ids, + "budget": budget, + "budget_type": budget_type, + "daily_budget": daily_budget, + "product_id": product_id, + "target_country_codes": target_country_codes, + }, + ad_campaign_create_params.AdCampaignCreateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=AdCampaignCreateResponse, + ) + + def retrieve( + self, + id: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> AdCampaignRetrieveResponse: + """ + Retrieves a single ad campaign by its unique identifier. + + Required permissions: + + - `ad_campaign:basic:read` + - `access_pass:basic:read` + - `company:balance:read` + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not id: + raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") + return self._get( + path_template("/ad_campaigns/{id}", id=id), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=AdCampaignRetrieveResponse, + ) + + def update( + self, + id: str, + *, + ad_creative_set_ids: Optional[SequenceNotStr[str]] | Omit = omit, + budget: Optional[float] | Omit = omit, + budget_type: Optional[Literal["daily", "lifetime"]] | Omit = omit, + config: Optional[ad_campaign_update_params.Config] | Omit = omit, + daily_budget: Optional[float] | Omit = omit, + product_id: Optional[str] | Omit = omit, + target_country_codes: Optional[SequenceNotStr[str]] | Omit = omit, + title: Optional[str] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> AdCampaignUpdateResponse: + """ + Updates an existing ad campaign. + + Required permissions: + + - `ad_campaign:update` + - `access_pass:basic:read` + - `company:balance:read` + + Args: + ad_creative_set_ids: Array of creative set IDs to link to this campaign. + + budget: Budget amount in dollars. + + budget_type: The budget type for an ad campaign or ad group. + + config: Unified campaign configuration (conversion goal, budget, bidding, etc.). + + daily_budget: Daily budget in dollars (minimum $5). + + product_id: The unique identifier of the product (access pass) to promote. + + target_country_codes: Array of ISO3166 country codes for territory targeting. + + title: The title of the ad campaign. Must be max 100 characters. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not id: + raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") + return self._patch( + path_template("/ad_campaigns/{id}", id=id), + body=maybe_transform( + { + "ad_creative_set_ids": ad_creative_set_ids, + "budget": budget, + "budget_type": budget_type, + "config": config, + "daily_budget": daily_budget, + "product_id": product_id, + "target_country_codes": target_country_codes, + "title": title, + }, + ad_campaign_update_params.AdCampaignUpdateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=AdCampaignUpdateResponse, + ) + + def list( + self, + *, + company_id: str, + after: Optional[str] | Omit = omit, + before: Optional[str] | Omit = omit, + created_after: Union[str, datetime, None] | Omit = omit, + created_before: Union[str, datetime, None] | Omit = omit, + first: Optional[int] | Omit = omit, + last: Optional[int] | Omit = omit, + query: Optional[str] | Omit = omit, + status: Optional[ + Literal[ + "active", + "paused", + "inactive", + "stale", + "pending_refund", + "payment_failed", + "draft", + "in_review", + "flagged", + ] + ] + | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> SyncCursorPage[AdCampaignListResponse]: + """ + Returns a paginated list of ad campaigns for a company, with optional filtering + by status, and creation date. + + Required permissions: + + - `ad_campaign:basic:read` + - `access_pass:basic:read` + + Args: + company_id: The unique identifier of the company to list ad campaigns for. + + after: Returns the elements in the list that come after the specified cursor. + + before: Returns the elements in the list that come before the specified cursor. + + created_after: Only return ad campaigns created after this timestamp. + + created_before: Only return ad campaigns created before this timestamp. + + first: Returns the first _n_ elements from the list. + + last: Returns the last _n_ elements from the list. + + query: Case-insensitive substring match against the campaign title. + + status: The status of an ad campaign. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return self._get_api_list( + "/ad_campaigns", + page=SyncCursorPage[AdCampaignListResponse], + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "company_id": company_id, + "after": after, + "before": before, + "created_after": created_after, + "created_before": created_before, + "first": first, + "last": last, + "query": query, + "status": status, + }, + ad_campaign_list_params.AdCampaignListParams, + ), + ), + model=AdCampaignListResponse, + ) + + def pause( + self, + id: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> AdCampaignPauseResponse: + """ + Pauses an ad campaign, optionally until a specific date. + + Required permissions: + + - `ad_campaign:update` + - `access_pass:basic:read` + - `company:balance:read` + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not id: + raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") + return self._post( + path_template("/ad_campaigns/{id}/pause", id=id), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=AdCampaignPauseResponse, + ) + + def unpause( + self, + id: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> AdCampaignUnpauseResponse: + """ + Resumes a paused ad campaign. + + Required permissions: + + - `ad_campaign:update` + - `access_pass:basic:read` + - `company:balance:read` + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not id: + raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") + return self._post( + path_template("/ad_campaigns/{id}/unpause", id=id), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=AdCampaignUnpauseResponse, + ) + + +class AsyncAdCampaignsResource(AsyncAPIResource): + """Ad campaigns""" + + @cached_property + def with_raw_response(self) -> AsyncAdCampaignsResourceWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/whopio/whopsdk-python#accessing-raw-response-data-eg-headers + """ + return AsyncAdCampaignsResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncAdCampaignsResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/whopio/whopsdk-python#with_streaming_response + """ + return AsyncAdCampaignsResourceWithStreamingResponse(self) + + async def create( + self, + *, + company_id: str, + config: ad_campaign_create_params.Config, + platform: Literal["meta", "tiktok"], + title: str, + ad_creative_set_ids: Optional[SequenceNotStr[str]] | Omit = omit, + budget: Optional[float] | Omit = omit, + budget_type: Optional[Literal["daily", "lifetime"]] | Omit = omit, + daily_budget: Optional[float] | Omit = omit, + product_id: Optional[str] | Omit = omit, + target_country_codes: Optional[SequenceNotStr[str]] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> AdCampaignCreateResponse: + """ + Creates a new ad campaign for a product. + + Required permissions: + + - `ad_campaign:create` + - `access_pass:basic:read` + - `company:balance:read` + + Args: + company_id: The company ID to create this ad campaign for. + + config: Unified campaign configuration (conversion goal, budget, bidding, etc.). + + platform: The ad platform to run on (e.g., meta, tiktok). + + title: The title of the ad campaign. Must be max 100 characters. + + ad_creative_set_ids: Array of creative set IDs to link to this campaign. + + budget: Budget amount in dollars. + + budget_type: The budget type for an ad campaign or ad group. + + daily_budget: Daily budget in dollars (minimum $5). Required unless lifetime_budget is set in + config. + + product_id: The unique identifier of the product to promote. + + target_country_codes: Array of ISO3166 country codes for territory targeting. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return await self._post( + "/ad_campaigns", + body=await async_maybe_transform( + { + "company_id": company_id, + "config": config, + "platform": platform, + "title": title, + "ad_creative_set_ids": ad_creative_set_ids, + "budget": budget, + "budget_type": budget_type, + "daily_budget": daily_budget, + "product_id": product_id, + "target_country_codes": target_country_codes, + }, + ad_campaign_create_params.AdCampaignCreateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=AdCampaignCreateResponse, + ) + + async def retrieve( + self, + id: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> AdCampaignRetrieveResponse: + """ + Retrieves a single ad campaign by its unique identifier. + + Required permissions: + + - `ad_campaign:basic:read` + - `access_pass:basic:read` + - `company:balance:read` + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not id: + raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") + return await self._get( + path_template("/ad_campaigns/{id}", id=id), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=AdCampaignRetrieveResponse, + ) + + async def update( + self, + id: str, + *, + ad_creative_set_ids: Optional[SequenceNotStr[str]] | Omit = omit, + budget: Optional[float] | Omit = omit, + budget_type: Optional[Literal["daily", "lifetime"]] | Omit = omit, + config: Optional[ad_campaign_update_params.Config] | Omit = omit, + daily_budget: Optional[float] | Omit = omit, + product_id: Optional[str] | Omit = omit, + target_country_codes: Optional[SequenceNotStr[str]] | Omit = omit, + title: Optional[str] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> AdCampaignUpdateResponse: + """ + Updates an existing ad campaign. + + Required permissions: + + - `ad_campaign:update` + - `access_pass:basic:read` + - `company:balance:read` + + Args: + ad_creative_set_ids: Array of creative set IDs to link to this campaign. + + budget: Budget amount in dollars. + + budget_type: The budget type for an ad campaign or ad group. + + config: Unified campaign configuration (conversion goal, budget, bidding, etc.). + + daily_budget: Daily budget in dollars (minimum $5). + + product_id: The unique identifier of the product (access pass) to promote. + + target_country_codes: Array of ISO3166 country codes for territory targeting. + + title: The title of the ad campaign. Must be max 100 characters. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not id: + raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") + return await self._patch( + path_template("/ad_campaigns/{id}", id=id), + body=await async_maybe_transform( + { + "ad_creative_set_ids": ad_creative_set_ids, + "budget": budget, + "budget_type": budget_type, + "config": config, + "daily_budget": daily_budget, + "product_id": product_id, + "target_country_codes": target_country_codes, + "title": title, + }, + ad_campaign_update_params.AdCampaignUpdateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=AdCampaignUpdateResponse, + ) + + def list( + self, + *, + company_id: str, + after: Optional[str] | Omit = omit, + before: Optional[str] | Omit = omit, + created_after: Union[str, datetime, None] | Omit = omit, + created_before: Union[str, datetime, None] | Omit = omit, + first: Optional[int] | Omit = omit, + last: Optional[int] | Omit = omit, + query: Optional[str] | Omit = omit, + status: Optional[ + Literal[ + "active", + "paused", + "inactive", + "stale", + "pending_refund", + "payment_failed", + "draft", + "in_review", + "flagged", + ] + ] + | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> AsyncPaginator[AdCampaignListResponse, AsyncCursorPage[AdCampaignListResponse]]: + """ + Returns a paginated list of ad campaigns for a company, with optional filtering + by status, and creation date. + + Required permissions: + + - `ad_campaign:basic:read` + - `access_pass:basic:read` + + Args: + company_id: The unique identifier of the company to list ad campaigns for. + + after: Returns the elements in the list that come after the specified cursor. + + before: Returns the elements in the list that come before the specified cursor. + + created_after: Only return ad campaigns created after this timestamp. + + created_before: Only return ad campaigns created before this timestamp. + + first: Returns the first _n_ elements from the list. + + last: Returns the last _n_ elements from the list. + + query: Case-insensitive substring match against the campaign title. + + status: The status of an ad campaign. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return self._get_api_list( + "/ad_campaigns", + page=AsyncCursorPage[AdCampaignListResponse], + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "company_id": company_id, + "after": after, + "before": before, + "created_after": created_after, + "created_before": created_before, + "first": first, + "last": last, + "query": query, + "status": status, + }, + ad_campaign_list_params.AdCampaignListParams, + ), + ), + model=AdCampaignListResponse, + ) + + async def pause( + self, + id: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> AdCampaignPauseResponse: + """ + Pauses an ad campaign, optionally until a specific date. + + Required permissions: + + - `ad_campaign:update` + - `access_pass:basic:read` + - `company:balance:read` + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not id: + raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") + return await self._post( + path_template("/ad_campaigns/{id}/pause", id=id), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=AdCampaignPauseResponse, + ) + + async def unpause( + self, + id: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> AdCampaignUnpauseResponse: + """ + Resumes a paused ad campaign. + + Required permissions: + + - `ad_campaign:update` + - `access_pass:basic:read` + - `company:balance:read` + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not id: + raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") + return await self._post( + path_template("/ad_campaigns/{id}/unpause", id=id), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=AdCampaignUnpauseResponse, + ) + + +class AdCampaignsResourceWithRawResponse: + def __init__(self, ad_campaigns: AdCampaignsResource) -> None: + self._ad_campaigns = ad_campaigns + + self.create = to_raw_response_wrapper( + ad_campaigns.create, + ) + self.retrieve = to_raw_response_wrapper( + ad_campaigns.retrieve, + ) + self.update = to_raw_response_wrapper( + ad_campaigns.update, + ) + self.list = to_raw_response_wrapper( + ad_campaigns.list, + ) + self.pause = to_raw_response_wrapper( + ad_campaigns.pause, + ) + self.unpause = to_raw_response_wrapper( + ad_campaigns.unpause, + ) + + +class AsyncAdCampaignsResourceWithRawResponse: + def __init__(self, ad_campaigns: AsyncAdCampaignsResource) -> None: + self._ad_campaigns = ad_campaigns + + self.create = async_to_raw_response_wrapper( + ad_campaigns.create, + ) + self.retrieve = async_to_raw_response_wrapper( + ad_campaigns.retrieve, + ) + self.update = async_to_raw_response_wrapper( + ad_campaigns.update, + ) + self.list = async_to_raw_response_wrapper( + ad_campaigns.list, + ) + self.pause = async_to_raw_response_wrapper( + ad_campaigns.pause, + ) + self.unpause = async_to_raw_response_wrapper( + ad_campaigns.unpause, + ) + + +class AdCampaignsResourceWithStreamingResponse: + def __init__(self, ad_campaigns: AdCampaignsResource) -> None: + self._ad_campaigns = ad_campaigns + + self.create = to_streamed_response_wrapper( + ad_campaigns.create, + ) + self.retrieve = to_streamed_response_wrapper( + ad_campaigns.retrieve, + ) + self.update = to_streamed_response_wrapper( + ad_campaigns.update, + ) + self.list = to_streamed_response_wrapper( + ad_campaigns.list, + ) + self.pause = to_streamed_response_wrapper( + ad_campaigns.pause, + ) + self.unpause = to_streamed_response_wrapper( + ad_campaigns.unpause, + ) + + +class AsyncAdCampaignsResourceWithStreamingResponse: + def __init__(self, ad_campaigns: AsyncAdCampaignsResource) -> None: + self._ad_campaigns = ad_campaigns + + self.create = async_to_streamed_response_wrapper( + ad_campaigns.create, + ) + self.retrieve = async_to_streamed_response_wrapper( + ad_campaigns.retrieve, + ) + self.update = async_to_streamed_response_wrapper( + ad_campaigns.update, + ) + self.list = async_to_streamed_response_wrapper( + ad_campaigns.list, + ) + self.pause = async_to_streamed_response_wrapper( + ad_campaigns.pause, + ) + self.unpause = async_to_streamed_response_wrapper( + ad_campaigns.unpause, + ) diff --git a/src/whop_sdk/resources/ad_groups.py b/src/whop_sdk/resources/ad_groups.py new file mode 100644 index 00000000..0eae840a --- /dev/null +++ b/src/whop_sdk/resources/ad_groups.py @@ -0,0 +1,762 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Union, Optional +from datetime import datetime +from typing_extensions import Literal + +import httpx + +from ..types import ad_group_list_params, ad_group_create_params, ad_group_update_params +from .._types import Body, Omit, Query, Headers, NotGiven, omit, not_given +from .._utils import path_template, maybe_transform, async_maybe_transform +from .._compat import cached_property +from .._resource import SyncAPIResource, AsyncAPIResource +from .._response import ( + to_raw_response_wrapper, + to_streamed_response_wrapper, + async_to_raw_response_wrapper, + async_to_streamed_response_wrapper, +) +from ..pagination import SyncCursorPage, AsyncCursorPage +from .._base_client import AsyncPaginator, make_request_options +from ..types.ad_group_list_response import AdGroupListResponse +from ..types.ad_group_create_response import AdGroupCreateResponse +from ..types.ad_group_delete_response import AdGroupDeleteResponse +from ..types.ad_group_update_response import AdGroupUpdateResponse +from ..types.ad_group_retrieve_response import AdGroupRetrieveResponse + +__all__ = ["AdGroupsResource", "AsyncAdGroupsResource"] + + +class AdGroupsResource(SyncAPIResource): + """Ad groups""" + + @cached_property + def with_raw_response(self) -> AdGroupsResourceWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/whopio/whopsdk-python#accessing-raw-response-data-eg-headers + """ + return AdGroupsResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AdGroupsResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/whopio/whopsdk-python#with_streaming_response + """ + return AdGroupsResourceWithStreamingResponse(self) + + def create( + self, + *, + campaign_id: str, + budget: Optional[float] | Omit = omit, + budget_type: Optional[Literal["daily", "lifetime"]] | Omit = omit, + config: Optional[ad_group_create_params.Config] | Omit = omit, + daily_budget: Optional[float] | Omit = omit, + name: Optional[str] | Omit = omit, + platform_config: Optional[ad_group_create_params.PlatformConfig] | Omit = omit, + status: Optional[Literal["active", "paused", "inactive", "in_review", "rejected", "flagged"]] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> AdGroupCreateResponse: + """ + Creates a new ad group within a campaign. + + Required permissions: + + - `ad_campaign:create` + - `ad_campaign:basic:read` + + Args: + campaign_id: The ad campaign to create this ad group within. + + budget: Budget amount in dollars. + + budget_type: The budget type for an ad campaign or ad group. + + config: Unified ad group configuration (bidding, optimization, targeting). + + daily_budget: Daily budget in dollars. + + name: Human-readable ad group name. + + platform_config: Platform-specific ad group configuration. + + status: The status of an external ad group. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return self._post( + "/ad_groups", + body=maybe_transform( + { + "campaign_id": campaign_id, + "budget": budget, + "budget_type": budget_type, + "config": config, + "daily_budget": daily_budget, + "name": name, + "platform_config": platform_config, + "status": status, + }, + ad_group_create_params.AdGroupCreateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=AdGroupCreateResponse, + ) + + def retrieve( + self, + id: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> AdGroupRetrieveResponse: + """ + Retrieves a single ad group by its unique identifier. + + Required permissions: + + - `ad_campaign:basic:read` + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not id: + raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") + return self._get( + path_template("/ad_groups/{id}", id=id), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=AdGroupRetrieveResponse, + ) + + def update( + self, + id: str, + *, + budget: Optional[float] | Omit = omit, + budget_type: Optional[Literal["daily", "lifetime"]] | Omit = omit, + config: Optional[ad_group_update_params.Config] | Omit = omit, + daily_budget: Optional[float] | Omit = omit, + name: Optional[str] | Omit = omit, + platform_config: Optional[ad_group_update_params.PlatformConfig] | Omit = omit, + status: Optional[Literal["active", "paused", "inactive", "in_review", "rejected", "flagged"]] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> AdGroupUpdateResponse: + """ + Updates an existing ad group. + + Required permissions: + + - `ad_campaign:update` + - `ad_campaign:basic:read` + + Args: + budget: Budget amount in dollars. + + budget_type: The budget type for an ad campaign or ad group. + + config: Unified ad group configuration (bidding, optimization, targeting). + + daily_budget: Daily budget in dollars. + + name: Human-readable ad group name. + + platform_config: Platform-specific ad group configuration. + + status: The status of an external ad group. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not id: + raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") + return self._patch( + path_template("/ad_groups/{id}", id=id), + body=maybe_transform( + { + "budget": budget, + "budget_type": budget_type, + "config": config, + "daily_budget": daily_budget, + "name": name, + "platform_config": platform_config, + "status": status, + }, + ad_group_update_params.AdGroupUpdateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=AdGroupUpdateResponse, + ) + + def list( + self, + *, + after: Optional[str] | Omit = omit, + before: Optional[str] | Omit = omit, + campaign_id: Optional[str] | Omit = omit, + company_id: Optional[str] | Omit = omit, + created_after: Union[str, datetime, None] | Omit = omit, + created_before: Union[str, datetime, None] | Omit = omit, + first: Optional[int] | Omit = omit, + last: Optional[int] | Omit = omit, + query: Optional[str] | Omit = omit, + status: Optional[Literal["active", "paused", "inactive", "in_review", "rejected", "flagged"]] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> SyncCursorPage[AdGroupListResponse]: + """ + Returns a paginated list of ad groups scoped by campaign or company, with + optional filtering by status and creation date. + + Required permissions: + + - `ad_campaign:basic:read` + + Args: + after: Returns the elements in the list that come after the specified cursor. + + before: Returns the elements in the list that come before the specified cursor. + + campaign_id: Filter by campaign. Provide exactly one of campaign_id or company_id. + + company_id: Filter by company. Provide exactly one of campaign_id or company_id. + + created_after: Only return ad groups created after this timestamp. + + created_before: Only return ad groups created before this timestamp. + + first: Returns the first _n_ elements from the list. + + last: Returns the last _n_ elements from the list. + + query: Case-insensitive substring match against the ad group name. + + status: The status of an external ad group. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return self._get_api_list( + "/ad_groups", + page=SyncCursorPage[AdGroupListResponse], + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "after": after, + "before": before, + "campaign_id": campaign_id, + "company_id": company_id, + "created_after": created_after, + "created_before": created_before, + "first": first, + "last": last, + "query": query, + "status": status, + }, + ad_group_list_params.AdGroupListParams, + ), + ), + model=AdGroupListResponse, + ) + + def delete( + self, + id: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> AdGroupDeleteResponse: + """ + Soft-deletes an ad group. + + Required permissions: + + - `ad_campaign:delete` + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not id: + raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") + return self._delete( + path_template("/ad_groups/{id}", id=id), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=AdGroupDeleteResponse, + ) + + +class AsyncAdGroupsResource(AsyncAPIResource): + """Ad groups""" + + @cached_property + def with_raw_response(self) -> AsyncAdGroupsResourceWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/whopio/whopsdk-python#accessing-raw-response-data-eg-headers + """ + return AsyncAdGroupsResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncAdGroupsResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/whopio/whopsdk-python#with_streaming_response + """ + return AsyncAdGroupsResourceWithStreamingResponse(self) + + async def create( + self, + *, + campaign_id: str, + budget: Optional[float] | Omit = omit, + budget_type: Optional[Literal["daily", "lifetime"]] | Omit = omit, + config: Optional[ad_group_create_params.Config] | Omit = omit, + daily_budget: Optional[float] | Omit = omit, + name: Optional[str] | Omit = omit, + platform_config: Optional[ad_group_create_params.PlatformConfig] | Omit = omit, + status: Optional[Literal["active", "paused", "inactive", "in_review", "rejected", "flagged"]] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> AdGroupCreateResponse: + """ + Creates a new ad group within a campaign. + + Required permissions: + + - `ad_campaign:create` + - `ad_campaign:basic:read` + + Args: + campaign_id: The ad campaign to create this ad group within. + + budget: Budget amount in dollars. + + budget_type: The budget type for an ad campaign or ad group. + + config: Unified ad group configuration (bidding, optimization, targeting). + + daily_budget: Daily budget in dollars. + + name: Human-readable ad group name. + + platform_config: Platform-specific ad group configuration. + + status: The status of an external ad group. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return await self._post( + "/ad_groups", + body=await async_maybe_transform( + { + "campaign_id": campaign_id, + "budget": budget, + "budget_type": budget_type, + "config": config, + "daily_budget": daily_budget, + "name": name, + "platform_config": platform_config, + "status": status, + }, + ad_group_create_params.AdGroupCreateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=AdGroupCreateResponse, + ) + + async def retrieve( + self, + id: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> AdGroupRetrieveResponse: + """ + Retrieves a single ad group by its unique identifier. + + Required permissions: + + - `ad_campaign:basic:read` + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not id: + raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") + return await self._get( + path_template("/ad_groups/{id}", id=id), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=AdGroupRetrieveResponse, + ) + + async def update( + self, + id: str, + *, + budget: Optional[float] | Omit = omit, + budget_type: Optional[Literal["daily", "lifetime"]] | Omit = omit, + config: Optional[ad_group_update_params.Config] | Omit = omit, + daily_budget: Optional[float] | Omit = omit, + name: Optional[str] | Omit = omit, + platform_config: Optional[ad_group_update_params.PlatformConfig] | Omit = omit, + status: Optional[Literal["active", "paused", "inactive", "in_review", "rejected", "flagged"]] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> AdGroupUpdateResponse: + """ + Updates an existing ad group. + + Required permissions: + + - `ad_campaign:update` + - `ad_campaign:basic:read` + + Args: + budget: Budget amount in dollars. + + budget_type: The budget type for an ad campaign or ad group. + + config: Unified ad group configuration (bidding, optimization, targeting). + + daily_budget: Daily budget in dollars. + + name: Human-readable ad group name. + + platform_config: Platform-specific ad group configuration. + + status: The status of an external ad group. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not id: + raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") + return await self._patch( + path_template("/ad_groups/{id}", id=id), + body=await async_maybe_transform( + { + "budget": budget, + "budget_type": budget_type, + "config": config, + "daily_budget": daily_budget, + "name": name, + "platform_config": platform_config, + "status": status, + }, + ad_group_update_params.AdGroupUpdateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=AdGroupUpdateResponse, + ) + + def list( + self, + *, + after: Optional[str] | Omit = omit, + before: Optional[str] | Omit = omit, + campaign_id: Optional[str] | Omit = omit, + company_id: Optional[str] | Omit = omit, + created_after: Union[str, datetime, None] | Omit = omit, + created_before: Union[str, datetime, None] | Omit = omit, + first: Optional[int] | Omit = omit, + last: Optional[int] | Omit = omit, + query: Optional[str] | Omit = omit, + status: Optional[Literal["active", "paused", "inactive", "in_review", "rejected", "flagged"]] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> AsyncPaginator[AdGroupListResponse, AsyncCursorPage[AdGroupListResponse]]: + """ + Returns a paginated list of ad groups scoped by campaign or company, with + optional filtering by status and creation date. + + Required permissions: + + - `ad_campaign:basic:read` + + Args: + after: Returns the elements in the list that come after the specified cursor. + + before: Returns the elements in the list that come before the specified cursor. + + campaign_id: Filter by campaign. Provide exactly one of campaign_id or company_id. + + company_id: Filter by company. Provide exactly one of campaign_id or company_id. + + created_after: Only return ad groups created after this timestamp. + + created_before: Only return ad groups created before this timestamp. + + first: Returns the first _n_ elements from the list. + + last: Returns the last _n_ elements from the list. + + query: Case-insensitive substring match against the ad group name. + + status: The status of an external ad group. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return self._get_api_list( + "/ad_groups", + page=AsyncCursorPage[AdGroupListResponse], + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "after": after, + "before": before, + "campaign_id": campaign_id, + "company_id": company_id, + "created_after": created_after, + "created_before": created_before, + "first": first, + "last": last, + "query": query, + "status": status, + }, + ad_group_list_params.AdGroupListParams, + ), + ), + model=AdGroupListResponse, + ) + + async def delete( + self, + id: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> AdGroupDeleteResponse: + """ + Soft-deletes an ad group. + + Required permissions: + + - `ad_campaign:delete` + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not id: + raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") + return await self._delete( + path_template("/ad_groups/{id}", id=id), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=AdGroupDeleteResponse, + ) + + +class AdGroupsResourceWithRawResponse: + def __init__(self, ad_groups: AdGroupsResource) -> None: + self._ad_groups = ad_groups + + self.create = to_raw_response_wrapper( + ad_groups.create, + ) + self.retrieve = to_raw_response_wrapper( + ad_groups.retrieve, + ) + self.update = to_raw_response_wrapper( + ad_groups.update, + ) + self.list = to_raw_response_wrapper( + ad_groups.list, + ) + self.delete = to_raw_response_wrapper( + ad_groups.delete, + ) + + +class AsyncAdGroupsResourceWithRawResponse: + def __init__(self, ad_groups: AsyncAdGroupsResource) -> None: + self._ad_groups = ad_groups + + self.create = async_to_raw_response_wrapper( + ad_groups.create, + ) + self.retrieve = async_to_raw_response_wrapper( + ad_groups.retrieve, + ) + self.update = async_to_raw_response_wrapper( + ad_groups.update, + ) + self.list = async_to_raw_response_wrapper( + ad_groups.list, + ) + self.delete = async_to_raw_response_wrapper( + ad_groups.delete, + ) + + +class AdGroupsResourceWithStreamingResponse: + def __init__(self, ad_groups: AdGroupsResource) -> None: + self._ad_groups = ad_groups + + self.create = to_streamed_response_wrapper( + ad_groups.create, + ) + self.retrieve = to_streamed_response_wrapper( + ad_groups.retrieve, + ) + self.update = to_streamed_response_wrapper( + ad_groups.update, + ) + self.list = to_streamed_response_wrapper( + ad_groups.list, + ) + self.delete = to_streamed_response_wrapper( + ad_groups.delete, + ) + + +class AsyncAdGroupsResourceWithStreamingResponse: + def __init__(self, ad_groups: AsyncAdGroupsResource) -> None: + self._ad_groups = ad_groups + + self.create = async_to_streamed_response_wrapper( + ad_groups.create, + ) + self.retrieve = async_to_streamed_response_wrapper( + ad_groups.retrieve, + ) + self.update = async_to_streamed_response_wrapper( + ad_groups.update, + ) + self.list = async_to_streamed_response_wrapper( + ad_groups.list, + ) + self.delete = async_to_streamed_response_wrapper( + ad_groups.delete, + ) diff --git a/src/whop_sdk/resources/ads.py b/src/whop_sdk/resources/ads.py new file mode 100644 index 00000000..466957e6 --- /dev/null +++ b/src/whop_sdk/resources/ads.py @@ -0,0 +1,510 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Union, Optional +from datetime import datetime +from typing_extensions import Literal + +import httpx + +from ..types import ad_list_params, ad_create_params +from .._types import Body, Omit, Query, Headers, NotGiven, omit, not_given +from .._utils import path_template, maybe_transform, async_maybe_transform +from .._compat import cached_property +from .._resource import SyncAPIResource, AsyncAPIResource +from .._response import ( + to_raw_response_wrapper, + to_streamed_response_wrapper, + async_to_raw_response_wrapper, + async_to_streamed_response_wrapper, +) +from ..pagination import SyncCursorPage, AsyncCursorPage +from .._base_client import AsyncPaginator, make_request_options +from ..types.ad_list_response import AdListResponse +from ..types.ad_create_response import AdCreateResponse +from ..types.ad_retrieve_response import AdRetrieveResponse + +__all__ = ["AdsResource", "AsyncAdsResource"] + + +class AdsResource(SyncAPIResource): + """Ads""" + + @cached_property + def with_raw_response(self) -> AdsResourceWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/whopio/whopsdk-python#accessing-raw-response-data-eg-headers + """ + return AdsResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AdsResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/whopio/whopsdk-python#with_streaming_response + """ + return AdsResourceWithStreamingResponse(self) + + def create( + self, + *, + ad_group_id: str, + creative_set_id: Optional[str] | Omit = omit, + existing_instagram_media_id: Optional[str] | Omit = omit, + existing_post_id: Optional[str] | Omit = omit, + platform_config: Optional[ad_create_params.PlatformConfig] | Omit = omit, + status: Optional[Literal["active", "paused", "inactive", "in_review", "rejected", "flagged"]] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> AdCreateResponse: + """ + Create an ad within an ad group. + + Required permissions: + + - `ad_campaign:create` + + Args: + ad_group_id: The unique identifier of the ad group to create this ad in. + + creative_set_id: The unique identifier of the creative set to use. + + existing_instagram_media_id: ID of an existing Instagram media item to use as the ad creative (instead of a + creative set or Facebook post). + + existing_post_id: ID of an existing Facebook post to use as the ad creative (instead of a creative + set). + + platform_config: Platform-specific configuration. Must match the campaign platform. + + status: The status of an external ad. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return self._post( + "/ads", + body=maybe_transform( + { + "ad_group_id": ad_group_id, + "creative_set_id": creative_set_id, + "existing_instagram_media_id": existing_instagram_media_id, + "existing_post_id": existing_post_id, + "platform_config": platform_config, + "status": status, + }, + ad_create_params.AdCreateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=AdCreateResponse, + ) + + def retrieve( + self, + id: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> AdRetrieveResponse: + """ + Retrieve an ad by its unique identifier. + + Required permissions: + + - `ad_campaign:basic:read` + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not id: + raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") + return self._get( + path_template("/ads/{id}", id=id), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=AdRetrieveResponse, + ) + + def list( + self, + *, + ad_group_id: Optional[str] | Omit = omit, + after: Optional[str] | Omit = omit, + before: Optional[str] | Omit = omit, + campaign_id: Optional[str] | Omit = omit, + company_id: Optional[str] | Omit = omit, + created_after: Union[str, datetime, None] | Omit = omit, + created_before: Union[str, datetime, None] | Omit = omit, + first: Optional[int] | Omit = omit, + last: Optional[int] | Omit = omit, + status: Optional[Literal["active", "paused", "inactive", "in_review", "rejected", "flagged"]] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> SyncCursorPage[AdListResponse]: + """ + List ads scoped by ad group, campaign, or company. + + Required permissions: + + - `ad_campaign:basic:read` + + Args: + ad_group_id: Filter by ad group. Provide exactly one of ad_group_id, campaign_id, or + company_id. + + after: Returns the elements in the list that come after the specified cursor. + + before: Returns the elements in the list that come before the specified cursor. + + campaign_id: Filter by campaign. Provide exactly one of ad_group_id, campaign_id, or + company_id. + + company_id: Filter by company. Provide exactly one of ad_group_id, campaign_id, or + company_id. + + created_after: Only return ads created after this timestamp. + + created_before: Only return ads created before this timestamp. + + first: Returns the first _n_ elements from the list. + + last: Returns the last _n_ elements from the list. + + status: The status of an external ad. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return self._get_api_list( + "/ads", + page=SyncCursorPage[AdListResponse], + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "ad_group_id": ad_group_id, + "after": after, + "before": before, + "campaign_id": campaign_id, + "company_id": company_id, + "created_after": created_after, + "created_before": created_before, + "first": first, + "last": last, + "status": status, + }, + ad_list_params.AdListParams, + ), + ), + model=AdListResponse, + ) + + +class AsyncAdsResource(AsyncAPIResource): + """Ads""" + + @cached_property + def with_raw_response(self) -> AsyncAdsResourceWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/whopio/whopsdk-python#accessing-raw-response-data-eg-headers + """ + return AsyncAdsResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncAdsResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/whopio/whopsdk-python#with_streaming_response + """ + return AsyncAdsResourceWithStreamingResponse(self) + + async def create( + self, + *, + ad_group_id: str, + creative_set_id: Optional[str] | Omit = omit, + existing_instagram_media_id: Optional[str] | Omit = omit, + existing_post_id: Optional[str] | Omit = omit, + platform_config: Optional[ad_create_params.PlatformConfig] | Omit = omit, + status: Optional[Literal["active", "paused", "inactive", "in_review", "rejected", "flagged"]] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> AdCreateResponse: + """ + Create an ad within an ad group. + + Required permissions: + + - `ad_campaign:create` + + Args: + ad_group_id: The unique identifier of the ad group to create this ad in. + + creative_set_id: The unique identifier of the creative set to use. + + existing_instagram_media_id: ID of an existing Instagram media item to use as the ad creative (instead of a + creative set or Facebook post). + + existing_post_id: ID of an existing Facebook post to use as the ad creative (instead of a creative + set). + + platform_config: Platform-specific configuration. Must match the campaign platform. + + status: The status of an external ad. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return await self._post( + "/ads", + body=await async_maybe_transform( + { + "ad_group_id": ad_group_id, + "creative_set_id": creative_set_id, + "existing_instagram_media_id": existing_instagram_media_id, + "existing_post_id": existing_post_id, + "platform_config": platform_config, + "status": status, + }, + ad_create_params.AdCreateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=AdCreateResponse, + ) + + async def retrieve( + self, + id: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> AdRetrieveResponse: + """ + Retrieve an ad by its unique identifier. + + Required permissions: + + - `ad_campaign:basic:read` + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not id: + raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") + return await self._get( + path_template("/ads/{id}", id=id), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=AdRetrieveResponse, + ) + + def list( + self, + *, + ad_group_id: Optional[str] | Omit = omit, + after: Optional[str] | Omit = omit, + before: Optional[str] | Omit = omit, + campaign_id: Optional[str] | Omit = omit, + company_id: Optional[str] | Omit = omit, + created_after: Union[str, datetime, None] | Omit = omit, + created_before: Union[str, datetime, None] | Omit = omit, + first: Optional[int] | Omit = omit, + last: Optional[int] | Omit = omit, + status: Optional[Literal["active", "paused", "inactive", "in_review", "rejected", "flagged"]] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> AsyncPaginator[AdListResponse, AsyncCursorPage[AdListResponse]]: + """ + List ads scoped by ad group, campaign, or company. + + Required permissions: + + - `ad_campaign:basic:read` + + Args: + ad_group_id: Filter by ad group. Provide exactly one of ad_group_id, campaign_id, or + company_id. + + after: Returns the elements in the list that come after the specified cursor. + + before: Returns the elements in the list that come before the specified cursor. + + campaign_id: Filter by campaign. Provide exactly one of ad_group_id, campaign_id, or + company_id. + + company_id: Filter by company. Provide exactly one of ad_group_id, campaign_id, or + company_id. + + created_after: Only return ads created after this timestamp. + + created_before: Only return ads created before this timestamp. + + first: Returns the first _n_ elements from the list. + + last: Returns the last _n_ elements from the list. + + status: The status of an external ad. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return self._get_api_list( + "/ads", + page=AsyncCursorPage[AdListResponse], + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "ad_group_id": ad_group_id, + "after": after, + "before": before, + "campaign_id": campaign_id, + "company_id": company_id, + "created_after": created_after, + "created_before": created_before, + "first": first, + "last": last, + "status": status, + }, + ad_list_params.AdListParams, + ), + ), + model=AdListResponse, + ) + + +class AdsResourceWithRawResponse: + def __init__(self, ads: AdsResource) -> None: + self._ads = ads + + self.create = to_raw_response_wrapper( + ads.create, + ) + self.retrieve = to_raw_response_wrapper( + ads.retrieve, + ) + self.list = to_raw_response_wrapper( + ads.list, + ) + + +class AsyncAdsResourceWithRawResponse: + def __init__(self, ads: AsyncAdsResource) -> None: + self._ads = ads + + self.create = async_to_raw_response_wrapper( + ads.create, + ) + self.retrieve = async_to_raw_response_wrapper( + ads.retrieve, + ) + self.list = async_to_raw_response_wrapper( + ads.list, + ) + + +class AdsResourceWithStreamingResponse: + def __init__(self, ads: AdsResource) -> None: + self._ads = ads + + self.create = to_streamed_response_wrapper( + ads.create, + ) + self.retrieve = to_streamed_response_wrapper( + ads.retrieve, + ) + self.list = to_streamed_response_wrapper( + ads.list, + ) + + +class AsyncAdsResourceWithStreamingResponse: + def __init__(self, ads: AsyncAdsResource) -> None: + self._ads = ads + + self.create = async_to_streamed_response_wrapper( + ads.create, + ) + self.retrieve = async_to_streamed_response_wrapper( + ads.retrieve, + ) + self.list = async_to_streamed_response_wrapper( + ads.list, + ) diff --git a/src/whop_sdk/resources/bounties.py b/src/whop_sdk/resources/bounties.py new file mode 100644 index 00000000..53dc8987 --- /dev/null +++ b/src/whop_sdk/resources/bounties.py @@ -0,0 +1,495 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Optional +from typing_extensions import Literal + +import httpx + +from ..types import bounty_list_params, bounty_create_params +from .._types import Body, Omit, Query, Headers, NotGiven, SequenceNotStr, omit, not_given +from .._utils import path_template, maybe_transform, async_maybe_transform +from .._compat import cached_property +from .._resource import SyncAPIResource, AsyncAPIResource +from .._response import ( + to_raw_response_wrapper, + to_streamed_response_wrapper, + async_to_raw_response_wrapper, + async_to_streamed_response_wrapper, +) +from ..pagination import SyncCursorPage, AsyncCursorPage +from .._base_client import AsyncPaginator, make_request_options +from ..types.shared.currency import Currency +from ..types.shared.direction import Direction +from ..types.bounty_list_response import BountyListResponse +from ..types.bounty_create_response import BountyCreateResponse +from ..types.bounty_retrieve_response import BountyRetrieveResponse + +__all__ = ["BountiesResource", "AsyncBountiesResource"] + + +class BountiesResource(SyncAPIResource): + """Bounties""" + + @cached_property + def with_raw_response(self) -> BountiesResourceWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/whopio/whopsdk-python#accessing-raw-response-data-eg-headers + """ + return BountiesResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> BountiesResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/whopio/whopsdk-python#with_streaming_response + """ + return BountiesResourceWithStreamingResponse(self) + + def create( + self, + *, + base_unit_amount: float, + currency: Currency, + description: str, + title: str, + accepted_submissions_limit: Optional[int] | Omit = omit, + allowed_country_codes: Optional[SequenceNotStr[str]] | Omit = omit, + experience_id: Optional[str] | Omit = omit, + origin_account_id: Optional[str] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> BountyCreateResponse: + """ + Create a new workforce bounty by funding a dedicated bounty pool. + + Required permissions: + + - `bounty:create` + + Args: + base_unit_amount: The amount paid to each approved submission. The total bounty pool funded is + this amount times accepted_submissions_limit. + + currency: The currency for the bounty pool funding amount. + + description: The description of the bounty. + + title: The title of the bounty. + + accepted_submissions_limit: The number of submissions that can be approved before the bounty closes. + Defaults to 1. + + allowed_country_codes: The ISO3166 country codes where this bounty should be visible. Empty means + globally visible. + + experience_id: An optional experience to scope the bounty to. + + origin_account_id: The user (user*\\**) or company (biz*\\**) tag whose balance funds this bounty pool. + Defaults to the requester's personal balance when omitted. The requester must be + the user themself or an owner/admin of the company. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return self._post( + "/bounties", + body=maybe_transform( + { + "base_unit_amount": base_unit_amount, + "currency": currency, + "description": description, + "title": title, + "accepted_submissions_limit": accepted_submissions_limit, + "allowed_country_codes": allowed_country_codes, + "experience_id": experience_id, + "origin_account_id": origin_account_id, + }, + bounty_create_params.BountyCreateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=BountyCreateResponse, + ) + + def retrieve( + self, + id: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> BountyRetrieveResponse: + """ + Retrieves a workforce bounty for the current authenticated user. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not id: + raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") + return self._get( + path_template("/bounties/{id}", id=id), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=BountyRetrieveResponse, + ) + + def list( + self, + *, + after: Optional[str] | Omit = omit, + before: Optional[str] | Omit = omit, + direction: Optional[Direction] | Omit = omit, + experience_id: Optional[str] | Omit = omit, + first: Optional[int] | Omit = omit, + last: Optional[int] | Omit = omit, + status: Optional[Literal["published", "archived"]] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> SyncCursorPage[BountyListResponse]: + """Returns a paginated list of workforce bounties. + + When experienceId is provided, + returns bounties scoped to that experience. When omitted, returns bounties with + no experience. + + Args: + after: Returns the elements in the list that come after the specified cursor. + + before: Returns the elements in the list that come before the specified cursor. + + direction: The direction of the sort. + + experience_id: The experience to list bounties for. When omitted, returns bounties with no + experience. + + first: Returns the first _n_ elements from the list. + + last: Returns the last _n_ elements from the list. + + status: The available bounty statuses to choose from. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return self._get_api_list( + "/bounties", + page=SyncCursorPage[BountyListResponse], + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "after": after, + "before": before, + "direction": direction, + "experience_id": experience_id, + "first": first, + "last": last, + "status": status, + }, + bounty_list_params.BountyListParams, + ), + ), + model=BountyListResponse, + ) + + +class AsyncBountiesResource(AsyncAPIResource): + """Bounties""" + + @cached_property + def with_raw_response(self) -> AsyncBountiesResourceWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/whopio/whopsdk-python#accessing-raw-response-data-eg-headers + """ + return AsyncBountiesResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncBountiesResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/whopio/whopsdk-python#with_streaming_response + """ + return AsyncBountiesResourceWithStreamingResponse(self) + + async def create( + self, + *, + base_unit_amount: float, + currency: Currency, + description: str, + title: str, + accepted_submissions_limit: Optional[int] | Omit = omit, + allowed_country_codes: Optional[SequenceNotStr[str]] | Omit = omit, + experience_id: Optional[str] | Omit = omit, + origin_account_id: Optional[str] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> BountyCreateResponse: + """ + Create a new workforce bounty by funding a dedicated bounty pool. + + Required permissions: + + - `bounty:create` + + Args: + base_unit_amount: The amount paid to each approved submission. The total bounty pool funded is + this amount times accepted_submissions_limit. + + currency: The currency for the bounty pool funding amount. + + description: The description of the bounty. + + title: The title of the bounty. + + accepted_submissions_limit: The number of submissions that can be approved before the bounty closes. + Defaults to 1. + + allowed_country_codes: The ISO3166 country codes where this bounty should be visible. Empty means + globally visible. + + experience_id: An optional experience to scope the bounty to. + + origin_account_id: The user (user*\\**) or company (biz*\\**) tag whose balance funds this bounty pool. + Defaults to the requester's personal balance when omitted. The requester must be + the user themself or an owner/admin of the company. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return await self._post( + "/bounties", + body=await async_maybe_transform( + { + "base_unit_amount": base_unit_amount, + "currency": currency, + "description": description, + "title": title, + "accepted_submissions_limit": accepted_submissions_limit, + "allowed_country_codes": allowed_country_codes, + "experience_id": experience_id, + "origin_account_id": origin_account_id, + }, + bounty_create_params.BountyCreateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=BountyCreateResponse, + ) + + async def retrieve( + self, + id: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> BountyRetrieveResponse: + """ + Retrieves a workforce bounty for the current authenticated user. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not id: + raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") + return await self._get( + path_template("/bounties/{id}", id=id), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=BountyRetrieveResponse, + ) + + def list( + self, + *, + after: Optional[str] | Omit = omit, + before: Optional[str] | Omit = omit, + direction: Optional[Direction] | Omit = omit, + experience_id: Optional[str] | Omit = omit, + first: Optional[int] | Omit = omit, + last: Optional[int] | Omit = omit, + status: Optional[Literal["published", "archived"]] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> AsyncPaginator[BountyListResponse, AsyncCursorPage[BountyListResponse]]: + """Returns a paginated list of workforce bounties. + + When experienceId is provided, + returns bounties scoped to that experience. When omitted, returns bounties with + no experience. + + Args: + after: Returns the elements in the list that come after the specified cursor. + + before: Returns the elements in the list that come before the specified cursor. + + direction: The direction of the sort. + + experience_id: The experience to list bounties for. When omitted, returns bounties with no + experience. + + first: Returns the first _n_ elements from the list. + + last: Returns the last _n_ elements from the list. + + status: The available bounty statuses to choose from. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return self._get_api_list( + "/bounties", + page=AsyncCursorPage[BountyListResponse], + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "after": after, + "before": before, + "direction": direction, + "experience_id": experience_id, + "first": first, + "last": last, + "status": status, + }, + bounty_list_params.BountyListParams, + ), + ), + model=BountyListResponse, + ) + + +class BountiesResourceWithRawResponse: + def __init__(self, bounties: BountiesResource) -> None: + self._bounties = bounties + + self.create = to_raw_response_wrapper( + bounties.create, + ) + self.retrieve = to_raw_response_wrapper( + bounties.retrieve, + ) + self.list = to_raw_response_wrapper( + bounties.list, + ) + + +class AsyncBountiesResourceWithRawResponse: + def __init__(self, bounties: AsyncBountiesResource) -> None: + self._bounties = bounties + + self.create = async_to_raw_response_wrapper( + bounties.create, + ) + self.retrieve = async_to_raw_response_wrapper( + bounties.retrieve, + ) + self.list = async_to_raw_response_wrapper( + bounties.list, + ) + + +class BountiesResourceWithStreamingResponse: + def __init__(self, bounties: BountiesResource) -> None: + self._bounties = bounties + + self.create = to_streamed_response_wrapper( + bounties.create, + ) + self.retrieve = to_streamed_response_wrapper( + bounties.retrieve, + ) + self.list = to_streamed_response_wrapper( + bounties.list, + ) + + +class AsyncBountiesResourceWithStreamingResponse: + def __init__(self, bounties: AsyncBountiesResource) -> None: + self._bounties = bounties + + self.create = async_to_streamed_response_wrapper( + bounties.create, + ) + self.retrieve = async_to_streamed_response_wrapper( + bounties.retrieve, + ) + self.list = async_to_streamed_response_wrapper( + bounties.list, + ) diff --git a/src/whop_sdk/resources/companies.py b/src/whop_sdk/resources/companies.py index 38507758..a8a824d7 100644 --- a/src/whop_sdk/resources/companies.py +++ b/src/whop_sdk/resources/companies.py @@ -4,10 +4,16 @@ from typing import Dict, Union, Optional from datetime import datetime +from typing_extensions import Literal import httpx -from ..types import company_list_params, company_create_params, company_update_params +from ..types import ( + company_list_params, + company_create_params, + company_update_params, + company_create_api_key_params, +) from .._types import Body, Omit, Query, Headers, NotGiven, omit, not_given from .._utils import path_template, maybe_transform, async_maybe_transform from .._compat import cached_property @@ -23,6 +29,7 @@ from ..types.shared.company import Company from ..types.shared.direction import Direction from ..types.company_list_response import CompanyListResponse +from ..types.company_create_api_key_response import CompanyCreateAPIKeyResponse __all__ = ["CompaniesResource", "AsyncCompaniesResource"] @@ -330,6 +337,63 @@ def list( model=CompanyListResponse, ) + def create_api_key( + self, + parent_company_id: str, + *, + child_company_id: str, + name: Optional[str] | Omit = omit, + permissions: Optional[Iterable[company_create_api_key_params.Permission]] | Omit = omit, + role: Optional[Literal["owner", "admin", "moderator", "sales_manager", "advertiser"]] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> CompanyCreateAPIKeyResponse: + """ + Create an API key for a connected account (child company) owned by a parent + company. + + Args: + child_company_id: The unique identifier of the connected account to create the API key for (e.g. + 'biz_xxx'). + + name: A human-readable name for the API key, such as 'Production API Key'. + + permissions: Granular permission statements defining which actions this API key can perform. + Either permissions or role must be provided. + + role: The different system roles that can be assigned. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not parent_company_id: + raise ValueError(f"Expected a non-empty value for `parent_company_id` but received {parent_company_id!r}") + return self._post( + path_template("/companies/{parent_company_id}/api_keys", parent_company_id=parent_company_id), + body=maybe_transform( + { + "child_company_id": child_company_id, + "name": name, + "permissions": permissions, + "role": role, + }, + company_create_api_key_params.CompanyCreateAPIKeyParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=CompanyCreateAPIKeyResponse, + ) + class AsyncCompaniesResource(AsyncAPIResource): @cached_property @@ -634,6 +698,63 @@ def list( model=CompanyListResponse, ) + async def create_api_key( + self, + parent_company_id: str, + *, + child_company_id: str, + name: Optional[str] | Omit = omit, + permissions: Optional[Iterable[company_create_api_key_params.Permission]] | Omit = omit, + role: Optional[Literal["owner", "admin", "moderator", "sales_manager", "advertiser"]] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> CompanyCreateAPIKeyResponse: + """ + Create an API key for a connected account (child company) owned by a parent + company. + + Args: + child_company_id: The unique identifier of the connected account to create the API key for (e.g. + 'biz_xxx'). + + name: A human-readable name for the API key, such as 'Production API Key'. + + permissions: Granular permission statements defining which actions this API key can perform. + Either permissions or role must be provided. + + role: The different system roles that can be assigned. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not parent_company_id: + raise ValueError(f"Expected a non-empty value for `parent_company_id` but received {parent_company_id!r}") + return await self._post( + path_template("/companies/{parent_company_id}/api_keys", parent_company_id=parent_company_id), + body=await async_maybe_transform( + { + "child_company_id": child_company_id, + "name": name, + "permissions": permissions, + "role": role, + }, + company_create_api_key_params.CompanyCreateAPIKeyParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=CompanyCreateAPIKeyResponse, + ) + class CompaniesResourceWithRawResponse: def __init__(self, companies: CompaniesResource) -> None: @@ -651,6 +772,9 @@ def __init__(self, companies: CompaniesResource) -> None: self.list = to_raw_response_wrapper( companies.list, ) + self.create_api_key = to_raw_response_wrapper( + companies.create_api_key, + ) class AsyncCompaniesResourceWithRawResponse: @@ -669,6 +793,9 @@ def __init__(self, companies: AsyncCompaniesResource) -> None: self.list = async_to_raw_response_wrapper( companies.list, ) + self.create_api_key = async_to_raw_response_wrapper( + companies.create_api_key, + ) class CompaniesResourceWithStreamingResponse: @@ -687,6 +814,9 @@ def __init__(self, companies: CompaniesResource) -> None: self.list = to_streamed_response_wrapper( companies.list, ) + self.create_api_key = to_streamed_response_wrapper( + companies.create_api_key, + ) class AsyncCompaniesResourceWithStreamingResponse: @@ -705,3 +835,6 @@ def __init__(self, companies: AsyncCompaniesResource) -> None: self.list = async_to_streamed_response_wrapper( companies.list, ) + self.create_api_key = async_to_streamed_response_wrapper( + companies.create_api_key, + ) diff --git a/src/whop_sdk/resources/conversions.py b/src/whop_sdk/resources/conversions.py new file mode 100644 index 00000000..a3dd6259 --- /dev/null +++ b/src/whop_sdk/resources/conversions.py @@ -0,0 +1,324 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Union, Optional +from datetime import datetime +from typing_extensions import Literal + +import httpx + +from ..types import conversion_create_params +from .._types import Body, Omit, Query, Headers, NotGiven, omit, not_given +from .._utils import maybe_transform, async_maybe_transform +from .._compat import cached_property +from .._resource import SyncAPIResource, AsyncAPIResource +from .._response import ( + to_raw_response_wrapper, + to_streamed_response_wrapper, + async_to_raw_response_wrapper, + async_to_streamed_response_wrapper, +) +from .._base_client import make_request_options +from ..types.shared.currency import Currency +from ..types.conversion_create_response import ConversionCreateResponse + +__all__ = ["ConversionsResource", "AsyncConversionsResource"] + + +class ConversionsResource(SyncAPIResource): + """Conversions""" + + @cached_property + def with_raw_response(self) -> ConversionsResourceWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/whopio/whopsdk-python#accessing-raw-response-data-eg-headers + """ + return ConversionsResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> ConversionsResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/whopio/whopsdk-python#with_streaming_response + """ + return ConversionsResourceWithStreamingResponse(self) + + def create( + self, + *, + company_id: str, + event_name: Literal["lead", "submit_application", "contact", "complete_registration", "schedule", "custom"], + action_source: Optional[ + Literal[ + "email", + "website", + "app", + "phone_call", + "chat", + "physical_store", + "system_generated", + "business_messaging", + "other", + ] + ] + | Omit = omit, + context: Optional[conversion_create_params.Context] | Omit = omit, + currency: Optional[Currency] | Omit = omit, + custom_name: Optional[str] | Omit = omit, + event_id: Optional[str] | Omit = omit, + event_time: Union[str, datetime, None] | Omit = omit, + plan_id: Optional[str] | Omit = omit, + product_id: Optional[str] | Omit = omit, + referrer_url: Optional[str] | Omit = omit, + url: Optional[str] | Omit = omit, + user: Optional[conversion_create_params.User] | Omit = omit, + value: Optional[float] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> ConversionCreateResponse: + """ + Track a conversion or engagement event for a company. + + Required permissions: + + - `event:create` + + Args: + company_id: The company to associate with this event. + + event_name: The type of event. + + action_source: The channel where an event originated + + context: Tracking and attribution context. + + currency: The available currencies on the platform + + custom_name: Custom event name when event_name is 'custom'. + + event_id: Client-provided identifier for deduplication. Generated if omitted. + + event_time: When the event occurred. Defaults to now. + + plan_id: The plan associated with the event. + + product_id: The product associated with the event. + + referrer_url: The referring URL. + + url: The URL where the event occurred. + + user: User identity and profile data. + + value: Monetary value associated with the event. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return self._post( + "/conversions", + body=maybe_transform( + { + "company_id": company_id, + "event_name": event_name, + "action_source": action_source, + "context": context, + "currency": currency, + "custom_name": custom_name, + "event_id": event_id, + "event_time": event_time, + "plan_id": plan_id, + "product_id": product_id, + "referrer_url": referrer_url, + "url": url, + "user": user, + "value": value, + }, + conversion_create_params.ConversionCreateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=ConversionCreateResponse, + ) + + +class AsyncConversionsResource(AsyncAPIResource): + """Conversions""" + + @cached_property + def with_raw_response(self) -> AsyncConversionsResourceWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/whopio/whopsdk-python#accessing-raw-response-data-eg-headers + """ + return AsyncConversionsResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncConversionsResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/whopio/whopsdk-python#with_streaming_response + """ + return AsyncConversionsResourceWithStreamingResponse(self) + + async def create( + self, + *, + company_id: str, + event_name: Literal["lead", "submit_application", "contact", "complete_registration", "schedule", "custom"], + action_source: Optional[ + Literal[ + "email", + "website", + "app", + "phone_call", + "chat", + "physical_store", + "system_generated", + "business_messaging", + "other", + ] + ] + | Omit = omit, + context: Optional[conversion_create_params.Context] | Omit = omit, + currency: Optional[Currency] | Omit = omit, + custom_name: Optional[str] | Omit = omit, + event_id: Optional[str] | Omit = omit, + event_time: Union[str, datetime, None] | Omit = omit, + plan_id: Optional[str] | Omit = omit, + product_id: Optional[str] | Omit = omit, + referrer_url: Optional[str] | Omit = omit, + url: Optional[str] | Omit = omit, + user: Optional[conversion_create_params.User] | Omit = omit, + value: Optional[float] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> ConversionCreateResponse: + """ + Track a conversion or engagement event for a company. + + Required permissions: + + - `event:create` + + Args: + company_id: The company to associate with this event. + + event_name: The type of event. + + action_source: The channel where an event originated + + context: Tracking and attribution context. + + currency: The available currencies on the platform + + custom_name: Custom event name when event_name is 'custom'. + + event_id: Client-provided identifier for deduplication. Generated if omitted. + + event_time: When the event occurred. Defaults to now. + + plan_id: The plan associated with the event. + + product_id: The product associated with the event. + + referrer_url: The referring URL. + + url: The URL where the event occurred. + + user: User identity and profile data. + + value: Monetary value associated with the event. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return await self._post( + "/conversions", + body=await async_maybe_transform( + { + "company_id": company_id, + "event_name": event_name, + "action_source": action_source, + "context": context, + "currency": currency, + "custom_name": custom_name, + "event_id": event_id, + "event_time": event_time, + "plan_id": plan_id, + "product_id": product_id, + "referrer_url": referrer_url, + "url": url, + "user": user, + "value": value, + }, + conversion_create_params.ConversionCreateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=ConversionCreateResponse, + ) + + +class ConversionsResourceWithRawResponse: + def __init__(self, conversions: ConversionsResource) -> None: + self._conversions = conversions + + self.create = to_raw_response_wrapper( + conversions.create, + ) + + +class AsyncConversionsResourceWithRawResponse: + def __init__(self, conversions: AsyncConversionsResource) -> None: + self._conversions = conversions + + self.create = async_to_raw_response_wrapper( + conversions.create, + ) + + +class ConversionsResourceWithStreamingResponse: + def __init__(self, conversions: ConversionsResource) -> None: + self._conversions = conversions + + self.create = to_streamed_response_wrapper( + conversions.create, + ) + + +class AsyncConversionsResourceWithStreamingResponse: + def __init__(self, conversions: AsyncConversionsResource) -> None: + self._conversions = conversions + + self.create = async_to_streamed_response_wrapper( + conversions.create, + ) diff --git a/src/whop_sdk/resources/files.py b/src/whop_sdk/resources/files.py index 5e1ad1bc..a27e5e6b 100644 --- a/src/whop_sdk/resources/files.py +++ b/src/whop_sdk/resources/files.py @@ -2,10 +2,13 @@ from __future__ import annotations +from typing import Optional +from typing_extensions import Literal + import httpx from ..types import file_create_params -from .._types import Body, Query, Headers, NotGiven, not_given +from .._types import Body, Omit, Query, Headers, NotGiven, omit, not_given from .._utils import path_template, maybe_transform, async_maybe_transform from .._compat import cached_property from .._resource import SyncAPIResource, AsyncAPIResource @@ -46,6 +49,7 @@ def create( self, *, filename: str, + visibility: Optional[Literal["public", "private"]] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, @@ -61,6 +65,9 @@ def create( filename: The name of the file including its extension (e.g., "photo.png" or "document.pdf"). + visibility: Controls whether an uploaded file is publicly accessible or requires + authentication to access. + extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -71,7 +78,13 @@ def create( """ return self._post( "/files", - body=maybe_transform({"filename": filename}, file_create_params.FileCreateParams), + body=maybe_transform( + { + "filename": filename, + "visibility": visibility, + }, + file_create_params.FileCreateParams, + ), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -136,6 +149,7 @@ async def create( self, *, filename: str, + visibility: Optional[Literal["public", "private"]] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, @@ -151,6 +165,9 @@ async def create( filename: The name of the file including its extension (e.g., "photo.png" or "document.pdf"). + visibility: Controls whether an uploaded file is publicly accessible or requires + authentication to access. + extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -161,7 +178,13 @@ async def create( """ return await self._post( "/files", - body=await async_maybe_transform({"filename": filename}, file_create_params.FileCreateParams), + body=await async_maybe_transform( + { + "filename": filename, + "visibility": visibility, + }, + file_create_params.FileCreateParams, + ), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), diff --git a/src/whop_sdk/resources/forum_posts.py b/src/whop_sdk/resources/forum_posts.py index eacff2da..786233e3 100644 --- a/src/whop_sdk/resources/forum_posts.py +++ b/src/whop_sdk/resources/forum_posts.py @@ -258,6 +258,7 @@ def list( after: Optional[str] | Omit = omit, before: Optional[str] | Omit = omit, first: Optional[int] | Omit = omit, + include_bounty_anchors: Optional[bool] | Omit = omit, last: Optional[int] | Omit = omit, parent_id: Optional[str] | Omit = omit, pinned: Optional[bool] | Omit = omit, @@ -285,6 +286,8 @@ def list( first: Returns the first _n_ elements from the list. + include_bounty_anchors: Whether to include top-level bounty discussion anchors as rich forum items. + last: Returns the last _n_ elements from the list. parent_id: The unique identifier of a parent post to list comments for. When set, returns @@ -315,6 +318,7 @@ def list( "after": after, "before": before, "first": first, + "include_bounty_anchors": include_bounty_anchors, "last": last, "parent_id": parent_id, "pinned": pinned, @@ -552,6 +556,7 @@ def list( after: Optional[str] | Omit = omit, before: Optional[str] | Omit = omit, first: Optional[int] | Omit = omit, + include_bounty_anchors: Optional[bool] | Omit = omit, last: Optional[int] | Omit = omit, parent_id: Optional[str] | Omit = omit, pinned: Optional[bool] | Omit = omit, @@ -579,6 +584,8 @@ def list( first: Returns the first _n_ elements from the list. + include_bounty_anchors: Whether to include top-level bounty discussion anchors as rich forum items. + last: Returns the last _n_ elements from the list. parent_id: The unique identifier of a parent post to list comments for. When set, returns @@ -609,6 +616,7 @@ def list( "after": after, "before": before, "first": first, + "include_bounty_anchors": include_bounty_anchors, "last": last, "parent_id": parent_id, "pinned": pinned, diff --git a/src/whop_sdk/resources/invoices.py b/src/whop_sdk/resources/invoices.py index 5f9b090f..053064cb 100644 --- a/src/whop_sdk/resources/invoices.py +++ b/src/whop_sdk/resources/invoices.py @@ -371,6 +371,7 @@ def update( member_id: Optional[str] | Omit = omit, payment_method_id: Optional[str] | Omit = omit, plan: Optional[invoice_update_params.Plan] | Omit = omit, + product_id: Optional[str] | Omit = omit, subscription_billing_anchor_at: Union[str, datetime, None] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. @@ -413,6 +414,9 @@ def update( plan: Updated plan attributes. + product_id: The unique identifier of an existing product to attach to this invoice. Only + allowed while the invoice is still a draft. + subscription_billing_anchor_at: The date that defines when the subscription billing cycle should start. extra_headers: Send extra headers @@ -441,6 +445,7 @@ def update( "member_id": member_id, "payment_method_id": payment_method_id, "plan": plan, + "product_id": product_id, "subscription_billing_anchor_at": subscription_billing_anchor_at, }, invoice_update_params.InvoiceUpdateParams, @@ -1033,6 +1038,7 @@ async def update( member_id: Optional[str] | Omit = omit, payment_method_id: Optional[str] | Omit = omit, plan: Optional[invoice_update_params.Plan] | Omit = omit, + product_id: Optional[str] | Omit = omit, subscription_billing_anchor_at: Union[str, datetime, None] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. @@ -1075,6 +1081,9 @@ async def update( plan: Updated plan attributes. + product_id: The unique identifier of an existing product to attach to this invoice. Only + allowed while the invoice is still a draft. + subscription_billing_anchor_at: The date that defines when the subscription billing cycle should start. extra_headers: Send extra headers @@ -1103,6 +1112,7 @@ async def update( "member_id": member_id, "payment_method_id": payment_method_id, "plan": plan, + "product_id": product_id, "subscription_billing_anchor_at": subscription_billing_anchor_at, }, invoice_update_params.InvoiceUpdateParams, diff --git a/src/whop_sdk/resources/payments.py b/src/whop_sdk/resources/payments.py index db84e495..b3435328 100644 --- a/src/whop_sdk/resources/payments.py +++ b/src/whop_sdk/resources/payments.py @@ -478,8 +478,9 @@ def refund( - `payment:resolution_center_case:read` Args: - partial_amount: The amount to refund in the payment currency. If omitted, the full payment - amount is refunded. + partial_amount: The amount to refund. For multi-currency payments, this is in the charge + currency (what the buyer paid). For single-currency, this is in the payment + currency. If omitted, the full payment amount is refunded. extra_headers: Send extra headers @@ -1040,8 +1041,9 @@ async def refund( - `payment:resolution_center_case:read` Args: - partial_amount: The amount to refund in the payment currency. If omitted, the full payment - amount is refunded. + partial_amount: The amount to refund. For multi-currency payments, this is in the charge + currency (what the buyer paid). For single-currency, this is in the payment + currency. If omitted, the full payment amount is refunded. extra_headers: Send extra headers diff --git a/src/whop_sdk/resources/plans.py b/src/whop_sdk/resources/plans.py index a0f6dac1..8885f8b6 100644 --- a/src/whop_sdk/resources/plans.py +++ b/src/whop_sdk/resources/plans.py @@ -60,6 +60,7 @@ def create( *, company_id: str, product_id: str, + adaptive_pricing_enabled: Optional[bool] | Omit = omit, billing_period: Optional[int] | Omit = omit, checkout_styling: Optional[plan_create_params.CheckoutStyling] | Omit = omit, currency: Optional[Currency] | Omit = omit, @@ -104,6 +105,8 @@ def create( product_id: The unique identifier of the product to attach this plan to. + adaptive_pricing_enabled: Whether this plan accepts local currency payments via adaptive pricing. + billing_period: The number of days between recurring charges. For example, 30 for monthly or 365 for yearly. @@ -170,6 +173,7 @@ def create( { "company_id": company_id, "product_id": product_id, + "adaptive_pricing_enabled": adaptive_pricing_enabled, "billing_period": billing_period, "checkout_styling": checkout_styling, "currency": currency, @@ -241,6 +245,7 @@ def update( self, id: str, *, + adaptive_pricing_enabled: Optional[bool] | Omit = omit, billing_period: Optional[int] | Omit = omit, checkout_styling: Optional[plan_update_params.CheckoutStyling] | Omit = omit, currency: Optional[Currency] | Omit = omit, @@ -280,6 +285,8 @@ def update( - `plan:basic:read` Args: + adaptive_pricing_enabled: Whether this plan accepts local currency payments via adaptive pricing. + billing_period: The number of days between recurring charges. For example, 30 for monthly or 365 for yearly. @@ -346,6 +353,7 @@ def update( path_template("/plans/{id}", id=id), body=maybe_transform( { + "adaptive_pricing_enabled": adaptive_pricing_enabled, "billing_period": billing_period, "checkout_styling": checkout_styling, "currency": currency, @@ -538,6 +546,7 @@ async def create( *, company_id: str, product_id: str, + adaptive_pricing_enabled: Optional[bool] | Omit = omit, billing_period: Optional[int] | Omit = omit, checkout_styling: Optional[plan_create_params.CheckoutStyling] | Omit = omit, currency: Optional[Currency] | Omit = omit, @@ -582,6 +591,8 @@ async def create( product_id: The unique identifier of the product to attach this plan to. + adaptive_pricing_enabled: Whether this plan accepts local currency payments via adaptive pricing. + billing_period: The number of days between recurring charges. For example, 30 for monthly or 365 for yearly. @@ -648,6 +659,7 @@ async def create( { "company_id": company_id, "product_id": product_id, + "adaptive_pricing_enabled": adaptive_pricing_enabled, "billing_period": billing_period, "checkout_styling": checkout_styling, "currency": currency, @@ -719,6 +731,7 @@ async def update( self, id: str, *, + adaptive_pricing_enabled: Optional[bool] | Omit = omit, billing_period: Optional[int] | Omit = omit, checkout_styling: Optional[plan_update_params.CheckoutStyling] | Omit = omit, currency: Optional[Currency] | Omit = omit, @@ -758,6 +771,8 @@ async def update( - `plan:basic:read` Args: + adaptive_pricing_enabled: Whether this plan accepts local currency payments via adaptive pricing. + billing_period: The number of days between recurring charges. For example, 30 for monthly or 365 for yearly. @@ -824,6 +839,7 @@ async def update( path_template("/plans/{id}", id=id), body=await async_maybe_transform( { + "adaptive_pricing_enabled": adaptive_pricing_enabled, "billing_period": billing_period, "checkout_styling": checkout_styling, "currency": currency, diff --git a/src/whop_sdk/resources/stats.py b/src/whop_sdk/resources/stats.py new file mode 100644 index 00000000..c4b200fb --- /dev/null +++ b/src/whop_sdk/resources/stats.py @@ -0,0 +1,760 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Any, Dict, Union, Optional, cast +from datetime import datetime + +import httpx + +from ..types import stat_run_sql_params, stat_describe_params, stat_query_raw_params, stat_query_metric_params +from .._types import Body, Omit, Query, Headers, NotGiven, SequenceNotStr, omit, not_given +from .._utils import maybe_transform, async_maybe_transform +from .._compat import cached_property +from .._resource import SyncAPIResource, AsyncAPIResource +from .._response import ( + to_raw_response_wrapper, + to_streamed_response_wrapper, + async_to_raw_response_wrapper, + async_to_streamed_response_wrapper, +) +from .._base_client import make_request_options +from ..types.shared.direction import Direction +from ..types.stat_run_sql_response import StatRunSqlResponse +from ..types.stat_describe_response import StatDescribeResponse +from ..types.stat_query_raw_response import StatQueryRawResponse +from ..types.stat_query_metric_response import StatQueryMetricResponse + +__all__ = ["StatsResource", "AsyncStatsResource"] + + +class StatsResource(SyncAPIResource): + """Stats""" + + @cached_property + def with_raw_response(self) -> StatsResourceWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/whopio/whopsdk-python#accessing-raw-response-data-eg-headers + """ + return StatsResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> StatsResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/whopio/whopsdk-python#with_streaming_response + """ + return StatsResourceWithStreamingResponse(self) + + def describe( + self, + *, + company_id: Optional[str] | Omit = omit, + resource: Optional[str] | Omit = omit, + user_id: Optional[str] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> StatDescribeResponse: + """Describe available stats schema. + + Without resource returns root nodes and + metrics. With resource returns node columns, associations, and available + metrics. + + Required permissions: + + - `stats:read` + + Args: + company_id: Scope query to a specific company. + + resource: Resource path using : as separator (e.g., 'receipts', 'payments:membership', + 'receipts:gross_revenue'). + + user_id: Scope query to a specific user. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return cast( + StatDescribeResponse, + self._get( + "/stats/describe", + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "company_id": company_id, + "resource": resource, + "user_id": user_id, + }, + stat_describe_params.StatDescribeParams, + ), + ), + cast_to=cast( + Any, StatDescribeResponse + ), # Union types cannot be passed in as arguments in the type system + ), + ) + + def query_metric( + self, + *, + resource: str, + breakdowns: Optional[SequenceNotStr[str]] | Omit = omit, + company_id: Optional[str] | Omit = omit, + filters: Optional[Dict[str, object]] | Omit = omit, + from_: Union[str, datetime, None] | Omit = omit, + granularity: Optional[str] | Omit = omit, + time_zone: Optional[str] | Omit = omit, + to: Union[str, datetime, None] | Omit = omit, + user_id: Optional[str] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> StatQueryMetricResponse: + """Query an aggregated metric. + + Returns data grouped by period with optional + breakdowns. + + Required permissions: + + - `stats:read` + + Args: + resource: Metric resource using : as separator (e.g., 'receipts:gross_revenue', + 'members:new_users'). + + breakdowns: Columns to break down the metric by. + + company_id: Scope query to a specific company. + + filters: Key-value pairs to filter the data. + + from_: Start of time range (unix timestamp). + + granularity: Time granularity (daily, weekly, monthly). + + time_zone: IANA timezone for period bucketing (e.g. 'America/New_York'). Defaults to UTC. + Only applies to ClickHouse metrics. + + to: End of time range (unix timestamp). + + user_id: Scope query to a specific user. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return self._get( + "/stats/metric", + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "resource": resource, + "breakdowns": breakdowns, + "company_id": company_id, + "filters": filters, + "from_": from_, + "granularity": granularity, + "time_zone": time_zone, + "to": to, + "user_id": user_id, + }, + stat_query_metric_params.StatQueryMetricParams, + ), + ), + cast_to=StatQueryMetricResponse, + ) + + def query_raw( + self, + *, + resource: str, + company_id: Optional[str] | Omit = omit, + cursor: Optional[str] | Omit = omit, + from_: Union[str, datetime, None] | Omit = omit, + limit: Optional[int] | Omit = omit, + sort: Optional[str] | Omit = omit, + sort_direction: Optional[Direction] | Omit = omit, + to: Union[str, datetime, None] | Omit = omit, + user_id: Optional[str] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> StatQueryRawResponse: + """Query raw data from a resource. + + Returns paginated rows with all columns. + + Required permissions: + + - `stats:read` + + Args: + resource: Resource path using : as separator (e.g., 'members', 'payments:membership'). + + company_id: Scope query to a specific company. + + cursor: Pagination cursor for next page. + + from_: Start of time range (unix timestamp). + + limit: Number of records to return (max 10000). + + sort: Column to sort by. + + sort_direction: The direction of the sort. + + to: End of time range (unix timestamp). + + user_id: Scope query to a specific user. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return self._get( + "/stats/raw", + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "resource": resource, + "company_id": company_id, + "cursor": cursor, + "from_": from_, + "limit": limit, + "sort": sort, + "sort_direction": sort_direction, + "to": to, + "user_id": user_id, + }, + stat_query_raw_params.StatQueryRawParams, + ), + ), + cast_to=StatQueryRawResponse, + ) + + def run_sql( + self, + *, + resource: str, + sql: str, + company_id: Optional[str] | Omit = omit, + cursor: Optional[str] | Omit = omit, + from_: Union[str, datetime, None] | Omit = omit, + limit: Optional[int] | Omit = omit, + sort: Optional[str] | Omit = omit, + sort_direction: Optional[Direction] | Omit = omit, + to: Union[str, datetime, None] | Omit = omit, + user_id: Optional[str] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> StatRunSqlResponse: + """Run custom SQL against a scoped resource. + + Use SCOPED_DATA as the table name. + + Required permissions: + + - `stats:read` + + Args: + resource: Resource path using : as separator (e.g., 'receipts', 'payments:membership'). + + sql: SQL query. Use SCOPED_DATA as the table name. + + company_id: Scope query to a specific company. + + cursor: Pagination cursor for next page. + + from_: Start of time range (unix timestamp). + + limit: Number of records to return (max 10000). + + sort: Column to sort by. + + sort_direction: The direction of the sort. + + to: End of time range (unix timestamp). + + user_id: Scope query to a specific user. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return self._get( + "/stats/sql", + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "resource": resource, + "sql": sql, + "company_id": company_id, + "cursor": cursor, + "from_": from_, + "limit": limit, + "sort": sort, + "sort_direction": sort_direction, + "to": to, + "user_id": user_id, + }, + stat_run_sql_params.StatRunSqlParams, + ), + ), + cast_to=StatRunSqlResponse, + ) + + +class AsyncStatsResource(AsyncAPIResource): + """Stats""" + + @cached_property + def with_raw_response(self) -> AsyncStatsResourceWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/whopio/whopsdk-python#accessing-raw-response-data-eg-headers + """ + return AsyncStatsResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncStatsResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/whopio/whopsdk-python#with_streaming_response + """ + return AsyncStatsResourceWithStreamingResponse(self) + + async def describe( + self, + *, + company_id: Optional[str] | Omit = omit, + resource: Optional[str] | Omit = omit, + user_id: Optional[str] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> StatDescribeResponse: + """Describe available stats schema. + + Without resource returns root nodes and + metrics. With resource returns node columns, associations, and available + metrics. + + Required permissions: + + - `stats:read` + + Args: + company_id: Scope query to a specific company. + + resource: Resource path using : as separator (e.g., 'receipts', 'payments:membership', + 'receipts:gross_revenue'). + + user_id: Scope query to a specific user. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return cast( + StatDescribeResponse, + await self._get( + "/stats/describe", + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=await async_maybe_transform( + { + "company_id": company_id, + "resource": resource, + "user_id": user_id, + }, + stat_describe_params.StatDescribeParams, + ), + ), + cast_to=cast( + Any, StatDescribeResponse + ), # Union types cannot be passed in as arguments in the type system + ), + ) + + async def query_metric( + self, + *, + resource: str, + breakdowns: Optional[SequenceNotStr[str]] | Omit = omit, + company_id: Optional[str] | Omit = omit, + filters: Optional[Dict[str, object]] | Omit = omit, + from_: Union[str, datetime, None] | Omit = omit, + granularity: Optional[str] | Omit = omit, + time_zone: Optional[str] | Omit = omit, + to: Union[str, datetime, None] | Omit = omit, + user_id: Optional[str] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> StatQueryMetricResponse: + """Query an aggregated metric. + + Returns data grouped by period with optional + breakdowns. + + Required permissions: + + - `stats:read` + + Args: + resource: Metric resource using : as separator (e.g., 'receipts:gross_revenue', + 'members:new_users'). + + breakdowns: Columns to break down the metric by. + + company_id: Scope query to a specific company. + + filters: Key-value pairs to filter the data. + + from_: Start of time range (unix timestamp). + + granularity: Time granularity (daily, weekly, monthly). + + time_zone: IANA timezone for period bucketing (e.g. 'America/New_York'). Defaults to UTC. + Only applies to ClickHouse metrics. + + to: End of time range (unix timestamp). + + user_id: Scope query to a specific user. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return await self._get( + "/stats/metric", + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=await async_maybe_transform( + { + "resource": resource, + "breakdowns": breakdowns, + "company_id": company_id, + "filters": filters, + "from_": from_, + "granularity": granularity, + "time_zone": time_zone, + "to": to, + "user_id": user_id, + }, + stat_query_metric_params.StatQueryMetricParams, + ), + ), + cast_to=StatQueryMetricResponse, + ) + + async def query_raw( + self, + *, + resource: str, + company_id: Optional[str] | Omit = omit, + cursor: Optional[str] | Omit = omit, + from_: Union[str, datetime, None] | Omit = omit, + limit: Optional[int] | Omit = omit, + sort: Optional[str] | Omit = omit, + sort_direction: Optional[Direction] | Omit = omit, + to: Union[str, datetime, None] | Omit = omit, + user_id: Optional[str] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> StatQueryRawResponse: + """Query raw data from a resource. + + Returns paginated rows with all columns. + + Required permissions: + + - `stats:read` + + Args: + resource: Resource path using : as separator (e.g., 'members', 'payments:membership'). + + company_id: Scope query to a specific company. + + cursor: Pagination cursor for next page. + + from_: Start of time range (unix timestamp). + + limit: Number of records to return (max 10000). + + sort: Column to sort by. + + sort_direction: The direction of the sort. + + to: End of time range (unix timestamp). + + user_id: Scope query to a specific user. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return await self._get( + "/stats/raw", + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=await async_maybe_transform( + { + "resource": resource, + "company_id": company_id, + "cursor": cursor, + "from_": from_, + "limit": limit, + "sort": sort, + "sort_direction": sort_direction, + "to": to, + "user_id": user_id, + }, + stat_query_raw_params.StatQueryRawParams, + ), + ), + cast_to=StatQueryRawResponse, + ) + + async def run_sql( + self, + *, + resource: str, + sql: str, + company_id: Optional[str] | Omit = omit, + cursor: Optional[str] | Omit = omit, + from_: Union[str, datetime, None] | Omit = omit, + limit: Optional[int] | Omit = omit, + sort: Optional[str] | Omit = omit, + sort_direction: Optional[Direction] | Omit = omit, + to: Union[str, datetime, None] | Omit = omit, + user_id: Optional[str] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> StatRunSqlResponse: + """Run custom SQL against a scoped resource. + + Use SCOPED_DATA as the table name. + + Required permissions: + + - `stats:read` + + Args: + resource: Resource path using : as separator (e.g., 'receipts', 'payments:membership'). + + sql: SQL query. Use SCOPED_DATA as the table name. + + company_id: Scope query to a specific company. + + cursor: Pagination cursor for next page. + + from_: Start of time range (unix timestamp). + + limit: Number of records to return (max 10000). + + sort: Column to sort by. + + sort_direction: The direction of the sort. + + to: End of time range (unix timestamp). + + user_id: Scope query to a specific user. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return await self._get( + "/stats/sql", + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=await async_maybe_transform( + { + "resource": resource, + "sql": sql, + "company_id": company_id, + "cursor": cursor, + "from_": from_, + "limit": limit, + "sort": sort, + "sort_direction": sort_direction, + "to": to, + "user_id": user_id, + }, + stat_run_sql_params.StatRunSqlParams, + ), + ), + cast_to=StatRunSqlResponse, + ) + + +class StatsResourceWithRawResponse: + def __init__(self, stats: StatsResource) -> None: + self._stats = stats + + self.describe = to_raw_response_wrapper( + stats.describe, + ) + self.query_metric = to_raw_response_wrapper( + stats.query_metric, + ) + self.query_raw = to_raw_response_wrapper( + stats.query_raw, + ) + self.run_sql = to_raw_response_wrapper( + stats.run_sql, + ) + + +class AsyncStatsResourceWithRawResponse: + def __init__(self, stats: AsyncStatsResource) -> None: + self._stats = stats + + self.describe = async_to_raw_response_wrapper( + stats.describe, + ) + self.query_metric = async_to_raw_response_wrapper( + stats.query_metric, + ) + self.query_raw = async_to_raw_response_wrapper( + stats.query_raw, + ) + self.run_sql = async_to_raw_response_wrapper( + stats.run_sql, + ) + + +class StatsResourceWithStreamingResponse: + def __init__(self, stats: StatsResource) -> None: + self._stats = stats + + self.describe = to_streamed_response_wrapper( + stats.describe, + ) + self.query_metric = to_streamed_response_wrapper( + stats.query_metric, + ) + self.query_raw = to_streamed_response_wrapper( + stats.query_raw, + ) + self.run_sql = to_streamed_response_wrapper( + stats.run_sql, + ) + + +class AsyncStatsResourceWithStreamingResponse: + def __init__(self, stats: AsyncStatsResource) -> None: + self._stats = stats + + self.describe = async_to_streamed_response_wrapper( + stats.describe, + ) + self.query_metric = async_to_streamed_response_wrapper( + stats.query_metric, + ) + self.query_raw = async_to_streamed_response_wrapper( + stats.query_raw, + ) + self.run_sql = async_to_streamed_response_wrapper( + stats.run_sql, + ) diff --git a/src/whop_sdk/resources/support_channels.py b/src/whop_sdk/resources/support_channels.py index 7d919e49..db5bc8c7 100644 --- a/src/whop_sdk/resources/support_channels.py +++ b/src/whop_sdk/resources/support_channels.py @@ -141,14 +141,15 @@ def retrieve( def list( self, *, - company_id: str, after: Optional[str] | Omit = omit, before: Optional[str] | Omit = omit, + company_id: Optional[str] | Omit = omit, direction: Optional[Direction] | Omit = omit, first: Optional[int] | Omit = omit, last: Optional[int] | Omit = omit, open: Optional[bool] | Omit = omit, order: Optional[Literal["created_at", "last_post_sent_at"]] | Omit = omit, + view: Optional[Literal["all", "admin", "customer"]] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, @@ -165,12 +166,14 @@ def list( - `support_chat:read` Args: - company_id: The unique identifier of the company to list support channels for. - after: Returns the elements in the list that come after the specified cursor. before: Returns the elements in the list that come before the specified cursor. + company_id: The unique identifier of the company to list support channels for. Includes + channels of child companies. When omitted, returns support channels across all + companies the user has access to. + direction: The direction of the sort. first: Returns the first _n_ elements from the list. @@ -182,6 +185,8 @@ def list( order: Sort options for message channels + view: The perspective to filter support channels by. + extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -200,14 +205,15 @@ def list( timeout=timeout, query=maybe_transform( { - "company_id": company_id, "after": after, "before": before, + "company_id": company_id, "direction": direction, "first": first, "last": last, "open": open, "order": order, + "view": view, }, support_channel_list_params.SupportChannelListParams, ), @@ -330,14 +336,15 @@ async def retrieve( def list( self, *, - company_id: str, after: Optional[str] | Omit = omit, before: Optional[str] | Omit = omit, + company_id: Optional[str] | Omit = omit, direction: Optional[Direction] | Omit = omit, first: Optional[int] | Omit = omit, last: Optional[int] | Omit = omit, open: Optional[bool] | Omit = omit, order: Optional[Literal["created_at", "last_post_sent_at"]] | Omit = omit, + view: Optional[Literal["all", "admin", "customer"]] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, @@ -354,12 +361,14 @@ def list( - `support_chat:read` Args: - company_id: The unique identifier of the company to list support channels for. - after: Returns the elements in the list that come after the specified cursor. before: Returns the elements in the list that come before the specified cursor. + company_id: The unique identifier of the company to list support channels for. Includes + channels of child companies. When omitted, returns support channels across all + companies the user has access to. + direction: The direction of the sort. first: Returns the first _n_ elements from the list. @@ -371,6 +380,8 @@ def list( order: Sort options for message channels + view: The perspective to filter support channels by. + extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -389,14 +400,15 @@ def list( timeout=timeout, query=maybe_transform( { - "company_id": company_id, "after": after, "before": before, + "company_id": company_id, "direction": direction, "first": first, "last": last, "open": open, "order": order, + "view": view, }, support_channel_list_params.SupportChannelListParams, ), diff --git a/src/whop_sdk/types/__init__.py b/src/whop_sdk/types/__init__.py index c8683b56..3167ebee 100644 --- a/src/whop_sdk/types/__init__.py +++ b/src/whop_sdk/types/__init__.py @@ -87,6 +87,7 @@ from .review_status import ReviewStatus as ReviewStatus from .upload_status import UploadStatus as UploadStatus from .webhook_event import WebhookEvent as WebhookEvent +from .ad_list_params import AdListParams as AdListParams from .cancel_options import CancelOptions as CancelOptions from .checkout_modes import CheckoutModes as CheckoutModes from .checkout_shape import CheckoutShape as CheckoutShape @@ -96,6 +97,8 @@ from .authorized_user import AuthorizedUser as AuthorizedUser from .billing_reasons import BillingReasons as BillingReasons from .fee_markup_type import FeeMarkupType as FeeMarkupType +from .ad_create_params import AdCreateParams as AdCreateParams +from .ad_list_response import AdListResponse as AdListResponse from .dispute_statuses import DisputeStatuses as DisputeStatuses from .lead_list_params import LeadListParams as LeadListParams from .payment_provider import PaymentProvider as PaymentProvider @@ -109,6 +112,8 @@ from .promo_code_status import PromoCodeStatus as PromoCodeStatus from .withdrawal_speeds import WithdrawalSpeeds as WithdrawalSpeeds from .withdrawal_status import WithdrawalStatus as WithdrawalStatus +from .ad_create_response import AdCreateResponse as AdCreateResponse +from .bounty_list_params import BountyListParams as BountyListParams from .course_list_params import CourseListParams as CourseListParams from .dispute_alert_type import DisputeAlertType as DisputeAlertType from .file_create_params import FileCreateParams as FileCreateParams @@ -136,10 +141,15 @@ from .payment_list_params import PaymentListParams as PaymentListParams from .product_list_params import ProductListParams as ProductListParams from .setup_intent_status import SetupIntentStatus as SetupIntentStatus +from .stat_run_sql_params import StatRunSqlParams as StatRunSqlParams from .tax_identifier_type import TaxIdentifierType as TaxIdentifierType from .topup_create_params import TopupCreateParams as TopupCreateParams from .verification_status import VerificationStatus as VerificationStatus from .webhook_list_params import WebhookListParams as WebhookListParams +from .ad_group_list_params import AdGroupListParams as AdGroupListParams +from .ad_retrieve_response import AdRetrieveResponse as AdRetrieveResponse +from .bounty_create_params import BountyCreateParams as BountyCreateParams +from .bounty_list_response import BountyListResponse as BountyListResponse from .course_create_params import CourseCreateParams as CourseCreateParams from .course_list_response import CourseListResponse as CourseListResponse from .course_update_params import CourseUpdateParams as CourseUpdateParams @@ -153,6 +163,7 @@ from .review_list_response import ReviewListResponse as ReviewListResponse from .shipment_list_params import ShipmentListParams as ShipmentListParams from .social_link_websites import SocialLinkWebsites as SocialLinkWebsites +from .stat_describe_params import StatDescribeParams as StatDescribeParams from .transfer_list_params import TransferListParams as TransferListParams from .unwrap_webhook_event import UnwrapWebhookEvent as UnwrapWebhookEvent from .user_retrieve_params import UserRetrieveParams as UserRetrieveParams @@ -179,10 +190,16 @@ from .product_create_params import ProductCreateParams as ProductCreateParams from .product_update_params import ProductUpdateParams as ProductUpdateParams from .refund_reference_type import RefundReferenceType as RefundReferenceType +from .stat_query_raw_params import StatQueryRawParams as StatQueryRawParams +from .stat_run_sql_response import StatRunSqlResponse as StatRunSqlResponse from .topup_create_response import TopupCreateResponse as TopupCreateResponse from .webhook_create_params import WebhookCreateParams as WebhookCreateParams from .webhook_list_response import WebhookListResponse as WebhookListResponse from .webhook_update_params import WebhookUpdateParams as WebhookUpdateParams +from .ad_group_create_params import AdGroupCreateParams as AdGroupCreateParams +from .ad_group_list_response import AdGroupListResponse as AdGroupListResponse +from .ad_group_update_params import AdGroupUpdateParams as AdGroupUpdateParams +from .bounty_create_response import BountyCreateResponse as BountyCreateResponse from .course_delete_response import CourseDeleteResponse as CourseDeleteResponse from .dm_channel_list_params import DmChannelListParams as DmChannelListParams from .entry_approve_response import EntryApproveResponse as EntryApproveResponse @@ -197,9 +214,11 @@ from .reaction_list_response import ReactionListResponse as ReactionListResponse from .shipment_create_params import ShipmentCreateParams as ShipmentCreateParams from .shipment_list_response import ShipmentListResponse as ShipmentListResponse +from .stat_describe_response import StatDescribeResponse as StatDescribeResponse from .transfer_create_params import TransferCreateParams as TransferCreateParams from .transfer_list_response import TransferListResponse as TransferListResponse from .withdrawal_list_params import WithdrawalListParams as WithdrawalListParams +from .ad_campaign_list_params import AdCampaignListParams as AdCampaignListParams from .affiliate_create_params import AffiliateCreateParams as AffiliateCreateParams from .affiliate_list_response import AffiliateListResponse as AffiliateListResponse from .ai_chat_delete_response import AIChatDeleteResponse as AIChatDeleteResponse @@ -214,10 +233,16 @@ from .message_delete_response import MessageDeleteResponse as MessageDeleteResponse from .product_delete_response import ProductDeleteResponse as ProductDeleteResponse from .refund_reference_status import RefundReferenceStatus as RefundReferenceStatus +from .stat_query_raw_response import StatQueryRawResponse as StatQueryRawResponse from .verification_error_code import VerificationErrorCode as VerificationErrorCode from .webhook_create_response import WebhookCreateResponse as WebhookCreateResponse from .webhook_delete_response import WebhookDeleteResponse as WebhookDeleteResponse +from .ad_group_create_response import AdGroupCreateResponse as AdGroupCreateResponse +from .ad_group_delete_response import AdGroupDeleteResponse as AdGroupDeleteResponse +from .ad_group_update_response import AdGroupUpdateResponse as AdGroupUpdateResponse +from .bounty_retrieve_response import BountyRetrieveResponse as BountyRetrieveResponse from .chat_channel_list_params import ChatChannelListParams as ChatChannelListParams +from .conversion_create_params import ConversionCreateParams as ConversionCreateParams from .dm_channel_create_params import DmChannelCreateParams as DmChannelCreateParams from .dm_channel_list_response import DmChannelListResponse as DmChannelListResponse from .dm_channel_update_params import DmChannelUpdateParams as DmChannelUpdateParams @@ -243,9 +268,13 @@ from .refund_retrieve_response import RefundRetrieveResponse as RefundRetrieveResponse from .review_retrieve_response import ReviewRetrieveResponse as ReviewRetrieveResponse from .setup_intent_list_params import SetupIntentListParams as SetupIntentListParams +from .stat_query_metric_params import StatQueryMetricParams as StatQueryMetricParams from .verification_list_params import VerificationListParams as VerificationListParams from .withdrawal_create_params import WithdrawalCreateParams as WithdrawalCreateParams from .withdrawal_list_response import WithdrawalListResponse as WithdrawalListResponse +from .ad_campaign_create_params import AdCampaignCreateParams as AdCampaignCreateParams +from .ad_campaign_list_response import AdCampaignListResponse as AdCampaignListResponse +from .ad_campaign_update_params import AdCampaignUpdateParams as AdCampaignUpdateParams from .assessment_question_types import AssessmentQuestionTypes as AssessmentQuestionTypes from .company_token_transaction import CompanyTokenTransaction as CompanyTokenTransaction from .course_lesson_list_params import CourseLessonListParams as CourseLessonListParams @@ -254,9 +283,12 @@ from .payout_method_list_params import PayoutMethodListParams as PayoutMethodListParams from .access_token_create_params import AccessTokenCreateParams as AccessTokenCreateParams from .account_link_create_params import AccountLinkCreateParams as AccountLinkCreateParams +from .ad_campaign_pause_response import AdCampaignPauseResponse as AdCampaignPauseResponse +from .ad_group_retrieve_response import AdGroupRetrieveResponse as AdGroupRetrieveResponse from .affiliate_archive_response import AffiliateArchiveResponse as AffiliateArchiveResponse from .chat_channel_list_response import ChatChannelListResponse as ChatChannelListResponse from .chat_channel_update_params import ChatChannelUpdateParams as ChatChannelUpdateParams +from .conversion_create_response import ConversionCreateResponse as ConversionCreateResponse from .course_chapter_list_params import CourseChapterListParams as CourseChapterListParams from .course_student_list_params import CourseStudentListParams as CourseStudentListParams from .dm_channel_delete_response import DmChannelDeleteResponse as DmChannelDeleteResponse @@ -272,8 +304,11 @@ from .payment_method_list_params import PaymentMethodListParams as PaymentMethodListParams from .promo_code_delete_response import PromoCodeDeleteResponse as PromoCodeDeleteResponse from .setup_intent_list_response import SetupIntentListResponse as SetupIntentListResponse +from .stat_query_metric_response import StatQueryMetricResponse as StatQueryMetricResponse from .user_check_access_response import UserCheckAccessResponse as UserCheckAccessResponse from .verification_list_response import VerificationListResponse as VerificationListResponse +from .ad_campaign_create_response import AdCampaignCreateResponse as AdCampaignCreateResponse +from .ad_campaign_update_response import AdCampaignUpdateResponse as AdCampaignUpdateResponse from .authorized_user_list_params import AuthorizedUserListParams as AuthorizedUserListParams from .course_lesson_create_params import CourseLessonCreateParams as CourseLessonCreateParams from .course_lesson_list_response import CourseLessonListResponse as CourseLessonListResponse @@ -287,6 +322,7 @@ from .support_channel_list_params import SupportChannelListParams as SupportChannelListParams from .access_token_create_response import AccessTokenCreateResponse as AccessTokenCreateResponse from .account_link_create_response import AccountLinkCreateResponse as AccountLinkCreateResponse +from .ad_campaign_unpause_response import AdCampaignUnpauseResponse as AdCampaignUnpauseResponse from .affiliate_unarchive_response import AffiliateUnarchiveResponse as AffiliateUnarchiveResponse from .course_chapter_create_params import CourseChapterCreateParams as CourseChapterCreateParams from .course_chapter_list_response import CourseChapterListResponse as CourseChapterListResponse @@ -300,9 +336,11 @@ from .payment_method_list_response import PaymentMethodListResponse as PaymentMethodListResponse from .refund_created_webhook_event import RefundCreatedWebhookEvent as RefundCreatedWebhookEvent from .refund_updated_webhook_event import RefundUpdatedWebhookEvent as RefundUpdatedWebhookEvent +from .ad_campaign_retrieve_response import AdCampaignRetrieveResponse as AdCampaignRetrieveResponse from .authorized_user_create_params import AuthorizedUserCreateParams as AuthorizedUserCreateParams from .authorized_user_delete_params import AuthorizedUserDeleteParams as AuthorizedUserDeleteParams from .authorized_user_list_response import AuthorizedUserListResponse as AuthorizedUserListResponse +from .company_create_api_key_params import CompanyCreateAPIKeyParams as CompanyCreateAPIKeyParams from .course_lesson_delete_response import CourseLessonDeleteResponse as CourseLessonDeleteResponse from .dispute_created_webhook_event import DisputeCreatedWebhookEvent as DisputeCreatedWebhookEvent from .dispute_updated_webhook_event import DisputeUpdatedWebhookEvent as DisputeUpdatedWebhookEvent @@ -319,6 +357,7 @@ from .payment_method_retrieve_params import PaymentMethodRetrieveParams as PaymentMethodRetrieveParams from .verification_retrieve_response import VerificationRetrieveResponse as VerificationRetrieveResponse from .authorized_user_delete_response import AuthorizedUserDeleteResponse as AuthorizedUserDeleteResponse +from .company_create_api_key_response import CompanyCreateAPIKeyResponse as CompanyCreateAPIKeyResponse from .dispute_alert_retrieve_response import DisputeAlertRetrieveResponse as DisputeAlertRetrieveResponse from .membership_add_free_days_params import MembershipAddFreeDaysParams as MembershipAddFreeDaysParams from .payment_succeeded_webhook_event import PaymentSucceededWebhookEvent as PaymentSucceededWebhookEvent diff --git a/src/whop_sdk/types/ad_campaign_create_params.py b/src/whop_sdk/types/ad_campaign_create_params.py new file mode 100644 index 00000000..061ab6df --- /dev/null +++ b/src/whop_sdk/types/ad_campaign_create_params.py @@ -0,0 +1,85 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Optional +from typing_extensions import Literal, Required, TypedDict + +from .._types import SequenceNotStr + +__all__ = ["AdCampaignCreateParams", "Config"] + + +class AdCampaignCreateParams(TypedDict, total=False): + company_id: Required[str] + """The company ID to create this ad campaign for.""" + + config: Required[Config] + """Unified campaign configuration (conversion goal, budget, bidding, etc.).""" + + platform: Required[Literal["meta", "tiktok"]] + """The ad platform to run on (e.g., meta, tiktok).""" + + title: Required[str] + """The title of the ad campaign. Must be max 100 characters.""" + + ad_creative_set_ids: Optional[SequenceNotStr[str]] + """Array of creative set IDs to link to this campaign.""" + + budget: Optional[float] + """Budget amount in dollars.""" + + budget_type: Optional[Literal["daily", "lifetime"]] + """The budget type for an ad campaign or ad group.""" + + daily_budget: Optional[float] + """Daily budget in dollars (minimum $5). + + Required unless lifetime_budget is set in config. + """ + + product_id: Optional[str] + """The unique identifier of the product to promote.""" + + target_country_codes: Optional[SequenceNotStr[str]] + """Array of ISO3166 country codes for territory targeting.""" + + +class Config(TypedDict, total=False): + """Unified campaign configuration (conversion goal, budget, bidding, etc.).""" + + bid_amount: Optional[int] + """Bid cap amount in cents. Only used when bid_strategy is bid_cap.""" + + bid_strategy: Optional[Literal["lowest_cost", "bid_cap", "cost_cap"]] + """The bidding strategy used to optimize spend for this campaign.""" + + budget_optimization: Optional[bool] + """ + Whether campaign budget optimization (CBO) is enabled, allowing the platform to + distribute budget across ad groups. + """ + + end_time: Optional[str] + """The scheduled end time of the campaign (ISO8601).""" + + lifetime_budget: Optional[int] + """Represents non-fractional signed whole numeric values. + + Int can represent values between -(2^31) and 2^31 - 1. + """ + + objective: Optional[Literal["awareness", "traffic", "engagement", "leads", "sales"]] + """The campaign objective that determines how Meta optimizes delivery.""" + + special_categories: Optional[SequenceNotStr[str]] + """ + Special ad categories required by the platform (e.g., housing, employment, + credit). + """ + + start_time: Optional[str] + """The scheduled start time of the campaign (ISO8601).""" + + status: Optional[Literal["active", "paused"]] + """The campaign status as set by the advertiser (active or paused).""" diff --git a/src/whop_sdk/types/ad_campaign_create_response.py b/src/whop_sdk/types/ad_campaign_create_response.py new file mode 100644 index 00000000..b269877c --- /dev/null +++ b/src/whop_sdk/types/ad_campaign_create_response.py @@ -0,0 +1,509 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Union, Optional +from datetime import datetime +from typing_extensions import Literal, Annotated, TypeAlias + +from .._utils import PropertyInfo +from .._models import BaseModel +from .card_brands import CardBrands +from .payment_method_types import PaymentMethodTypes + +__all__ = [ + "AdCampaignCreateResponse", + "BillingLedgerAccount", + "Config", + "CreatedByUser", + "PaymentMethod", + "PaymentMethodBasePaymentMethod", + "PaymentMethodCardPaymentMethod", + "PaymentMethodCardPaymentMethodCard", + "PaymentMethodUsBankAccountPaymentMethod", + "PaymentMethodUsBankAccountPaymentMethodUsBankAccount", + "PaymentMethodCashappPaymentMethod", + "PaymentMethodCashappPaymentMethodCashapp", + "PaymentMethodIdealPaymentMethod", + "PaymentMethodIdealPaymentMethodIdeal", + "PaymentMethodSepaDebitPaymentMethod", + "PaymentMethodSepaDebitPaymentMethodSepaDebit", + "Product", +] + + +class BillingLedgerAccount(BaseModel): + """ + The ledger account being charged for platform balance billing (null if using card) + """ + + id: str + """The unique identifier for the ledger account.""" + + +class Config(BaseModel): + """Meta campaign configuration (objective, budget, bidding, etc.). + + Null for non-Meta campaigns — use `tiktokConfig` for TikTok. + """ + + bid_amount: Optional[int] = None + """Bid cap amount in cents. Only used when bid_strategy is bid_cap.""" + + bid_strategy: Optional[Literal["lowest_cost", "bid_cap", "cost_cap"]] = None + """The bidding strategy used to optimize spend for this campaign.""" + + budget_optimization: Optional[bool] = None + """ + Whether campaign budget optimization (CBO) is enabled, allowing the platform to + distribute budget across ad groups. + """ + + end_time: Optional[str] = None + """The scheduled end time of the campaign (ISO8601).""" + + objective: Optional[Literal["awareness", "traffic", "engagement", "leads", "sales"]] = None + """The campaign objective that determines how Meta optimizes delivery.""" + + special_categories: Optional[List[str]] = None + """ + Special ad categories required by the platform (e.g., housing, employment, + credit). + """ + + start_time: Optional[str] = None + """The scheduled start time of the campaign (ISO8601).""" + + status: Optional[Literal["active", "paused"]] = None + """The campaign status as set by the advertiser (active or paused).""" + + +class CreatedByUser(BaseModel): + """The user who created the campaign""" + + id: str + """The unique identifier for the user.""" + + name: Optional[str] = None + """The user's display name shown on their public profile.""" + + username: str + """The user's unique username shown on their public profile.""" + + +class PaymentMethodBasePaymentMethod(BaseModel): + """A saved payment method with no type-specific details available.""" + + id: str + """Represents a unique identifier that is Base64 obfuscated. + + It is often used to refetch an object or as key for a cache. The ID type appears + in a JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such as + `"VXNlci0xMA=="`) or integer (such as `4`) input value will be accepted as an + ID. + """ + + created_at: datetime + """The time of the event in ISO 8601 UTC format with millisecond precision""" + + payment_method_type: PaymentMethodTypes + """ + The type of payment instrument stored on file (e.g., card, us_bank_account, + cashapp, ideal, sepa_debit). + """ + + typename: Literal["BasePaymentMethod"] + """The typename of this object""" + + +class PaymentMethodCardPaymentMethodCard(BaseModel): + """ + The card-specific details for this payment method, including brand, last four digits, and expiration. + """ + + brand: Optional[CardBrands] = None + """Possible card brands that a payment token can have""" + + exp_month: Optional[int] = None + """The two-digit expiration month of the card (1-12). Null if not available.""" + + exp_year: Optional[int] = None + """The two-digit expiration year of the card (e.g., 27 for 2027). + + Null if not available. + """ + + last4: Optional[str] = None + """The last four digits of the card number. Null if not available.""" + + +class PaymentMethodCardPaymentMethod(BaseModel): + """ + A saved card payment method, including brand, last four digits, and expiration details. + """ + + id: str + """Represents a unique identifier that is Base64 obfuscated. + + It is often used to refetch an object or as key for a cache. The ID type appears + in a JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such as + `"VXNlci0xMA=="`) or integer (such as `4`) input value will be accepted as an + ID. + """ + + card: PaymentMethodCardPaymentMethodCard + """ + The card-specific details for this payment method, including brand, last four + digits, and expiration. + """ + + created_at: datetime + """The time of the event in ISO 8601 UTC format with millisecond precision""" + + payment_method_type: PaymentMethodTypes + """ + The type of payment instrument stored on file (e.g., card, us_bank_account, + cashapp, ideal, sepa_debit). + """ + + typename: Literal["CardPaymentMethod"] + """The typename of this object""" + + +class PaymentMethodUsBankAccountPaymentMethodUsBankAccount(BaseModel): + """ + The bank account-specific details for this payment method, including bank name and last four digits. + """ + + account_type: str + """The type of bank account (e.g., checking, savings).""" + + bank_name: str + """The name of the financial institution holding the account.""" + + last4: str + """The last four digits of the bank account number.""" + + +class PaymentMethodUsBankAccountPaymentMethod(BaseModel): + """ + A saved US bank account payment method, including bank name, last four digits, and account type. + """ + + id: str + """Represents a unique identifier that is Base64 obfuscated. + + It is often used to refetch an object or as key for a cache. The ID type appears + in a JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such as + `"VXNlci0xMA=="`) or integer (such as `4`) input value will be accepted as an + ID. + """ + + created_at: datetime + """The time of the event in ISO 8601 UTC format with millisecond precision""" + + payment_method_type: PaymentMethodTypes + """ + The type of payment instrument stored on file (e.g., card, us_bank_account, + cashapp, ideal, sepa_debit). + """ + + typename: Literal["UsBankAccountPaymentMethod"] + """The typename of this object""" + + us_bank_account: PaymentMethodUsBankAccountPaymentMethodUsBankAccount + """ + The bank account-specific details for this payment method, including bank name + and last four digits. + """ + + +class PaymentMethodCashappPaymentMethodCashapp(BaseModel): + """ + The Cash App-specific details for this payment method, including cashtag and buyer ID. + """ + + buyer_id: Optional[str] = None + """The unique and immutable identifier assigned by Cash App to the buyer. + + Null if not available. + """ + + cashtag: Optional[str] = None + """The public cashtag handle of the buyer on Cash App. Null if not available.""" + + +class PaymentMethodCashappPaymentMethod(BaseModel): + """ + A saved Cash App payment method, including the buyer's cashtag and unique identifier. + """ + + id: str + """Represents a unique identifier that is Base64 obfuscated. + + It is often used to refetch an object or as key for a cache. The ID type appears + in a JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such as + `"VXNlci0xMA=="`) or integer (such as `4`) input value will be accepted as an + ID. + """ + + cashapp: PaymentMethodCashappPaymentMethodCashapp + """ + The Cash App-specific details for this payment method, including cashtag and + buyer ID. + """ + + created_at: datetime + """The time of the event in ISO 8601 UTC format with millisecond precision""" + + payment_method_type: PaymentMethodTypes + """ + The type of payment instrument stored on file (e.g., card, us_bank_account, + cashapp, ideal, sepa_debit). + """ + + typename: Literal["CashappPaymentMethod"] + """The typename of this object""" + + +class PaymentMethodIdealPaymentMethodIdeal(BaseModel): + """ + The iDEAL-specific details for this payment method, including bank name and BIC. + """ + + bank: Optional[str] = None + """The name of the customer's bank used for the iDEAL transaction. + + Null if not available. + """ + + bic: Optional[str] = None + """The Bank Identifier Code (BIC/SWIFT) of the customer's bank. + + Null if not available. + """ + + +class PaymentMethodIdealPaymentMethod(BaseModel): + """A saved iDEAL payment method, including the customer's bank name and BIC code.""" + + id: str + """Represents a unique identifier that is Base64 obfuscated. + + It is often used to refetch an object or as key for a cache. The ID type appears + in a JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such as + `"VXNlci0xMA=="`) or integer (such as `4`) input value will be accepted as an + ID. + """ + + created_at: datetime + """The time of the event in ISO 8601 UTC format with millisecond precision""" + + ideal: PaymentMethodIdealPaymentMethodIdeal + """ + The iDEAL-specific details for this payment method, including bank name and BIC. + """ + + payment_method_type: PaymentMethodTypes + """ + The type of payment instrument stored on file (e.g., card, us_bank_account, + cashapp, ideal, sepa_debit). + """ + + typename: Literal["IdealPaymentMethod"] + """The typename of this object""" + + +class PaymentMethodSepaDebitPaymentMethodSepaDebit(BaseModel): + """ + The SEPA Direct Debit-specific details for this payment method, including bank code and last four IBAN digits. + """ + + bank_code: Optional[str] = None + """The bank code of the financial institution associated with this SEPA account. + + Null if not available. + """ + + branch_code: Optional[str] = None + """The branch code of the financial institution associated with this SEPA account. + + Null if not available. + """ + + country: Optional[str] = None + """The two-letter ISO country code where the bank account is located. + + Null if not available. + """ + + last4: Optional[str] = None + """The last four digits of the IBAN associated with this SEPA account. + + Null if not available. + """ + + +class PaymentMethodSepaDebitPaymentMethod(BaseModel): + """ + A saved SEPA Direct Debit payment method, including the bank code, country, and last four IBAN digits. + """ + + id: str + """Represents a unique identifier that is Base64 obfuscated. + + It is often used to refetch an object or as key for a cache. The ID type appears + in a JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such as + `"VXNlci0xMA=="`) or integer (such as `4`) input value will be accepted as an + ID. + """ + + created_at: datetime + """The time of the event in ISO 8601 UTC format with millisecond precision""" + + payment_method_type: PaymentMethodTypes + """ + The type of payment instrument stored on file (e.g., card, us_bank_account, + cashapp, ideal, sepa_debit). + """ + + sepa_debit: PaymentMethodSepaDebitPaymentMethodSepaDebit + """ + The SEPA Direct Debit-specific details for this payment method, including bank + code and last four IBAN digits. + """ + + typename: Literal["SepaDebitPaymentMethod"] + """The typename of this object""" + + +PaymentMethod: TypeAlias = Annotated[ + Union[ + Optional[PaymentMethodBasePaymentMethod], + Optional[PaymentMethodCardPaymentMethod], + Optional[PaymentMethodUsBankAccountPaymentMethod], + Optional[PaymentMethodCashappPaymentMethod], + Optional[PaymentMethodIdealPaymentMethod], + Optional[PaymentMethodSepaDebitPaymentMethod], + ], + PropertyInfo(discriminator="typename"), +] + + +class Product(BaseModel): + """The access pass being promoted. + + Null for campaigns that don't target a specific product. + """ + + id: str + """The unique identifier for the product.""" + + route: str + """ + The URL slug used in the product's public link (e.g., 'my-product' in + whop.com/company/my-product). + """ + + title: str + """ + The display name of the product shown to customers on the product page and in + search results. + """ + + +class AdCampaignCreateResponse(BaseModel): + """An advertising campaign running on an external platform or within Whop.""" + + id: str + """The unique identifier for the ad campaign.""" + + available_budget: float + """ + Available budget in dollars, capped at daily budget minus today's spend for + daily campaigns + """ + + billing_ledger_account: Optional[BillingLedgerAccount] = None + """ + The ledger account being charged for platform balance billing (null if using + card) + """ + + clicks_count: int + """Number of clicks""" + + config: Optional[Config] = None + """Meta campaign configuration (objective, budget, bidding, etc.). + + Null for non-Meta campaigns — use `tiktokConfig` for TikTok. + """ + + created_at: datetime + """The datetime the ad campaign was created.""" + + created_by_user: CreatedByUser + """The user who created the campaign""" + + daily_budget: Optional[float] = None + """ + Effective daily budget in dollars — sum of ad group budgets when set, otherwise + campaign-level daily budget + """ + + impressions_count: int + """Number of impressions (views)""" + + paused_until: Optional[datetime] = None + """If temporarily paused, the timestamp when the campaign will auto-resume""" + + payment_method: PaymentMethod + """The payment method used for daily billing (null if using platform balance)""" + + platform: Optional[Literal["meta", "tiktok"]] = None + """The platforms where an ad campaign can run.""" + + product: Optional[Product] = None + """The access pass being promoted. + + Null for campaigns that don't target a specific product. + """ + + purchases_count: int + """Number of purchases""" + + remaining_balance: float + """Remaining balance in dollars""" + + return_on_ad_spend: float + """Return on Ad Spend (ROAS) percentage - revenue generated divided by ad spend""" + + revenue: float + """Total revenue generated from users who converted through this campaign""" + + status: Literal[ + "active", "paused", "inactive", "stale", "pending_refund", "payment_failed", "draft", "in_review", "flagged" + ] + """Current status of the campaign (active, paused, or inactive)""" + + target_country_codes: List[str] + """Array of ISO3166 country codes for territory targeting""" + + title: str + """The title of the ad campaign""" + + todays_spend: float + """Amount spent today in dollars""" + + total_credits: float + """Total credits added to the campaign in dollars""" + + total_spend: float + """Total amount spent on conversions in dollars""" + + updated_at: datetime + """The datetime the ad campaign was last updated.""" diff --git a/src/whop_sdk/types/ad_campaign_list_params.py b/src/whop_sdk/types/ad_campaign_list_params.py new file mode 100644 index 00000000..85585e1d --- /dev/null +++ b/src/whop_sdk/types/ad_campaign_list_params.py @@ -0,0 +1,44 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Union, Optional +from datetime import datetime +from typing_extensions import Literal, Required, Annotated, TypedDict + +from .._utils import PropertyInfo + +__all__ = ["AdCampaignListParams"] + + +class AdCampaignListParams(TypedDict, total=False): + company_id: Required[str] + """The unique identifier of the company to list ad campaigns for.""" + + after: Optional[str] + """Returns the elements in the list that come after the specified cursor.""" + + before: Optional[str] + """Returns the elements in the list that come before the specified cursor.""" + + created_after: Annotated[Union[str, datetime, None], PropertyInfo(format="iso8601")] + """Only return ad campaigns created after this timestamp.""" + + created_before: Annotated[Union[str, datetime, None], PropertyInfo(format="iso8601")] + """Only return ad campaigns created before this timestamp.""" + + first: Optional[int] + """Returns the first _n_ elements from the list.""" + + last: Optional[int] + """Returns the last _n_ elements from the list.""" + + query: Optional[str] + """Case-insensitive substring match against the campaign title.""" + + status: Optional[ + Literal[ + "active", "paused", "inactive", "stale", "pending_refund", "payment_failed", "draft", "in_review", "flagged" + ] + ] + """The status of an ad campaign.""" diff --git a/src/whop_sdk/types/ad_campaign_list_response.py b/src/whop_sdk/types/ad_campaign_list_response.py new file mode 100644 index 00000000..5d623574 --- /dev/null +++ b/src/whop_sdk/types/ad_campaign_list_response.py @@ -0,0 +1,106 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Optional +from datetime import datetime +from typing_extensions import Literal + +from .._models import BaseModel + +__all__ = ["AdCampaignListResponse", "Product"] + + +class Product(BaseModel): + """The access pass being promoted. + + Null for campaigns that don't target a specific product. + """ + + id: str + """The unique identifier for the product.""" + + route: str + """ + The URL slug used in the product's public link (e.g., 'my-product' in + whop.com/company/my-product). + """ + + title: str + """ + The display name of the product shown to customers on the product page and in + search results. + """ + + +class AdCampaignListResponse(BaseModel): + """An advertising campaign running on an external platform or within Whop.""" + + id: str + """The unique identifier for the ad campaign.""" + + available_budget: float + """ + Available budget in dollars, capped at daily budget minus today's spend for + daily campaigns + """ + + clicks_count: int + """Number of clicks""" + + created_at: datetime + """The datetime the ad campaign was created.""" + + daily_budget: Optional[float] = None + """ + Effective daily budget in dollars — sum of ad group budgets when set, otherwise + campaign-level daily budget + """ + + impressions_count: int + """Number of impressions (views)""" + + paused_until: Optional[datetime] = None + """If temporarily paused, the timestamp when the campaign will auto-resume""" + + platform: Optional[Literal["meta", "tiktok"]] = None + """The platforms where an ad campaign can run.""" + + product: Optional[Product] = None + """The access pass being promoted. + + Null for campaigns that don't target a specific product. + """ + + purchases_count: int + """Number of purchases""" + + remaining_balance: float + """Remaining balance in dollars""" + + return_on_ad_spend: float + """Return on Ad Spend (ROAS) percentage - revenue generated divided by ad spend""" + + revenue: float + """Total revenue generated from users who converted through this campaign""" + + status: Literal[ + "active", "paused", "inactive", "stale", "pending_refund", "payment_failed", "draft", "in_review", "flagged" + ] + """Current status of the campaign (active, paused, or inactive)""" + + target_country_codes: List[str] + """Array of ISO3166 country codes for territory targeting""" + + title: str + """The title of the ad campaign""" + + todays_spend: float + """Amount spent today in dollars""" + + total_credits: float + """Total credits added to the campaign in dollars""" + + total_spend: float + """Total amount spent on conversions in dollars""" + + updated_at: datetime + """The datetime the ad campaign was last updated.""" diff --git a/src/whop_sdk/types/ad_campaign_pause_response.py b/src/whop_sdk/types/ad_campaign_pause_response.py new file mode 100644 index 00000000..116715e7 --- /dev/null +++ b/src/whop_sdk/types/ad_campaign_pause_response.py @@ -0,0 +1,509 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Union, Optional +from datetime import datetime +from typing_extensions import Literal, Annotated, TypeAlias + +from .._utils import PropertyInfo +from .._models import BaseModel +from .card_brands import CardBrands +from .payment_method_types import PaymentMethodTypes + +__all__ = [ + "AdCampaignPauseResponse", + "BillingLedgerAccount", + "Config", + "CreatedByUser", + "PaymentMethod", + "PaymentMethodBasePaymentMethod", + "PaymentMethodCardPaymentMethod", + "PaymentMethodCardPaymentMethodCard", + "PaymentMethodUsBankAccountPaymentMethod", + "PaymentMethodUsBankAccountPaymentMethodUsBankAccount", + "PaymentMethodCashappPaymentMethod", + "PaymentMethodCashappPaymentMethodCashapp", + "PaymentMethodIdealPaymentMethod", + "PaymentMethodIdealPaymentMethodIdeal", + "PaymentMethodSepaDebitPaymentMethod", + "PaymentMethodSepaDebitPaymentMethodSepaDebit", + "Product", +] + + +class BillingLedgerAccount(BaseModel): + """ + The ledger account being charged for platform balance billing (null if using card) + """ + + id: str + """The unique identifier for the ledger account.""" + + +class Config(BaseModel): + """Meta campaign configuration (objective, budget, bidding, etc.). + + Null for non-Meta campaigns — use `tiktokConfig` for TikTok. + """ + + bid_amount: Optional[int] = None + """Bid cap amount in cents. Only used when bid_strategy is bid_cap.""" + + bid_strategy: Optional[Literal["lowest_cost", "bid_cap", "cost_cap"]] = None + """The bidding strategy used to optimize spend for this campaign.""" + + budget_optimization: Optional[bool] = None + """ + Whether campaign budget optimization (CBO) is enabled, allowing the platform to + distribute budget across ad groups. + """ + + end_time: Optional[str] = None + """The scheduled end time of the campaign (ISO8601).""" + + objective: Optional[Literal["awareness", "traffic", "engagement", "leads", "sales"]] = None + """The campaign objective that determines how Meta optimizes delivery.""" + + special_categories: Optional[List[str]] = None + """ + Special ad categories required by the platform (e.g., housing, employment, + credit). + """ + + start_time: Optional[str] = None + """The scheduled start time of the campaign (ISO8601).""" + + status: Optional[Literal["active", "paused"]] = None + """The campaign status as set by the advertiser (active or paused).""" + + +class CreatedByUser(BaseModel): + """The user who created the campaign""" + + id: str + """The unique identifier for the user.""" + + name: Optional[str] = None + """The user's display name shown on their public profile.""" + + username: str + """The user's unique username shown on their public profile.""" + + +class PaymentMethodBasePaymentMethod(BaseModel): + """A saved payment method with no type-specific details available.""" + + id: str + """Represents a unique identifier that is Base64 obfuscated. + + It is often used to refetch an object or as key for a cache. The ID type appears + in a JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such as + `"VXNlci0xMA=="`) or integer (such as `4`) input value will be accepted as an + ID. + """ + + created_at: datetime + """The time of the event in ISO 8601 UTC format with millisecond precision""" + + payment_method_type: PaymentMethodTypes + """ + The type of payment instrument stored on file (e.g., card, us_bank_account, + cashapp, ideal, sepa_debit). + """ + + typename: Literal["BasePaymentMethod"] + """The typename of this object""" + + +class PaymentMethodCardPaymentMethodCard(BaseModel): + """ + The card-specific details for this payment method, including brand, last four digits, and expiration. + """ + + brand: Optional[CardBrands] = None + """Possible card brands that a payment token can have""" + + exp_month: Optional[int] = None + """The two-digit expiration month of the card (1-12). Null if not available.""" + + exp_year: Optional[int] = None + """The two-digit expiration year of the card (e.g., 27 for 2027). + + Null if not available. + """ + + last4: Optional[str] = None + """The last four digits of the card number. Null if not available.""" + + +class PaymentMethodCardPaymentMethod(BaseModel): + """ + A saved card payment method, including brand, last four digits, and expiration details. + """ + + id: str + """Represents a unique identifier that is Base64 obfuscated. + + It is often used to refetch an object or as key for a cache. The ID type appears + in a JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such as + `"VXNlci0xMA=="`) or integer (such as `4`) input value will be accepted as an + ID. + """ + + card: PaymentMethodCardPaymentMethodCard + """ + The card-specific details for this payment method, including brand, last four + digits, and expiration. + """ + + created_at: datetime + """The time of the event in ISO 8601 UTC format with millisecond precision""" + + payment_method_type: PaymentMethodTypes + """ + The type of payment instrument stored on file (e.g., card, us_bank_account, + cashapp, ideal, sepa_debit). + """ + + typename: Literal["CardPaymentMethod"] + """The typename of this object""" + + +class PaymentMethodUsBankAccountPaymentMethodUsBankAccount(BaseModel): + """ + The bank account-specific details for this payment method, including bank name and last four digits. + """ + + account_type: str + """The type of bank account (e.g., checking, savings).""" + + bank_name: str + """The name of the financial institution holding the account.""" + + last4: str + """The last four digits of the bank account number.""" + + +class PaymentMethodUsBankAccountPaymentMethod(BaseModel): + """ + A saved US bank account payment method, including bank name, last four digits, and account type. + """ + + id: str + """Represents a unique identifier that is Base64 obfuscated. + + It is often used to refetch an object or as key for a cache. The ID type appears + in a JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such as + `"VXNlci0xMA=="`) or integer (such as `4`) input value will be accepted as an + ID. + """ + + created_at: datetime + """The time of the event in ISO 8601 UTC format with millisecond precision""" + + payment_method_type: PaymentMethodTypes + """ + The type of payment instrument stored on file (e.g., card, us_bank_account, + cashapp, ideal, sepa_debit). + """ + + typename: Literal["UsBankAccountPaymentMethod"] + """The typename of this object""" + + us_bank_account: PaymentMethodUsBankAccountPaymentMethodUsBankAccount + """ + The bank account-specific details for this payment method, including bank name + and last four digits. + """ + + +class PaymentMethodCashappPaymentMethodCashapp(BaseModel): + """ + The Cash App-specific details for this payment method, including cashtag and buyer ID. + """ + + buyer_id: Optional[str] = None + """The unique and immutable identifier assigned by Cash App to the buyer. + + Null if not available. + """ + + cashtag: Optional[str] = None + """The public cashtag handle of the buyer on Cash App. Null if not available.""" + + +class PaymentMethodCashappPaymentMethod(BaseModel): + """ + A saved Cash App payment method, including the buyer's cashtag and unique identifier. + """ + + id: str + """Represents a unique identifier that is Base64 obfuscated. + + It is often used to refetch an object or as key for a cache. The ID type appears + in a JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such as + `"VXNlci0xMA=="`) or integer (such as `4`) input value will be accepted as an + ID. + """ + + cashapp: PaymentMethodCashappPaymentMethodCashapp + """ + The Cash App-specific details for this payment method, including cashtag and + buyer ID. + """ + + created_at: datetime + """The time of the event in ISO 8601 UTC format with millisecond precision""" + + payment_method_type: PaymentMethodTypes + """ + The type of payment instrument stored on file (e.g., card, us_bank_account, + cashapp, ideal, sepa_debit). + """ + + typename: Literal["CashappPaymentMethod"] + """The typename of this object""" + + +class PaymentMethodIdealPaymentMethodIdeal(BaseModel): + """ + The iDEAL-specific details for this payment method, including bank name and BIC. + """ + + bank: Optional[str] = None + """The name of the customer's bank used for the iDEAL transaction. + + Null if not available. + """ + + bic: Optional[str] = None + """The Bank Identifier Code (BIC/SWIFT) of the customer's bank. + + Null if not available. + """ + + +class PaymentMethodIdealPaymentMethod(BaseModel): + """A saved iDEAL payment method, including the customer's bank name and BIC code.""" + + id: str + """Represents a unique identifier that is Base64 obfuscated. + + It is often used to refetch an object or as key for a cache. The ID type appears + in a JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such as + `"VXNlci0xMA=="`) or integer (such as `4`) input value will be accepted as an + ID. + """ + + created_at: datetime + """The time of the event in ISO 8601 UTC format with millisecond precision""" + + ideal: PaymentMethodIdealPaymentMethodIdeal + """ + The iDEAL-specific details for this payment method, including bank name and BIC. + """ + + payment_method_type: PaymentMethodTypes + """ + The type of payment instrument stored on file (e.g., card, us_bank_account, + cashapp, ideal, sepa_debit). + """ + + typename: Literal["IdealPaymentMethod"] + """The typename of this object""" + + +class PaymentMethodSepaDebitPaymentMethodSepaDebit(BaseModel): + """ + The SEPA Direct Debit-specific details for this payment method, including bank code and last four IBAN digits. + """ + + bank_code: Optional[str] = None + """The bank code of the financial institution associated with this SEPA account. + + Null if not available. + """ + + branch_code: Optional[str] = None + """The branch code of the financial institution associated with this SEPA account. + + Null if not available. + """ + + country: Optional[str] = None + """The two-letter ISO country code where the bank account is located. + + Null if not available. + """ + + last4: Optional[str] = None + """The last four digits of the IBAN associated with this SEPA account. + + Null if not available. + """ + + +class PaymentMethodSepaDebitPaymentMethod(BaseModel): + """ + A saved SEPA Direct Debit payment method, including the bank code, country, and last four IBAN digits. + """ + + id: str + """Represents a unique identifier that is Base64 obfuscated. + + It is often used to refetch an object or as key for a cache. The ID type appears + in a JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such as + `"VXNlci0xMA=="`) or integer (such as `4`) input value will be accepted as an + ID. + """ + + created_at: datetime + """The time of the event in ISO 8601 UTC format with millisecond precision""" + + payment_method_type: PaymentMethodTypes + """ + The type of payment instrument stored on file (e.g., card, us_bank_account, + cashapp, ideal, sepa_debit). + """ + + sepa_debit: PaymentMethodSepaDebitPaymentMethodSepaDebit + """ + The SEPA Direct Debit-specific details for this payment method, including bank + code and last four IBAN digits. + """ + + typename: Literal["SepaDebitPaymentMethod"] + """The typename of this object""" + + +PaymentMethod: TypeAlias = Annotated[ + Union[ + Optional[PaymentMethodBasePaymentMethod], + Optional[PaymentMethodCardPaymentMethod], + Optional[PaymentMethodUsBankAccountPaymentMethod], + Optional[PaymentMethodCashappPaymentMethod], + Optional[PaymentMethodIdealPaymentMethod], + Optional[PaymentMethodSepaDebitPaymentMethod], + ], + PropertyInfo(discriminator="typename"), +] + + +class Product(BaseModel): + """The access pass being promoted. + + Null for campaigns that don't target a specific product. + """ + + id: str + """The unique identifier for the product.""" + + route: str + """ + The URL slug used in the product's public link (e.g., 'my-product' in + whop.com/company/my-product). + """ + + title: str + """ + The display name of the product shown to customers on the product page and in + search results. + """ + + +class AdCampaignPauseResponse(BaseModel): + """An advertising campaign running on an external platform or within Whop.""" + + id: str + """The unique identifier for the ad campaign.""" + + available_budget: float + """ + Available budget in dollars, capped at daily budget minus today's spend for + daily campaigns + """ + + billing_ledger_account: Optional[BillingLedgerAccount] = None + """ + The ledger account being charged for platform balance billing (null if using + card) + """ + + clicks_count: int + """Number of clicks""" + + config: Optional[Config] = None + """Meta campaign configuration (objective, budget, bidding, etc.). + + Null for non-Meta campaigns — use `tiktokConfig` for TikTok. + """ + + created_at: datetime + """The datetime the ad campaign was created.""" + + created_by_user: CreatedByUser + """The user who created the campaign""" + + daily_budget: Optional[float] = None + """ + Effective daily budget in dollars — sum of ad group budgets when set, otherwise + campaign-level daily budget + """ + + impressions_count: int + """Number of impressions (views)""" + + paused_until: Optional[datetime] = None + """If temporarily paused, the timestamp when the campaign will auto-resume""" + + payment_method: PaymentMethod + """The payment method used for daily billing (null if using platform balance)""" + + platform: Optional[Literal["meta", "tiktok"]] = None + """The platforms where an ad campaign can run.""" + + product: Optional[Product] = None + """The access pass being promoted. + + Null for campaigns that don't target a specific product. + """ + + purchases_count: int + """Number of purchases""" + + remaining_balance: float + """Remaining balance in dollars""" + + return_on_ad_spend: float + """Return on Ad Spend (ROAS) percentage - revenue generated divided by ad spend""" + + revenue: float + """Total revenue generated from users who converted through this campaign""" + + status: Literal[ + "active", "paused", "inactive", "stale", "pending_refund", "payment_failed", "draft", "in_review", "flagged" + ] + """Current status of the campaign (active, paused, or inactive)""" + + target_country_codes: List[str] + """Array of ISO3166 country codes for territory targeting""" + + title: str + """The title of the ad campaign""" + + todays_spend: float + """Amount spent today in dollars""" + + total_credits: float + """Total credits added to the campaign in dollars""" + + total_spend: float + """Total amount spent on conversions in dollars""" + + updated_at: datetime + """The datetime the ad campaign was last updated.""" diff --git a/src/whop_sdk/types/ad_campaign_retrieve_response.py b/src/whop_sdk/types/ad_campaign_retrieve_response.py new file mode 100644 index 00000000..52769385 --- /dev/null +++ b/src/whop_sdk/types/ad_campaign_retrieve_response.py @@ -0,0 +1,509 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Union, Optional +from datetime import datetime +from typing_extensions import Literal, Annotated, TypeAlias + +from .._utils import PropertyInfo +from .._models import BaseModel +from .card_brands import CardBrands +from .payment_method_types import PaymentMethodTypes + +__all__ = [ + "AdCampaignRetrieveResponse", + "BillingLedgerAccount", + "Config", + "CreatedByUser", + "PaymentMethod", + "PaymentMethodBasePaymentMethod", + "PaymentMethodCardPaymentMethod", + "PaymentMethodCardPaymentMethodCard", + "PaymentMethodUsBankAccountPaymentMethod", + "PaymentMethodUsBankAccountPaymentMethodUsBankAccount", + "PaymentMethodCashappPaymentMethod", + "PaymentMethodCashappPaymentMethodCashapp", + "PaymentMethodIdealPaymentMethod", + "PaymentMethodIdealPaymentMethodIdeal", + "PaymentMethodSepaDebitPaymentMethod", + "PaymentMethodSepaDebitPaymentMethodSepaDebit", + "Product", +] + + +class BillingLedgerAccount(BaseModel): + """ + The ledger account being charged for platform balance billing (null if using card) + """ + + id: str + """The unique identifier for the ledger account.""" + + +class Config(BaseModel): + """Meta campaign configuration (objective, budget, bidding, etc.). + + Null for non-Meta campaigns — use `tiktokConfig` for TikTok. + """ + + bid_amount: Optional[int] = None + """Bid cap amount in cents. Only used when bid_strategy is bid_cap.""" + + bid_strategy: Optional[Literal["lowest_cost", "bid_cap", "cost_cap"]] = None + """The bidding strategy used to optimize spend for this campaign.""" + + budget_optimization: Optional[bool] = None + """ + Whether campaign budget optimization (CBO) is enabled, allowing the platform to + distribute budget across ad groups. + """ + + end_time: Optional[str] = None + """The scheduled end time of the campaign (ISO8601).""" + + objective: Optional[Literal["awareness", "traffic", "engagement", "leads", "sales"]] = None + """The campaign objective that determines how Meta optimizes delivery.""" + + special_categories: Optional[List[str]] = None + """ + Special ad categories required by the platform (e.g., housing, employment, + credit). + """ + + start_time: Optional[str] = None + """The scheduled start time of the campaign (ISO8601).""" + + status: Optional[Literal["active", "paused"]] = None + """The campaign status as set by the advertiser (active or paused).""" + + +class CreatedByUser(BaseModel): + """The user who created the campaign""" + + id: str + """The unique identifier for the user.""" + + name: Optional[str] = None + """The user's display name shown on their public profile.""" + + username: str + """The user's unique username shown on their public profile.""" + + +class PaymentMethodBasePaymentMethod(BaseModel): + """A saved payment method with no type-specific details available.""" + + id: str + """Represents a unique identifier that is Base64 obfuscated. + + It is often used to refetch an object or as key for a cache. The ID type appears + in a JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such as + `"VXNlci0xMA=="`) or integer (such as `4`) input value will be accepted as an + ID. + """ + + created_at: datetime + """The time of the event in ISO 8601 UTC format with millisecond precision""" + + payment_method_type: PaymentMethodTypes + """ + The type of payment instrument stored on file (e.g., card, us_bank_account, + cashapp, ideal, sepa_debit). + """ + + typename: Literal["BasePaymentMethod"] + """The typename of this object""" + + +class PaymentMethodCardPaymentMethodCard(BaseModel): + """ + The card-specific details for this payment method, including brand, last four digits, and expiration. + """ + + brand: Optional[CardBrands] = None + """Possible card brands that a payment token can have""" + + exp_month: Optional[int] = None + """The two-digit expiration month of the card (1-12). Null if not available.""" + + exp_year: Optional[int] = None + """The two-digit expiration year of the card (e.g., 27 for 2027). + + Null if not available. + """ + + last4: Optional[str] = None + """The last four digits of the card number. Null if not available.""" + + +class PaymentMethodCardPaymentMethod(BaseModel): + """ + A saved card payment method, including brand, last four digits, and expiration details. + """ + + id: str + """Represents a unique identifier that is Base64 obfuscated. + + It is often used to refetch an object or as key for a cache. The ID type appears + in a JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such as + `"VXNlci0xMA=="`) or integer (such as `4`) input value will be accepted as an + ID. + """ + + card: PaymentMethodCardPaymentMethodCard + """ + The card-specific details for this payment method, including brand, last four + digits, and expiration. + """ + + created_at: datetime + """The time of the event in ISO 8601 UTC format with millisecond precision""" + + payment_method_type: PaymentMethodTypes + """ + The type of payment instrument stored on file (e.g., card, us_bank_account, + cashapp, ideal, sepa_debit). + """ + + typename: Literal["CardPaymentMethod"] + """The typename of this object""" + + +class PaymentMethodUsBankAccountPaymentMethodUsBankAccount(BaseModel): + """ + The bank account-specific details for this payment method, including bank name and last four digits. + """ + + account_type: str + """The type of bank account (e.g., checking, savings).""" + + bank_name: str + """The name of the financial institution holding the account.""" + + last4: str + """The last four digits of the bank account number.""" + + +class PaymentMethodUsBankAccountPaymentMethod(BaseModel): + """ + A saved US bank account payment method, including bank name, last four digits, and account type. + """ + + id: str + """Represents a unique identifier that is Base64 obfuscated. + + It is often used to refetch an object or as key for a cache. The ID type appears + in a JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such as + `"VXNlci0xMA=="`) or integer (such as `4`) input value will be accepted as an + ID. + """ + + created_at: datetime + """The time of the event in ISO 8601 UTC format with millisecond precision""" + + payment_method_type: PaymentMethodTypes + """ + The type of payment instrument stored on file (e.g., card, us_bank_account, + cashapp, ideal, sepa_debit). + """ + + typename: Literal["UsBankAccountPaymentMethod"] + """The typename of this object""" + + us_bank_account: PaymentMethodUsBankAccountPaymentMethodUsBankAccount + """ + The bank account-specific details for this payment method, including bank name + and last four digits. + """ + + +class PaymentMethodCashappPaymentMethodCashapp(BaseModel): + """ + The Cash App-specific details for this payment method, including cashtag and buyer ID. + """ + + buyer_id: Optional[str] = None + """The unique and immutable identifier assigned by Cash App to the buyer. + + Null if not available. + """ + + cashtag: Optional[str] = None + """The public cashtag handle of the buyer on Cash App. Null if not available.""" + + +class PaymentMethodCashappPaymentMethod(BaseModel): + """ + A saved Cash App payment method, including the buyer's cashtag and unique identifier. + """ + + id: str + """Represents a unique identifier that is Base64 obfuscated. + + It is often used to refetch an object or as key for a cache. The ID type appears + in a JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such as + `"VXNlci0xMA=="`) or integer (such as `4`) input value will be accepted as an + ID. + """ + + cashapp: PaymentMethodCashappPaymentMethodCashapp + """ + The Cash App-specific details for this payment method, including cashtag and + buyer ID. + """ + + created_at: datetime + """The time of the event in ISO 8601 UTC format with millisecond precision""" + + payment_method_type: PaymentMethodTypes + """ + The type of payment instrument stored on file (e.g., card, us_bank_account, + cashapp, ideal, sepa_debit). + """ + + typename: Literal["CashappPaymentMethod"] + """The typename of this object""" + + +class PaymentMethodIdealPaymentMethodIdeal(BaseModel): + """ + The iDEAL-specific details for this payment method, including bank name and BIC. + """ + + bank: Optional[str] = None + """The name of the customer's bank used for the iDEAL transaction. + + Null if not available. + """ + + bic: Optional[str] = None + """The Bank Identifier Code (BIC/SWIFT) of the customer's bank. + + Null if not available. + """ + + +class PaymentMethodIdealPaymentMethod(BaseModel): + """A saved iDEAL payment method, including the customer's bank name and BIC code.""" + + id: str + """Represents a unique identifier that is Base64 obfuscated. + + It is often used to refetch an object or as key for a cache. The ID type appears + in a JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such as + `"VXNlci0xMA=="`) or integer (such as `4`) input value will be accepted as an + ID. + """ + + created_at: datetime + """The time of the event in ISO 8601 UTC format with millisecond precision""" + + ideal: PaymentMethodIdealPaymentMethodIdeal + """ + The iDEAL-specific details for this payment method, including bank name and BIC. + """ + + payment_method_type: PaymentMethodTypes + """ + The type of payment instrument stored on file (e.g., card, us_bank_account, + cashapp, ideal, sepa_debit). + """ + + typename: Literal["IdealPaymentMethod"] + """The typename of this object""" + + +class PaymentMethodSepaDebitPaymentMethodSepaDebit(BaseModel): + """ + The SEPA Direct Debit-specific details for this payment method, including bank code and last four IBAN digits. + """ + + bank_code: Optional[str] = None + """The bank code of the financial institution associated with this SEPA account. + + Null if not available. + """ + + branch_code: Optional[str] = None + """The branch code of the financial institution associated with this SEPA account. + + Null if not available. + """ + + country: Optional[str] = None + """The two-letter ISO country code where the bank account is located. + + Null if not available. + """ + + last4: Optional[str] = None + """The last four digits of the IBAN associated with this SEPA account. + + Null if not available. + """ + + +class PaymentMethodSepaDebitPaymentMethod(BaseModel): + """ + A saved SEPA Direct Debit payment method, including the bank code, country, and last four IBAN digits. + """ + + id: str + """Represents a unique identifier that is Base64 obfuscated. + + It is often used to refetch an object or as key for a cache. The ID type appears + in a JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such as + `"VXNlci0xMA=="`) or integer (such as `4`) input value will be accepted as an + ID. + """ + + created_at: datetime + """The time of the event in ISO 8601 UTC format with millisecond precision""" + + payment_method_type: PaymentMethodTypes + """ + The type of payment instrument stored on file (e.g., card, us_bank_account, + cashapp, ideal, sepa_debit). + """ + + sepa_debit: PaymentMethodSepaDebitPaymentMethodSepaDebit + """ + The SEPA Direct Debit-specific details for this payment method, including bank + code and last four IBAN digits. + """ + + typename: Literal["SepaDebitPaymentMethod"] + """The typename of this object""" + + +PaymentMethod: TypeAlias = Annotated[ + Union[ + Optional[PaymentMethodBasePaymentMethod], + Optional[PaymentMethodCardPaymentMethod], + Optional[PaymentMethodUsBankAccountPaymentMethod], + Optional[PaymentMethodCashappPaymentMethod], + Optional[PaymentMethodIdealPaymentMethod], + Optional[PaymentMethodSepaDebitPaymentMethod], + ], + PropertyInfo(discriminator="typename"), +] + + +class Product(BaseModel): + """The access pass being promoted. + + Null for campaigns that don't target a specific product. + """ + + id: str + """The unique identifier for the product.""" + + route: str + """ + The URL slug used in the product's public link (e.g., 'my-product' in + whop.com/company/my-product). + """ + + title: str + """ + The display name of the product shown to customers on the product page and in + search results. + """ + + +class AdCampaignRetrieveResponse(BaseModel): + """An advertising campaign running on an external platform or within Whop.""" + + id: str + """The unique identifier for the ad campaign.""" + + available_budget: float + """ + Available budget in dollars, capped at daily budget minus today's spend for + daily campaigns + """ + + billing_ledger_account: Optional[BillingLedgerAccount] = None + """ + The ledger account being charged for platform balance billing (null if using + card) + """ + + clicks_count: int + """Number of clicks""" + + config: Optional[Config] = None + """Meta campaign configuration (objective, budget, bidding, etc.). + + Null for non-Meta campaigns — use `tiktokConfig` for TikTok. + """ + + created_at: datetime + """The datetime the ad campaign was created.""" + + created_by_user: CreatedByUser + """The user who created the campaign""" + + daily_budget: Optional[float] = None + """ + Effective daily budget in dollars — sum of ad group budgets when set, otherwise + campaign-level daily budget + """ + + impressions_count: int + """Number of impressions (views)""" + + paused_until: Optional[datetime] = None + """If temporarily paused, the timestamp when the campaign will auto-resume""" + + payment_method: PaymentMethod + """The payment method used for daily billing (null if using platform balance)""" + + platform: Optional[Literal["meta", "tiktok"]] = None + """The platforms where an ad campaign can run.""" + + product: Optional[Product] = None + """The access pass being promoted. + + Null for campaigns that don't target a specific product. + """ + + purchases_count: int + """Number of purchases""" + + remaining_balance: float + """Remaining balance in dollars""" + + return_on_ad_spend: float + """Return on Ad Spend (ROAS) percentage - revenue generated divided by ad spend""" + + revenue: float + """Total revenue generated from users who converted through this campaign""" + + status: Literal[ + "active", "paused", "inactive", "stale", "pending_refund", "payment_failed", "draft", "in_review", "flagged" + ] + """Current status of the campaign (active, paused, or inactive)""" + + target_country_codes: List[str] + """Array of ISO3166 country codes for territory targeting""" + + title: str + """The title of the ad campaign""" + + todays_spend: float + """Amount spent today in dollars""" + + total_credits: float + """Total credits added to the campaign in dollars""" + + total_spend: float + """Total amount spent on conversions in dollars""" + + updated_at: datetime + """The datetime the ad campaign was last updated.""" diff --git a/src/whop_sdk/types/ad_campaign_unpause_response.py b/src/whop_sdk/types/ad_campaign_unpause_response.py new file mode 100644 index 00000000..99328ae6 --- /dev/null +++ b/src/whop_sdk/types/ad_campaign_unpause_response.py @@ -0,0 +1,509 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Union, Optional +from datetime import datetime +from typing_extensions import Literal, Annotated, TypeAlias + +from .._utils import PropertyInfo +from .._models import BaseModel +from .card_brands import CardBrands +from .payment_method_types import PaymentMethodTypes + +__all__ = [ + "AdCampaignUnpauseResponse", + "BillingLedgerAccount", + "Config", + "CreatedByUser", + "PaymentMethod", + "PaymentMethodBasePaymentMethod", + "PaymentMethodCardPaymentMethod", + "PaymentMethodCardPaymentMethodCard", + "PaymentMethodUsBankAccountPaymentMethod", + "PaymentMethodUsBankAccountPaymentMethodUsBankAccount", + "PaymentMethodCashappPaymentMethod", + "PaymentMethodCashappPaymentMethodCashapp", + "PaymentMethodIdealPaymentMethod", + "PaymentMethodIdealPaymentMethodIdeal", + "PaymentMethodSepaDebitPaymentMethod", + "PaymentMethodSepaDebitPaymentMethodSepaDebit", + "Product", +] + + +class BillingLedgerAccount(BaseModel): + """ + The ledger account being charged for platform balance billing (null if using card) + """ + + id: str + """The unique identifier for the ledger account.""" + + +class Config(BaseModel): + """Meta campaign configuration (objective, budget, bidding, etc.). + + Null for non-Meta campaigns — use `tiktokConfig` for TikTok. + """ + + bid_amount: Optional[int] = None + """Bid cap amount in cents. Only used when bid_strategy is bid_cap.""" + + bid_strategy: Optional[Literal["lowest_cost", "bid_cap", "cost_cap"]] = None + """The bidding strategy used to optimize spend for this campaign.""" + + budget_optimization: Optional[bool] = None + """ + Whether campaign budget optimization (CBO) is enabled, allowing the platform to + distribute budget across ad groups. + """ + + end_time: Optional[str] = None + """The scheduled end time of the campaign (ISO8601).""" + + objective: Optional[Literal["awareness", "traffic", "engagement", "leads", "sales"]] = None + """The campaign objective that determines how Meta optimizes delivery.""" + + special_categories: Optional[List[str]] = None + """ + Special ad categories required by the platform (e.g., housing, employment, + credit). + """ + + start_time: Optional[str] = None + """The scheduled start time of the campaign (ISO8601).""" + + status: Optional[Literal["active", "paused"]] = None + """The campaign status as set by the advertiser (active or paused).""" + + +class CreatedByUser(BaseModel): + """The user who created the campaign""" + + id: str + """The unique identifier for the user.""" + + name: Optional[str] = None + """The user's display name shown on their public profile.""" + + username: str + """The user's unique username shown on their public profile.""" + + +class PaymentMethodBasePaymentMethod(BaseModel): + """A saved payment method with no type-specific details available.""" + + id: str + """Represents a unique identifier that is Base64 obfuscated. + + It is often used to refetch an object or as key for a cache. The ID type appears + in a JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such as + `"VXNlci0xMA=="`) or integer (such as `4`) input value will be accepted as an + ID. + """ + + created_at: datetime + """The time of the event in ISO 8601 UTC format with millisecond precision""" + + payment_method_type: PaymentMethodTypes + """ + The type of payment instrument stored on file (e.g., card, us_bank_account, + cashapp, ideal, sepa_debit). + """ + + typename: Literal["BasePaymentMethod"] + """The typename of this object""" + + +class PaymentMethodCardPaymentMethodCard(BaseModel): + """ + The card-specific details for this payment method, including brand, last four digits, and expiration. + """ + + brand: Optional[CardBrands] = None + """Possible card brands that a payment token can have""" + + exp_month: Optional[int] = None + """The two-digit expiration month of the card (1-12). Null if not available.""" + + exp_year: Optional[int] = None + """The two-digit expiration year of the card (e.g., 27 for 2027). + + Null if not available. + """ + + last4: Optional[str] = None + """The last four digits of the card number. Null if not available.""" + + +class PaymentMethodCardPaymentMethod(BaseModel): + """ + A saved card payment method, including brand, last four digits, and expiration details. + """ + + id: str + """Represents a unique identifier that is Base64 obfuscated. + + It is often used to refetch an object or as key for a cache. The ID type appears + in a JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such as + `"VXNlci0xMA=="`) or integer (such as `4`) input value will be accepted as an + ID. + """ + + card: PaymentMethodCardPaymentMethodCard + """ + The card-specific details for this payment method, including brand, last four + digits, and expiration. + """ + + created_at: datetime + """The time of the event in ISO 8601 UTC format with millisecond precision""" + + payment_method_type: PaymentMethodTypes + """ + The type of payment instrument stored on file (e.g., card, us_bank_account, + cashapp, ideal, sepa_debit). + """ + + typename: Literal["CardPaymentMethod"] + """The typename of this object""" + + +class PaymentMethodUsBankAccountPaymentMethodUsBankAccount(BaseModel): + """ + The bank account-specific details for this payment method, including bank name and last four digits. + """ + + account_type: str + """The type of bank account (e.g., checking, savings).""" + + bank_name: str + """The name of the financial institution holding the account.""" + + last4: str + """The last four digits of the bank account number.""" + + +class PaymentMethodUsBankAccountPaymentMethod(BaseModel): + """ + A saved US bank account payment method, including bank name, last four digits, and account type. + """ + + id: str + """Represents a unique identifier that is Base64 obfuscated. + + It is often used to refetch an object or as key for a cache. The ID type appears + in a JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such as + `"VXNlci0xMA=="`) or integer (such as `4`) input value will be accepted as an + ID. + """ + + created_at: datetime + """The time of the event in ISO 8601 UTC format with millisecond precision""" + + payment_method_type: PaymentMethodTypes + """ + The type of payment instrument stored on file (e.g., card, us_bank_account, + cashapp, ideal, sepa_debit). + """ + + typename: Literal["UsBankAccountPaymentMethod"] + """The typename of this object""" + + us_bank_account: PaymentMethodUsBankAccountPaymentMethodUsBankAccount + """ + The bank account-specific details for this payment method, including bank name + and last four digits. + """ + + +class PaymentMethodCashappPaymentMethodCashapp(BaseModel): + """ + The Cash App-specific details for this payment method, including cashtag and buyer ID. + """ + + buyer_id: Optional[str] = None + """The unique and immutable identifier assigned by Cash App to the buyer. + + Null if not available. + """ + + cashtag: Optional[str] = None + """The public cashtag handle of the buyer on Cash App. Null if not available.""" + + +class PaymentMethodCashappPaymentMethod(BaseModel): + """ + A saved Cash App payment method, including the buyer's cashtag and unique identifier. + """ + + id: str + """Represents a unique identifier that is Base64 obfuscated. + + It is often used to refetch an object or as key for a cache. The ID type appears + in a JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such as + `"VXNlci0xMA=="`) or integer (such as `4`) input value will be accepted as an + ID. + """ + + cashapp: PaymentMethodCashappPaymentMethodCashapp + """ + The Cash App-specific details for this payment method, including cashtag and + buyer ID. + """ + + created_at: datetime + """The time of the event in ISO 8601 UTC format with millisecond precision""" + + payment_method_type: PaymentMethodTypes + """ + The type of payment instrument stored on file (e.g., card, us_bank_account, + cashapp, ideal, sepa_debit). + """ + + typename: Literal["CashappPaymentMethod"] + """The typename of this object""" + + +class PaymentMethodIdealPaymentMethodIdeal(BaseModel): + """ + The iDEAL-specific details for this payment method, including bank name and BIC. + """ + + bank: Optional[str] = None + """The name of the customer's bank used for the iDEAL transaction. + + Null if not available. + """ + + bic: Optional[str] = None + """The Bank Identifier Code (BIC/SWIFT) of the customer's bank. + + Null if not available. + """ + + +class PaymentMethodIdealPaymentMethod(BaseModel): + """A saved iDEAL payment method, including the customer's bank name and BIC code.""" + + id: str + """Represents a unique identifier that is Base64 obfuscated. + + It is often used to refetch an object or as key for a cache. The ID type appears + in a JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such as + `"VXNlci0xMA=="`) or integer (such as `4`) input value will be accepted as an + ID. + """ + + created_at: datetime + """The time of the event in ISO 8601 UTC format with millisecond precision""" + + ideal: PaymentMethodIdealPaymentMethodIdeal + """ + The iDEAL-specific details for this payment method, including bank name and BIC. + """ + + payment_method_type: PaymentMethodTypes + """ + The type of payment instrument stored on file (e.g., card, us_bank_account, + cashapp, ideal, sepa_debit). + """ + + typename: Literal["IdealPaymentMethod"] + """The typename of this object""" + + +class PaymentMethodSepaDebitPaymentMethodSepaDebit(BaseModel): + """ + The SEPA Direct Debit-specific details for this payment method, including bank code and last four IBAN digits. + """ + + bank_code: Optional[str] = None + """The bank code of the financial institution associated with this SEPA account. + + Null if not available. + """ + + branch_code: Optional[str] = None + """The branch code of the financial institution associated with this SEPA account. + + Null if not available. + """ + + country: Optional[str] = None + """The two-letter ISO country code where the bank account is located. + + Null if not available. + """ + + last4: Optional[str] = None + """The last four digits of the IBAN associated with this SEPA account. + + Null if not available. + """ + + +class PaymentMethodSepaDebitPaymentMethod(BaseModel): + """ + A saved SEPA Direct Debit payment method, including the bank code, country, and last four IBAN digits. + """ + + id: str + """Represents a unique identifier that is Base64 obfuscated. + + It is often used to refetch an object or as key for a cache. The ID type appears + in a JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such as + `"VXNlci0xMA=="`) or integer (such as `4`) input value will be accepted as an + ID. + """ + + created_at: datetime + """The time of the event in ISO 8601 UTC format with millisecond precision""" + + payment_method_type: PaymentMethodTypes + """ + The type of payment instrument stored on file (e.g., card, us_bank_account, + cashapp, ideal, sepa_debit). + """ + + sepa_debit: PaymentMethodSepaDebitPaymentMethodSepaDebit + """ + The SEPA Direct Debit-specific details for this payment method, including bank + code and last four IBAN digits. + """ + + typename: Literal["SepaDebitPaymentMethod"] + """The typename of this object""" + + +PaymentMethod: TypeAlias = Annotated[ + Union[ + Optional[PaymentMethodBasePaymentMethod], + Optional[PaymentMethodCardPaymentMethod], + Optional[PaymentMethodUsBankAccountPaymentMethod], + Optional[PaymentMethodCashappPaymentMethod], + Optional[PaymentMethodIdealPaymentMethod], + Optional[PaymentMethodSepaDebitPaymentMethod], + ], + PropertyInfo(discriminator="typename"), +] + + +class Product(BaseModel): + """The access pass being promoted. + + Null for campaigns that don't target a specific product. + """ + + id: str + """The unique identifier for the product.""" + + route: str + """ + The URL slug used in the product's public link (e.g., 'my-product' in + whop.com/company/my-product). + """ + + title: str + """ + The display name of the product shown to customers on the product page and in + search results. + """ + + +class AdCampaignUnpauseResponse(BaseModel): + """An advertising campaign running on an external platform or within Whop.""" + + id: str + """The unique identifier for the ad campaign.""" + + available_budget: float + """ + Available budget in dollars, capped at daily budget minus today's spend for + daily campaigns + """ + + billing_ledger_account: Optional[BillingLedgerAccount] = None + """ + The ledger account being charged for platform balance billing (null if using + card) + """ + + clicks_count: int + """Number of clicks""" + + config: Optional[Config] = None + """Meta campaign configuration (objective, budget, bidding, etc.). + + Null for non-Meta campaigns — use `tiktokConfig` for TikTok. + """ + + created_at: datetime + """The datetime the ad campaign was created.""" + + created_by_user: CreatedByUser + """The user who created the campaign""" + + daily_budget: Optional[float] = None + """ + Effective daily budget in dollars — sum of ad group budgets when set, otherwise + campaign-level daily budget + """ + + impressions_count: int + """Number of impressions (views)""" + + paused_until: Optional[datetime] = None + """If temporarily paused, the timestamp when the campaign will auto-resume""" + + payment_method: PaymentMethod + """The payment method used for daily billing (null if using platform balance)""" + + platform: Optional[Literal["meta", "tiktok"]] = None + """The platforms where an ad campaign can run.""" + + product: Optional[Product] = None + """The access pass being promoted. + + Null for campaigns that don't target a specific product. + """ + + purchases_count: int + """Number of purchases""" + + remaining_balance: float + """Remaining balance in dollars""" + + return_on_ad_spend: float + """Return on Ad Spend (ROAS) percentage - revenue generated divided by ad spend""" + + revenue: float + """Total revenue generated from users who converted through this campaign""" + + status: Literal[ + "active", "paused", "inactive", "stale", "pending_refund", "payment_failed", "draft", "in_review", "flagged" + ] + """Current status of the campaign (active, paused, or inactive)""" + + target_country_codes: List[str] + """Array of ISO3166 country codes for territory targeting""" + + title: str + """The title of the ad campaign""" + + todays_spend: float + """Amount spent today in dollars""" + + total_credits: float + """Total credits added to the campaign in dollars""" + + total_spend: float + """Total amount spent on conversions in dollars""" + + updated_at: datetime + """The datetime the ad campaign was last updated.""" diff --git a/src/whop_sdk/types/ad_campaign_update_params.py b/src/whop_sdk/types/ad_campaign_update_params.py new file mode 100644 index 00000000..fd8872db --- /dev/null +++ b/src/whop_sdk/types/ad_campaign_update_params.py @@ -0,0 +1,76 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Optional +from typing_extensions import Literal, TypedDict + +from .._types import SequenceNotStr + +__all__ = ["AdCampaignUpdateParams", "Config"] + + +class AdCampaignUpdateParams(TypedDict, total=False): + ad_creative_set_ids: Optional[SequenceNotStr[str]] + """Array of creative set IDs to link to this campaign.""" + + budget: Optional[float] + """Budget amount in dollars.""" + + budget_type: Optional[Literal["daily", "lifetime"]] + """The budget type for an ad campaign or ad group.""" + + config: Optional[Config] + """Unified campaign configuration (conversion goal, budget, bidding, etc.).""" + + daily_budget: Optional[float] + """Daily budget in dollars (minimum $5).""" + + product_id: Optional[str] + """The unique identifier of the product (access pass) to promote.""" + + target_country_codes: Optional[SequenceNotStr[str]] + """Array of ISO3166 country codes for territory targeting.""" + + title: Optional[str] + """The title of the ad campaign. Must be max 100 characters.""" + + +class Config(TypedDict, total=False): + """Unified campaign configuration (conversion goal, budget, bidding, etc.).""" + + bid_amount: Optional[int] + """Bid cap amount in cents. Only used when bid_strategy is bid_cap.""" + + bid_strategy: Optional[Literal["lowest_cost", "bid_cap", "cost_cap"]] + """The bidding strategy used to optimize spend for this campaign.""" + + budget_optimization: Optional[bool] + """ + Whether campaign budget optimization (CBO) is enabled, allowing the platform to + distribute budget across ad groups. + """ + + end_time: Optional[str] + """The scheduled end time of the campaign (ISO8601).""" + + lifetime_budget: Optional[int] + """Represents non-fractional signed whole numeric values. + + Int can represent values between -(2^31) and 2^31 - 1. + """ + + objective: Optional[Literal["awareness", "traffic", "engagement", "leads", "sales"]] + """The campaign objective that determines how Meta optimizes delivery.""" + + special_categories: Optional[SequenceNotStr[str]] + """ + Special ad categories required by the platform (e.g., housing, employment, + credit). + """ + + start_time: Optional[str] + """The scheduled start time of the campaign (ISO8601).""" + + status: Optional[Literal["active", "paused"]] + """The campaign status as set by the advertiser (active or paused).""" diff --git a/src/whop_sdk/types/ad_campaign_update_response.py b/src/whop_sdk/types/ad_campaign_update_response.py new file mode 100644 index 00000000..44ea781b --- /dev/null +++ b/src/whop_sdk/types/ad_campaign_update_response.py @@ -0,0 +1,509 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Union, Optional +from datetime import datetime +from typing_extensions import Literal, Annotated, TypeAlias + +from .._utils import PropertyInfo +from .._models import BaseModel +from .card_brands import CardBrands +from .payment_method_types import PaymentMethodTypes + +__all__ = [ + "AdCampaignUpdateResponse", + "BillingLedgerAccount", + "Config", + "CreatedByUser", + "PaymentMethod", + "PaymentMethodBasePaymentMethod", + "PaymentMethodCardPaymentMethod", + "PaymentMethodCardPaymentMethodCard", + "PaymentMethodUsBankAccountPaymentMethod", + "PaymentMethodUsBankAccountPaymentMethodUsBankAccount", + "PaymentMethodCashappPaymentMethod", + "PaymentMethodCashappPaymentMethodCashapp", + "PaymentMethodIdealPaymentMethod", + "PaymentMethodIdealPaymentMethodIdeal", + "PaymentMethodSepaDebitPaymentMethod", + "PaymentMethodSepaDebitPaymentMethodSepaDebit", + "Product", +] + + +class BillingLedgerAccount(BaseModel): + """ + The ledger account being charged for platform balance billing (null if using card) + """ + + id: str + """The unique identifier for the ledger account.""" + + +class Config(BaseModel): + """Meta campaign configuration (objective, budget, bidding, etc.). + + Null for non-Meta campaigns — use `tiktokConfig` for TikTok. + """ + + bid_amount: Optional[int] = None + """Bid cap amount in cents. Only used when bid_strategy is bid_cap.""" + + bid_strategy: Optional[Literal["lowest_cost", "bid_cap", "cost_cap"]] = None + """The bidding strategy used to optimize spend for this campaign.""" + + budget_optimization: Optional[bool] = None + """ + Whether campaign budget optimization (CBO) is enabled, allowing the platform to + distribute budget across ad groups. + """ + + end_time: Optional[str] = None + """The scheduled end time of the campaign (ISO8601).""" + + objective: Optional[Literal["awareness", "traffic", "engagement", "leads", "sales"]] = None + """The campaign objective that determines how Meta optimizes delivery.""" + + special_categories: Optional[List[str]] = None + """ + Special ad categories required by the platform (e.g., housing, employment, + credit). + """ + + start_time: Optional[str] = None + """The scheduled start time of the campaign (ISO8601).""" + + status: Optional[Literal["active", "paused"]] = None + """The campaign status as set by the advertiser (active or paused).""" + + +class CreatedByUser(BaseModel): + """The user who created the campaign""" + + id: str + """The unique identifier for the user.""" + + name: Optional[str] = None + """The user's display name shown on their public profile.""" + + username: str + """The user's unique username shown on their public profile.""" + + +class PaymentMethodBasePaymentMethod(BaseModel): + """A saved payment method with no type-specific details available.""" + + id: str + """Represents a unique identifier that is Base64 obfuscated. + + It is often used to refetch an object or as key for a cache. The ID type appears + in a JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such as + `"VXNlci0xMA=="`) or integer (such as `4`) input value will be accepted as an + ID. + """ + + created_at: datetime + """The time of the event in ISO 8601 UTC format with millisecond precision""" + + payment_method_type: PaymentMethodTypes + """ + The type of payment instrument stored on file (e.g., card, us_bank_account, + cashapp, ideal, sepa_debit). + """ + + typename: Literal["BasePaymentMethod"] + """The typename of this object""" + + +class PaymentMethodCardPaymentMethodCard(BaseModel): + """ + The card-specific details for this payment method, including brand, last four digits, and expiration. + """ + + brand: Optional[CardBrands] = None + """Possible card brands that a payment token can have""" + + exp_month: Optional[int] = None + """The two-digit expiration month of the card (1-12). Null if not available.""" + + exp_year: Optional[int] = None + """The two-digit expiration year of the card (e.g., 27 for 2027). + + Null if not available. + """ + + last4: Optional[str] = None + """The last four digits of the card number. Null if not available.""" + + +class PaymentMethodCardPaymentMethod(BaseModel): + """ + A saved card payment method, including brand, last four digits, and expiration details. + """ + + id: str + """Represents a unique identifier that is Base64 obfuscated. + + It is often used to refetch an object or as key for a cache. The ID type appears + in a JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such as + `"VXNlci0xMA=="`) or integer (such as `4`) input value will be accepted as an + ID. + """ + + card: PaymentMethodCardPaymentMethodCard + """ + The card-specific details for this payment method, including brand, last four + digits, and expiration. + """ + + created_at: datetime + """The time of the event in ISO 8601 UTC format with millisecond precision""" + + payment_method_type: PaymentMethodTypes + """ + The type of payment instrument stored on file (e.g., card, us_bank_account, + cashapp, ideal, sepa_debit). + """ + + typename: Literal["CardPaymentMethod"] + """The typename of this object""" + + +class PaymentMethodUsBankAccountPaymentMethodUsBankAccount(BaseModel): + """ + The bank account-specific details for this payment method, including bank name and last four digits. + """ + + account_type: str + """The type of bank account (e.g., checking, savings).""" + + bank_name: str + """The name of the financial institution holding the account.""" + + last4: str + """The last four digits of the bank account number.""" + + +class PaymentMethodUsBankAccountPaymentMethod(BaseModel): + """ + A saved US bank account payment method, including bank name, last four digits, and account type. + """ + + id: str + """Represents a unique identifier that is Base64 obfuscated. + + It is often used to refetch an object or as key for a cache. The ID type appears + in a JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such as + `"VXNlci0xMA=="`) or integer (such as `4`) input value will be accepted as an + ID. + """ + + created_at: datetime + """The time of the event in ISO 8601 UTC format with millisecond precision""" + + payment_method_type: PaymentMethodTypes + """ + The type of payment instrument stored on file (e.g., card, us_bank_account, + cashapp, ideal, sepa_debit). + """ + + typename: Literal["UsBankAccountPaymentMethod"] + """The typename of this object""" + + us_bank_account: PaymentMethodUsBankAccountPaymentMethodUsBankAccount + """ + The bank account-specific details for this payment method, including bank name + and last four digits. + """ + + +class PaymentMethodCashappPaymentMethodCashapp(BaseModel): + """ + The Cash App-specific details for this payment method, including cashtag and buyer ID. + """ + + buyer_id: Optional[str] = None + """The unique and immutable identifier assigned by Cash App to the buyer. + + Null if not available. + """ + + cashtag: Optional[str] = None + """The public cashtag handle of the buyer on Cash App. Null if not available.""" + + +class PaymentMethodCashappPaymentMethod(BaseModel): + """ + A saved Cash App payment method, including the buyer's cashtag and unique identifier. + """ + + id: str + """Represents a unique identifier that is Base64 obfuscated. + + It is often used to refetch an object or as key for a cache. The ID type appears + in a JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such as + `"VXNlci0xMA=="`) or integer (such as `4`) input value will be accepted as an + ID. + """ + + cashapp: PaymentMethodCashappPaymentMethodCashapp + """ + The Cash App-specific details for this payment method, including cashtag and + buyer ID. + """ + + created_at: datetime + """The time of the event in ISO 8601 UTC format with millisecond precision""" + + payment_method_type: PaymentMethodTypes + """ + The type of payment instrument stored on file (e.g., card, us_bank_account, + cashapp, ideal, sepa_debit). + """ + + typename: Literal["CashappPaymentMethod"] + """The typename of this object""" + + +class PaymentMethodIdealPaymentMethodIdeal(BaseModel): + """ + The iDEAL-specific details for this payment method, including bank name and BIC. + """ + + bank: Optional[str] = None + """The name of the customer's bank used for the iDEAL transaction. + + Null if not available. + """ + + bic: Optional[str] = None + """The Bank Identifier Code (BIC/SWIFT) of the customer's bank. + + Null if not available. + """ + + +class PaymentMethodIdealPaymentMethod(BaseModel): + """A saved iDEAL payment method, including the customer's bank name and BIC code.""" + + id: str + """Represents a unique identifier that is Base64 obfuscated. + + It is often used to refetch an object or as key for a cache. The ID type appears + in a JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such as + `"VXNlci0xMA=="`) or integer (such as `4`) input value will be accepted as an + ID. + """ + + created_at: datetime + """The time of the event in ISO 8601 UTC format with millisecond precision""" + + ideal: PaymentMethodIdealPaymentMethodIdeal + """ + The iDEAL-specific details for this payment method, including bank name and BIC. + """ + + payment_method_type: PaymentMethodTypes + """ + The type of payment instrument stored on file (e.g., card, us_bank_account, + cashapp, ideal, sepa_debit). + """ + + typename: Literal["IdealPaymentMethod"] + """The typename of this object""" + + +class PaymentMethodSepaDebitPaymentMethodSepaDebit(BaseModel): + """ + The SEPA Direct Debit-specific details for this payment method, including bank code and last four IBAN digits. + """ + + bank_code: Optional[str] = None + """The bank code of the financial institution associated with this SEPA account. + + Null if not available. + """ + + branch_code: Optional[str] = None + """The branch code of the financial institution associated with this SEPA account. + + Null if not available. + """ + + country: Optional[str] = None + """The two-letter ISO country code where the bank account is located. + + Null if not available. + """ + + last4: Optional[str] = None + """The last four digits of the IBAN associated with this SEPA account. + + Null if not available. + """ + + +class PaymentMethodSepaDebitPaymentMethod(BaseModel): + """ + A saved SEPA Direct Debit payment method, including the bank code, country, and last four IBAN digits. + """ + + id: str + """Represents a unique identifier that is Base64 obfuscated. + + It is often used to refetch an object or as key for a cache. The ID type appears + in a JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such as + `"VXNlci0xMA=="`) or integer (such as `4`) input value will be accepted as an + ID. + """ + + created_at: datetime + """The time of the event in ISO 8601 UTC format with millisecond precision""" + + payment_method_type: PaymentMethodTypes + """ + The type of payment instrument stored on file (e.g., card, us_bank_account, + cashapp, ideal, sepa_debit). + """ + + sepa_debit: PaymentMethodSepaDebitPaymentMethodSepaDebit + """ + The SEPA Direct Debit-specific details for this payment method, including bank + code and last four IBAN digits. + """ + + typename: Literal["SepaDebitPaymentMethod"] + """The typename of this object""" + + +PaymentMethod: TypeAlias = Annotated[ + Union[ + Optional[PaymentMethodBasePaymentMethod], + Optional[PaymentMethodCardPaymentMethod], + Optional[PaymentMethodUsBankAccountPaymentMethod], + Optional[PaymentMethodCashappPaymentMethod], + Optional[PaymentMethodIdealPaymentMethod], + Optional[PaymentMethodSepaDebitPaymentMethod], + ], + PropertyInfo(discriminator="typename"), +] + + +class Product(BaseModel): + """The access pass being promoted. + + Null for campaigns that don't target a specific product. + """ + + id: str + """The unique identifier for the product.""" + + route: str + """ + The URL slug used in the product's public link (e.g., 'my-product' in + whop.com/company/my-product). + """ + + title: str + """ + The display name of the product shown to customers on the product page and in + search results. + """ + + +class AdCampaignUpdateResponse(BaseModel): + """An advertising campaign running on an external platform or within Whop.""" + + id: str + """The unique identifier for the ad campaign.""" + + available_budget: float + """ + Available budget in dollars, capped at daily budget minus today's spend for + daily campaigns + """ + + billing_ledger_account: Optional[BillingLedgerAccount] = None + """ + The ledger account being charged for platform balance billing (null if using + card) + """ + + clicks_count: int + """Number of clicks""" + + config: Optional[Config] = None + """Meta campaign configuration (objective, budget, bidding, etc.). + + Null for non-Meta campaigns — use `tiktokConfig` for TikTok. + """ + + created_at: datetime + """The datetime the ad campaign was created.""" + + created_by_user: CreatedByUser + """The user who created the campaign""" + + daily_budget: Optional[float] = None + """ + Effective daily budget in dollars — sum of ad group budgets when set, otherwise + campaign-level daily budget + """ + + impressions_count: int + """Number of impressions (views)""" + + paused_until: Optional[datetime] = None + """If temporarily paused, the timestamp when the campaign will auto-resume""" + + payment_method: PaymentMethod + """The payment method used for daily billing (null if using platform balance)""" + + platform: Optional[Literal["meta", "tiktok"]] = None + """The platforms where an ad campaign can run.""" + + product: Optional[Product] = None + """The access pass being promoted. + + Null for campaigns that don't target a specific product. + """ + + purchases_count: int + """Number of purchases""" + + remaining_balance: float + """Remaining balance in dollars""" + + return_on_ad_spend: float + """Return on Ad Spend (ROAS) percentage - revenue generated divided by ad spend""" + + revenue: float + """Total revenue generated from users who converted through this campaign""" + + status: Literal[ + "active", "paused", "inactive", "stale", "pending_refund", "payment_failed", "draft", "in_review", "flagged" + ] + """Current status of the campaign (active, paused, or inactive)""" + + target_country_codes: List[str] + """Array of ISO3166 country codes for territory targeting""" + + title: str + """The title of the ad campaign""" + + todays_spend: float + """Amount spent today in dollars""" + + total_credits: float + """Total credits added to the campaign in dollars""" + + total_spend: float + """Total amount spent on conversions in dollars""" + + updated_at: datetime + """The datetime the ad campaign was last updated.""" diff --git a/src/whop_sdk/types/ad_create_params.py b/src/whop_sdk/types/ad_create_params.py new file mode 100644 index 00000000..269d9229 --- /dev/null +++ b/src/whop_sdk/types/ad_create_params.py @@ -0,0 +1,475 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Dict, Iterable, Optional +from typing_extensions import Literal, Required, TypedDict + +from .._types import SequenceNotStr + +__all__ = [ + "AdCreateParams", + "PlatformConfig", + "PlatformConfigMeta", + "PlatformConfigMetaCarouselCard", + "PlatformConfigTiktok", +] + + +class AdCreateParams(TypedDict, total=False): + ad_group_id: Required[str] + """The unique identifier of the ad group to create this ad in.""" + + creative_set_id: Optional[str] + """The unique identifier of the creative set to use.""" + + existing_instagram_media_id: Optional[str] + """ + ID of an existing Instagram media item to use as the ad creative (instead of a + creative set or Facebook post). + """ + + existing_post_id: Optional[str] + """ + ID of an existing Facebook post to use as the ad creative (instead of a creative + set). + """ + + platform_config: Optional[PlatformConfig] + """Platform-specific configuration. Must match the campaign platform.""" + + status: Optional[Literal["active", "paused", "inactive", "in_review", "rejected", "flagged"]] + """The status of an external ad.""" + + +class PlatformConfigMetaCarouselCard(TypedDict, total=False): + """Per-card configuration for a carousel ad.""" + + call_to_action_type: Optional[str] + """CTA button type (e.g., SHOP_NOW, LEARN_MORE).""" + + description: Optional[str] + """Card description (max 30 chars recommended).""" + + link: Optional[str] + """Destination URL for this card (defaults to ad destination).""" + + name: Optional[str] + """Card title (max 35 chars recommended).""" + + +class PlatformConfigMeta(TypedDict, total=False): + """Configuration for Meta (Facebook/Instagram) ads.""" + + call_to_action_type: Optional[ + Literal[ + "LEARN_MORE", + "SHOP_NOW", + "SIGN_UP", + "SUBSCRIBE", + "GET_STARTED", + "BOOK_NOW", + "APPLY_NOW", + "CONTACT_US", + "DOWNLOAD", + "ORDER_NOW", + "BUY_NOW", + "GET_QUOTE", + "MESSAGE_PAGE", + "WHATSAPP_MESSAGE", + "INSTAGRAM_MESSAGE", + "CALL_NOW", + "GET_DIRECTIONS", + "SEND_UPDATES", + "GET_OFFER", + "WATCH_MORE", + "LISTEN_NOW", + "PLAY_GAME", + "OPEN_LINK", + "NO_BUTTON", + "GET_OFFER_VIEW", + "GET_EVENT_TICKETS", + "SEE_MENU", + "REQUEST_TIME", + "EVENT_RSVP", + "SEE_DETAILS", + "VIEW_INSTAGRAM_PROFILE", + ] + ] + """Call-to-action button type.""" + + carousel_cards: Optional[Iterable[PlatformConfigMetaCarouselCard]] + """Per-card carousel config.""" + + description: Optional[str] + """Description of the ad creative (legacy single-value).""" + + descriptions: Optional[SequenceNotStr[str]] + """Up to 5 description variants, rendered via Meta asset_feed_spec.""" + + existing_instagram_media_id: Optional[str] + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + existing_post_id: Optional[str] + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + headline: Optional[str] + """Headline of the ad creative (legacy single-value).""" + + headlines: Optional[SequenceNotStr[str]] + """Up to 5 headline variants, rendered via Meta asset_feed_spec.""" + + instagram_actor_id: Optional[str] + """Unique identifier of the Instagram account.""" + + lead_form_config: Optional[Dict[str, object]] + """Lead generation form configuration (JSON).""" + + link_url: Optional[str] + """Destination URL.""" + + multi_advertiser_enrollment: Optional[Literal["OPT_IN", "OPT_OUT"]] + + name: Optional[str] + """Ad name.""" + + page_id: Optional[str] + """Unique identifier of the Facebook Page.""" + + page_welcome_message: Optional[Dict[str, object]] + """Messenger welcome message / ice-breaker template (JSON).""" + + primary_text: Optional[str] + """Primary text of the ad creative (legacy single-value).""" + + primary_texts: Optional[SequenceNotStr[str]] + """Up to 5 primary-text variants, rendered via Meta asset_feed_spec.""" + + url_tags: Optional[str] + """URL query parameters appended to the destination link.""" + + +class PlatformConfigTiktok(TypedDict, total=False): + """Configuration for TikTok ads.""" + + access_pass_tag: Optional[str] + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + ad_format: Optional[Literal["SINGLE_IMAGE", "SINGLE_VIDEO", "CAROUSEL_ADS", "CATALOG_CAROUSEL", "LIVE_CONTENT"]] + """ + Ad format (SINGLE_IMAGE, SINGLE_VIDEO, CAROUSEL_ADS, CATALOG_CAROUSEL, + LIVE_CONTENT). + """ + + ad_name: Optional[str] + """Ad name.""" + + ad_text: Optional[str] + """Ad copy (single variant).""" + + ad_texts: Optional[SequenceNotStr[str]] + """Ad copy variants for search ads (up to 5).""" + + aigc_disclosure_type: Optional[Literal["UNSET", "CONTAINS_AIGC", "IS_AIGC", "NOT_AIGC"]] + """Whether the ad creative is AI-generated content. + + See docs/tiktok_api/ad.md § aigc_disclosure_type. + """ + + auto_disclaimer_types: Optional[SequenceNotStr[str]] + """Automatic disclaimer categories (e.g., FINANCE, ALCOHOL).""" + + automate_creative_enabled: Optional[bool] + """Represents `true` or `false` values.""" + + brand_safety_postbid_partner: Optional[Literal["UNSET", "IAS", "DOUBLE_VERIFY", "OPEN_SLATE", "ZEFR"]] + """Post-bid brand-safety vendor. + + See docs/tiktok_api/ad.md § brand_safety_postbid_partner. + """ + + brand_safety_vast_url: Optional[str] + """VAST URL for brand safety measurement.""" + + call_to_action: Optional[ + Literal[ + "LEARN_MORE", + "DOWNLOAD", + "SHOP_NOW", + "SIGN_UP", + "CONTACT_US", + "APPLY_NOW", + "BOOK_NOW", + "PLAY_GAME", + "WATCH_NOW", + "READ_MORE", + "VIEW_NOW", + "GET_QUOTE", + "ORDER_NOW", + "INSTALL_NOW", + "GET_SHOWTIMES", + "LISTEN_NOW", + "INTERESTED", + "SUBSCRIBE", + "GET_TICKETS_NOW", + "EXPERIENCE_NOW", + "PRE_ORDER_NOW", + "VISIT_STORE", + ] + ] + """TikTok call-to-action button text. See docs/tiktok_api/ad.md § call_to_action.""" + + call_to_action_enabled: Optional[bool] + """Represents `true` or `false` values.""" + + call_to_action_id: Optional[str] + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + call_to_action_mode: Optional[Literal["STANDARD", "DYNAMIC"]] + """How the call-to-action text is chosen. + + STANDARD uses a single fixed CTA; DYNAMIC lets TikTok rotate through a set of + CTAs to maximize performance. + """ + + card_id: Optional[str] + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + carousel_image_index: Optional[int] + """Represents non-fractional signed whole numeric values. + + Int can represent values between -(2^31) and 2^31 - 1. + """ + + catalog_id: Optional[str] + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + click_tracking_url: Optional[str] + """Third-party click tracker URL.""" + + cpp_url: Optional[str] + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + creative_authorized: Optional[bool] + """ + Whether the creator has authorized the use of this creative for paid promotion + (Spark Ads). + """ + + creative_auto_enhancement_strategy_list: Optional[SequenceNotStr[str]] + + dark_post_status: Optional[Literal["ON", "OFF"]] + + deeplink: Optional[str] + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + deeplink_format_type: Optional[Literal["UNSET", "DEEPLINK", "DEFERRED_DEEPLINK"]] + """How the ad's deeplink is resolved. + + See docs/tiktok_api/ad.md § deeplink_format_type. + """ + + deeplink_type: Optional[str] + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + deeplink_utm_params: Optional[Iterable[Dict[str, object]]] + """UTM params appended to the deeplink.""" + + disclaimer_clickable_texts: Optional[Iterable[Dict[str, object]]] + """Clickable disclaimer segments (text + url).""" + + disclaimer_text: Optional[str] + """Plain text shown when disclaimer_type is DISCLAIMER_TEXT / DISCLAIMER_WITH_URL.""" + + disclaimer_type: Optional[Literal["NONE", "DISCLAIMER_TEXT", "DISCLAIMER_WITH_URL"]] + """Ad disclaimer mode. See docs/tiktok_api/ad.md § disclaimer_type.""" + + dynamic_destination: Optional[str] + """Dynamic destination strategy for shopping ads.""" + + dynamic_format: Optional[str] + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + end_card_cta: Optional[str] + """End-card CTA text for video ads.""" + + fallback_type: Optional[Literal["UNSET", "APP_STORE", "LANDING_PAGE"]] + """Destination fallback when a deferred deeplink cannot open the app. + + See docs/tiktok_api/ad.md § fallback_type. + """ + + identity_authorized_bc_id: Optional[str] + """Business Center ID (required when identity_type is BC_AUTH_TT).""" + + identity_id: Optional[str] + """Unique identifier of the identity.""" + + identity_type: Optional[Literal["CUSTOMIZED_USER", "AUTH_CODE", "TT_USER", "BC_AUTH_TT"]] + """Identity type.""" + + image_ids: Optional[SequenceNotStr[str]] + """Unique identifiers of the images.""" + + impression_tracking_url: Optional[str] + """Third-party impression tracker URL.""" + + item_duet_status: Optional[Literal["ENABLE", "DISABLE"]] + + item_group_ids: Optional[SequenceNotStr[str]] + + item_stitch_status: Optional[Literal["ENABLE", "DISABLE"]] + + landing_page_url: Optional[str] + """Landing page URL.""" + + link_url: Optional[str] + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + music_id: Optional[str] + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + page_id: Optional[str] + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + product_display_field_list: Optional[SequenceNotStr[str]] + """Fields displayed on dynamic product cards.""" + + product_set_id: Optional[str] + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + product_specific_type: Optional[str] + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + promotional_music_disabled: Optional[bool] + """Represents `true` or `false` values.""" + + shopping_ads_fallback_type: Optional[Literal["UNSET", "LANDING_PAGE", "STORE"]] + """Fallback destination for shopping ads when the primary target is unavailable. + + See docs/tiktok_api/ad.md § shopping_ads_fallback_type. + """ + + shopping_ads_video_package_id: Optional[str] + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + showcase_products: Optional[Iterable[Dict[str, object]]] + + sku_ids: Optional[SequenceNotStr[str]] + + tiktok_item_id: Optional[str] + """TikTok item ID for Spark Ads (promotes an organic post).""" + + tracking_app_id: Optional[str] + """TikTok MMP-tracked app ID.""" + + tracking_message_event_set_id: Optional[str] + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + tracking_offline_event_set_ids: Optional[SequenceNotStr[str]] + """Offline event set IDs for attribution.""" + + tracking_pixel_id: Optional[str] + """TikTok pixel ID used for conversion tracking on this ad.""" + + utm_params: Optional[Iterable[Dict[str, object]]] + + vertical_video_strategy: Optional[str] + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + video_id: Optional[str] + """Unique identifier of the video.""" + + video_view_tracking_url: Optional[str] + """Third-party video-view tracker URL.""" + + viewability_postbid_partner: Optional[Literal["UNSET", "IAS", "DOUBLE_VERIFY", "MOAT"]] + """Post-bid viewability measurement partner. + + See docs/tiktok_api/ad.md § viewability_postbid_partner. + """ + + viewability_vast_url: Optional[str] + """VAST URL for viewability measurement.""" + + +class PlatformConfig(TypedDict, total=False): + """Platform-specific configuration. Must match the campaign platform.""" + + meta: Optional[PlatformConfigMeta] + """Configuration for Meta (Facebook/Instagram) ads.""" + + tiktok: Optional[PlatformConfigTiktok] + """Configuration for TikTok ads.""" diff --git a/src/whop_sdk/types/ad_create_response.py b/src/whop_sdk/types/ad_create_response.py new file mode 100644 index 00000000..13494ae6 --- /dev/null +++ b/src/whop_sdk/types/ad_create_response.py @@ -0,0 +1,251 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Union, Optional +from datetime import datetime +from typing_extensions import Literal, Annotated, TypeAlias + +from .._utils import PropertyInfo +from .._models import BaseModel + +__all__ = [ + "AdCreateResponse", + "ExternalAdCreativeSet", + "ExternalAdGroup", + "PlatformConfig", + "PlatformConfigMetaAdPlatformConfigType", + "PlatformConfigTiktokAdPlatformConfigType", +] + + +class ExternalAdCreativeSet(BaseModel): + """The creative set used by this ad.""" + + id: str + """The unique identifier for the external ad creative set.""" + + +class ExternalAdGroup(BaseModel): + """The parent ad group.""" + + id: str + """The unique identifier for the external ad group.""" + + name: Optional[str] = None + """Human-readable ad group name""" + + status: Literal["active", "paused", "inactive", "in_review", "rejected", "flagged"] + """Current operational status of the ad group""" + + +class PlatformConfigMetaAdPlatformConfigType(BaseModel): + """Meta (Facebook/Instagram) ad configuration.""" + + call_to_action_type: Optional[ + Literal[ + "LEARN_MORE", + "SHOP_NOW", + "SIGN_UP", + "SUBSCRIBE", + "GET_STARTED", + "BOOK_NOW", + "APPLY_NOW", + "CONTACT_US", + "DOWNLOAD", + "ORDER_NOW", + "BUY_NOW", + "GET_QUOTE", + "MESSAGE_PAGE", + "WHATSAPP_MESSAGE", + "INSTAGRAM_MESSAGE", + "CALL_NOW", + "GET_DIRECTIONS", + "SEND_UPDATES", + "GET_OFFER", + "WATCH_MORE", + "LISTEN_NOW", + "PLAY_GAME", + "OPEN_LINK", + "NO_BUTTON", + "GET_OFFER_VIEW", + "GET_EVENT_TICKETS", + "SEE_MENU", + "REQUEST_TIME", + "EVENT_RSVP", + "SEE_DETAILS", + "VIEW_INSTAGRAM_PROFILE", + ] + ] = None + + headline: Optional[str] = None + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + instagram_actor_id: Optional[str] = None + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + link_url: Optional[str] = None + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + name: Optional[str] = None + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + page_id: Optional[str] = None + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + platform: Literal["meta", "tiktok"] + """The ad platform.""" + + primary_text: Optional[str] = None + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + typename: Literal["MetaAdPlatformConfigType"] + """The typename of this object""" + + url_tags: Optional[str] = None + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + +class PlatformConfigTiktokAdPlatformConfigType(BaseModel): + """TikTok ad configuration.""" + + ad_format: Optional[Literal["SINGLE_IMAGE", "SINGLE_VIDEO", "CAROUSEL_ADS", "CATALOG_CAROUSEL", "LIVE_CONTENT"]] = ( + None + ) + + ad_name: Optional[str] = None + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + ad_text: Optional[str] = None + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + call_to_action: Optional[ + Literal[ + "LEARN_MORE", + "DOWNLOAD", + "SHOP_NOW", + "SIGN_UP", + "CONTACT_US", + "APPLY_NOW", + "BOOK_NOW", + "PLAY_GAME", + "WATCH_NOW", + "READ_MORE", + "VIEW_NOW", + "GET_QUOTE", + "ORDER_NOW", + "INSTALL_NOW", + "GET_SHOWTIMES", + "LISTEN_NOW", + "INTERESTED", + "SUBSCRIBE", + "GET_TICKETS_NOW", + "EXPERIENCE_NOW", + "PRE_ORDER_NOW", + "VISIT_STORE", + ] + ] = None + """TikTok call-to-action button text. See docs/tiktok_api/ad.md § call_to_action.""" + + identity_authorized_bc_id: Optional[str] = None + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + identity_id: Optional[str] = None + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + identity_type: Optional[Literal["CUSTOMIZED_USER", "AUTH_CODE", "TT_USER", "BC_AUTH_TT"]] = None + + image_ids: Optional[List[str]] = None + + landing_page_url: Optional[str] = None + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + platform: Literal["meta", "tiktok"] + """The ad platform.""" + + typename: Literal["TiktokAdPlatformConfigType"] + """The typename of this object""" + + video_id: Optional[str] = None + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + +PlatformConfig: TypeAlias = Annotated[ + Union[Optional[PlatformConfigMetaAdPlatformConfigType], Optional[PlatformConfigTiktokAdPlatformConfigType]], + PropertyInfo(discriminator="typename"), +] + + +class AdCreateResponse(BaseModel): + """An ad belonging to an ad group""" + + id: str + """Unique identifier for the ad.""" + + created_at: datetime + """When the ad was created.""" + + external_ad_creative_set: Optional[ExternalAdCreativeSet] = None + """The creative set used by this ad.""" + + external_ad_group: ExternalAdGroup + """The parent ad group.""" + + platform_config: PlatformConfig + """Typed platform-specific configuration.""" + + status: Literal["active", "paused", "inactive", "in_review", "rejected", "flagged"] + """Current status of the ad.""" + + updated_at: datetime + """When the ad was last updated.""" diff --git a/src/whop_sdk/types/ad_group_create_params.py b/src/whop_sdk/types/ad_group_create_params.py new file mode 100644 index 00000000..7d4dc6bd --- /dev/null +++ b/src/whop_sdk/types/ad_group_create_params.py @@ -0,0 +1,1219 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Dict, List, Iterable, Optional +from typing_extensions import Literal, Required, TypedDict + +from .._types import SequenceNotStr + +__all__ = [ + "AdGroupCreateParams", + "Config", + "ConfigTargeting", + "PlatformConfig", + "PlatformConfigMeta", + "PlatformConfigMetaAttributionSpec", + "PlatformConfigMetaExcludedGeoLocations", + "PlatformConfigMetaExcludedGeoLocationsCity", + "PlatformConfigMetaExcludedGeoLocationsRegion", + "PlatformConfigMetaExcludedGeoLocationsZip", + "PlatformConfigMetaGeoCity", + "PlatformConfigMetaGeoLocations", + "PlatformConfigMetaGeoLocationsCity", + "PlatformConfigMetaGeoLocationsRegion", + "PlatformConfigMetaGeoLocationsZip", + "PlatformConfigMetaGeoRegion", + "PlatformConfigMetaLeadFormConfig", + "PlatformConfigMetaLeadFormConfigQuestion", + "PlatformConfigMetaLeadFormConfigQuestionDependentConditionalQuestion", + "PlatformConfigMetaLeadFormConfigQuestionDependentConditionalQuestionOption", + "PlatformConfigMetaLeadFormConfigQuestionDependentConditionalQuestionOptionLogic", + "PlatformConfigMetaLeadFormConfigQuestionOption", + "PlatformConfigMetaLeadFormConfigQuestionOptionLogic", + "PlatformConfigMetaLeadFormConfigCustomDisclaimerCheckbox", + "PlatformConfigMetaLeadFormConfigThankYouPage", + "PlatformConfigMetaPromotedObject", + "PlatformConfigMetaTargetingAutomation", + "PlatformConfigTiktok", + "PlatformConfigTiktokAction", + "PlatformConfigTiktokInstantFormConfig", + "PlatformConfigTiktokInstantFormConfigQuestion", +] + + +class AdGroupCreateParams(TypedDict, total=False): + campaign_id: Required[str] + """The ad campaign to create this ad group within.""" + + budget: Optional[float] + """Budget amount in dollars.""" + + budget_type: Optional[Literal["daily", "lifetime"]] + """The budget type for an ad campaign or ad group.""" + + config: Optional[Config] + """Unified ad group configuration (bidding, optimization, targeting).""" + + daily_budget: Optional[float] + """Daily budget in dollars.""" + + name: Optional[str] + """Human-readable ad group name.""" + + platform_config: Optional[PlatformConfig] + """Platform-specific ad group configuration.""" + + status: Optional[Literal["active", "paused", "inactive", "in_review", "rejected", "flagged"]] + """The status of an external ad group.""" + + +class ConfigTargeting(TypedDict, total=False): + """Audience targeting settings (demographics, geo, interests, audiences, devices).""" + + age_max: Optional[int] + """Maximum age for demographic targeting.""" + + age_min: Optional[int] + """Minimum age for demographic targeting.""" + + countries: Optional[SequenceNotStr[str]] + """ISO 3166-1 alpha-2 country codes to target.""" + + device_platforms: Optional[List[Literal["mobile", "desktop"]]] + """Device platforms to target.""" + + exclude_audience_ids: Optional[SequenceNotStr[str]] + """Platform audience IDs to exclude.""" + + genders: Optional[List[Literal["male", "female", "all"]]] + """Genders to target.""" + + include_audience_ids: Optional[SequenceNotStr[str]] + """Platform audience IDs to include.""" + + interest_ids: Optional[SequenceNotStr[str]] + """Platform-specific interest IDs to target.""" + + languages: Optional[SequenceNotStr[str]] + """Language codes to target.""" + + placement_type: Optional[Literal["automatic", "manual"]] + """Placement strategy for ad delivery.""" + + +class Config(TypedDict, total=False): + """Unified ad group configuration (bidding, optimization, targeting).""" + + bid_amount: Optional[int] + """Bid cap amount in cents. Used when bid_strategy is bid_cap or cost_cap.""" + + bid_strategy: Optional[Literal["lowest_cost", "bid_cap", "cost_cap"]] + """Bid strategy: lowest_cost, bid_cap, or cost_cap.""" + + billing_event: Optional[Literal["impressions", "clicks", "optimized_cpm", "video_views"]] + """How you are billed (e.g., impressions, clicks).""" + + end_time: Optional[str] + """Scheduled end time (ISO8601). Required for lifetime budgets.""" + + frequency_cap: Optional[int] + """Maximum number of times to show ads to each person in the frequency interval.""" + + frequency_cap_interval_days: Optional[int] + """Number of days for the frequency cap interval.""" + + optimization_goal: Optional[ + Literal[ + "conversions", + "link_clicks", + "landing_page_views", + "reach", + "impressions", + "app_installs", + "video_views", + "lead_generation", + "value", + "page_likes", + "conversations", + "ad_recall_lift", + "two_second_continuous_video_views", + "post_engagement", + "event_responses", + "reminders_set", + "quality_lead", + ] + ] + """What the ad group optimizes for (e.g., conversions, link_clicks, reach).""" + + pacing: Optional[Literal["standard", "accelerated"]] + """Budget pacing: standard (even) or accelerated (fast).""" + + start_time: Optional[str] + """Scheduled start time (ISO8601).""" + + targeting: Optional[ConfigTargeting] + """Audience targeting settings (demographics, geo, interests, audiences, devices).""" + + +class PlatformConfigMetaAttributionSpec(TypedDict, total=False): + """Meta conversion attribution window.""" + + event_type: Required[str] + """Attribution event type (e.g., CLICK_THROUGH, VIEW_THROUGH).""" + + window_days: Required[int] + """Attribution window in days (1, 7, 28).""" + + +class PlatformConfigMetaExcludedGeoLocationsCity(TypedDict, total=False): + """A Meta geo target entry (region, city, or zip).""" + + key: Required[str] + """Meta geo target key/ID.""" + + country: Optional[str] + """Country code for this entry.""" + + name: Optional[str] + """Display name.""" + + radius: Optional[int] + """Radius in miles (cities only).""" + + +class PlatformConfigMetaExcludedGeoLocationsRegion(TypedDict, total=False): + """A Meta geo target entry (region, city, or zip).""" + + key: Required[str] + """Meta geo target key/ID.""" + + country: Optional[str] + """Country code for this entry.""" + + name: Optional[str] + """Display name.""" + + radius: Optional[int] + """Radius in miles (cities only).""" + + +class PlatformConfigMetaExcludedGeoLocationsZip(TypedDict, total=False): + """A Meta geo target entry (region, city, or zip).""" + + key: Required[str] + """Meta geo target key/ID.""" + + country: Optional[str] + """Country code for this entry.""" + + name: Optional[str] + """Display name.""" + + radius: Optional[int] + """Radius in miles (cities only).""" + + +class PlatformConfigMetaExcludedGeoLocations(TypedDict, total=False): + """Geo locations to exclude.""" + + cities: Optional[Iterable[PlatformConfigMetaExcludedGeoLocationsCity]] + """City targets.""" + + countries: Optional[SequenceNotStr[str]] + """ISO 3166-1 alpha-2 country codes.""" + + location_types: Optional[SequenceNotStr[str]] + """Location types (home, recent, travel_in).""" + + regions: Optional[Iterable[PlatformConfigMetaExcludedGeoLocationsRegion]] + """Region/state targets.""" + + zips: Optional[Iterable[PlatformConfigMetaExcludedGeoLocationsZip]] + """Zip/postal code targets.""" + + +class PlatformConfigMetaGeoCity(TypedDict, total=False): + """A Meta geo target entry (region, city, or zip).""" + + key: Required[str] + """Meta geo target key/ID.""" + + country: Optional[str] + """Country code for this entry.""" + + name: Optional[str] + """Display name.""" + + radius: Optional[int] + """Radius in miles (cities only).""" + + +class PlatformConfigMetaGeoLocationsCity(TypedDict, total=False): + """A Meta geo target entry (region, city, or zip).""" + + key: Required[str] + """Meta geo target key/ID.""" + + country: Optional[str] + """Country code for this entry.""" + + name: Optional[str] + """Display name.""" + + radius: Optional[int] + """Radius in miles (cities only).""" + + +class PlatformConfigMetaGeoLocationsRegion(TypedDict, total=False): + """A Meta geo target entry (region, city, or zip).""" + + key: Required[str] + """Meta geo target key/ID.""" + + country: Optional[str] + """Country code for this entry.""" + + name: Optional[str] + """Display name.""" + + radius: Optional[int] + """Radius in miles (cities only).""" + + +class PlatformConfigMetaGeoLocationsZip(TypedDict, total=False): + """A Meta geo target entry (region, city, or zip).""" + + key: Required[str] + """Meta geo target key/ID.""" + + country: Optional[str] + """Country code for this entry.""" + + name: Optional[str] + """Display name.""" + + radius: Optional[int] + """Radius in miles (cities only).""" + + +class PlatformConfigMetaGeoLocations(TypedDict, total=False): + """Geo targeting (countries, regions, cities, zips).""" + + cities: Optional[Iterable[PlatformConfigMetaGeoLocationsCity]] + """City targets.""" + + countries: Optional[SequenceNotStr[str]] + """ISO 3166-1 alpha-2 country codes.""" + + location_types: Optional[SequenceNotStr[str]] + """Location types (home, recent, travel_in).""" + + regions: Optional[Iterable[PlatformConfigMetaGeoLocationsRegion]] + """Region/state targets.""" + + zips: Optional[Iterable[PlatformConfigMetaGeoLocationsZip]] + """Zip/postal code targets.""" + + +class PlatformConfigMetaGeoRegion(TypedDict, total=False): + """A Meta geo target entry (region, city, or zip).""" + + key: Required[str] + """Meta geo target key/ID.""" + + country: Optional[str] + """Country code for this entry.""" + + name: Optional[str] + """Display name.""" + + radius: Optional[int] + """Radius in miles (cities only).""" + + +class PlatformConfigMetaLeadFormConfigQuestionDependentConditionalQuestionOptionLogic(TypedDict, total=False): + """Conditional logic routing for this answer option.""" + + type: Required[str] + """Logic type: go_to_question, submit_form, or close_form.""" + + target_end_page_index: Optional[int] + """Index of the end page to route to (for submit_form type).""" + + target_question_index: Optional[int] + """Index of the question to route to (for go_to_question type).""" + + +class PlatformConfigMetaLeadFormConfigQuestionDependentConditionalQuestionOption(TypedDict, total=False): + """An answer option for a multiple choice lead form question.""" + + key: Required[str] + """Unique key for this option.""" + + value: Required[str] + """Display text for this option.""" + + logic: Optional[PlatformConfigMetaLeadFormConfigQuestionDependentConditionalQuestionOptionLogic] + """Conditional logic routing for this answer option.""" + + +class PlatformConfigMetaLeadFormConfigQuestionDependentConditionalQuestion(TypedDict, total=False): + """A dependent conditional question (non-recursive to avoid schema recursion).""" + + type: Required[str] + """Question type (EMAIL, FULL_NAME, PHONE, CUSTOM, DATE_TIME, etc.).""" + + inline_context: Optional[str] + """Helper text shown below the question.""" + + key: Optional[str] + """Unique key for this question.""" + + label: Optional[str] + """Custom label for CUSTOM questions.""" + + options: Optional[Iterable[PlatformConfigMetaLeadFormConfigQuestionDependentConditionalQuestionOption]] + """Answer options for multiple choice questions.""" + + +class PlatformConfigMetaLeadFormConfigQuestionOptionLogic(TypedDict, total=False): + """Conditional logic routing for this answer option.""" + + type: Required[str] + """Logic type: go_to_question, submit_form, or close_form.""" + + target_end_page_index: Optional[int] + """Index of the end page to route to (for submit_form type).""" + + target_question_index: Optional[int] + """Index of the question to route to (for go_to_question type).""" + + +class PlatformConfigMetaLeadFormConfigQuestionOption(TypedDict, total=False): + """An answer option for a multiple choice lead form question.""" + + key: Required[str] + """Unique key for this option.""" + + value: Required[str] + """Display text for this option.""" + + logic: Optional[PlatformConfigMetaLeadFormConfigQuestionOptionLogic] + """Conditional logic routing for this answer option.""" + + +class PlatformConfigMetaLeadFormConfigQuestion(TypedDict, total=False): + """A question on a Meta lead gen form.""" + + type: Required[str] + """Question type (EMAIL, FULL_NAME, PHONE, CUSTOM, DATE_TIME, etc.).""" + + conditional_questions_group_id: Optional[str] + """Group ID for conditional question routing.""" + + dependent_conditional_questions: Optional[ + Iterable[PlatformConfigMetaLeadFormConfigQuestionDependentConditionalQuestion] + ] + """Questions shown conditionally based on this question's answer.""" + + inline_context: Optional[str] + """Helper text shown below the question.""" + + key: Optional[str] + """Unique key for this question.""" + + label: Optional[str] + """Custom label for CUSTOM questions.""" + + options: Optional[Iterable[PlatformConfigMetaLeadFormConfigQuestionOption]] + """Answer options for multiple choice CUSTOM questions.""" + + question_format: Optional[str] + """UI hint: short_answer, multiple_choice, or appointment.""" + + +class PlatformConfigMetaLeadFormConfigCustomDisclaimerCheckbox(TypedDict, total=False): + """A consent checkbox for the custom disclaimer section.""" + + key: Required[str] + """Unique key for this checkbox.""" + + text: Required[str] + """Label text for the checkbox.""" + + is_checked_by_default: Optional[bool] + """Whether the checkbox is checked by default.""" + + is_required: Optional[bool] + """Whether the checkbox must be checked to submit.""" + + +class PlatformConfigMetaLeadFormConfigThankYouPage(TypedDict, total=False): + """A thank-you / ending page for a Meta lead gen form.""" + + body: Optional[str] + """Body text for this ending page.""" + + business_phone: Optional[str] + """Business phone number for call CTA.""" + + button_text: Optional[str] + """Custom button text.""" + + button_type: Optional[str] + """CTA button type: VIEW_WEBSITE, CALL_BUSINESS, DOWNLOAD.""" + + conditional_question_group_id: Optional[str] + """Question group ID for conditional routing to this page.""" + + enable_messenger: Optional[bool] + """Enable Messenger follow-up.""" + + gated_file_url: Optional[str] + """Uploaded file URL for gated content download.""" + + link: Optional[str] + """URL the button links to.""" + + name: Optional[str] + """Internal name for this ending page.""" + + title: Optional[str] + """Headline for this ending page.""" + + +class PlatformConfigMetaLeadFormConfig(TypedDict, total=False): + """Configuration for a Meta lead gen instant form.""" + + name: Required[str] + """Name of the lead form.""" + + privacy_policy_url: Required[str] + """URL to your privacy policy. Required by Meta.""" + + questions: Required[Iterable[PlatformConfigMetaLeadFormConfigQuestion]] + """Questions to ask on the form.""" + + background_image_source: Optional[str] + """Background image source: from_ad or custom.""" + + background_image_url: Optional[str] + """URL of custom background image.""" + + conditional_logic_enabled: Optional[bool] + """Whether conditional logic is enabled for questions.""" + + context_card_button_text: Optional[str] + """CTA button text on the greeting card.""" + + context_card_content: Optional[SequenceNotStr[str]] + """Optional greeting card bullet points.""" + + context_card_style: Optional[str] + """Greeting layout: PARAGRAPH_STYLE or LIST_STYLE.""" + + context_card_title: Optional[str] + """Optional greeting card title.""" + + custom_disclaimer_body: Optional[str] + """Custom disclaimer body text.""" + + custom_disclaimer_checkboxes: Optional[Iterable[PlatformConfigMetaLeadFormConfigCustomDisclaimerCheckbox]] + """Consent checkboxes for the custom disclaimer.""" + + custom_disclaimer_title: Optional[str] + """Custom disclaimer section title.""" + + form_type: Optional[str] + """Form type: more_volume, higher_intent, or rich_creative.""" + + messenger_enabled: Optional[bool] + """Enable Messenger follow-up after form submission.""" + + phone_verification_enabled: Optional[bool] + """Require phone number verification via OTP (higher_intent only).""" + + privacy_policy_link_text: Optional[str] + """Custom link text for privacy policy (max 70 chars).""" + + question_page_custom_headline: Optional[str] + """Custom headline for the questions page.""" + + rich_creative_headline: Optional[str] + """Headline for rich creative form intro.""" + + rich_creative_overview: Optional[str] + """Overview description for rich creative form intro.""" + + rich_creative_url: Optional[str] + """Uploaded image URL for rich creative form type.""" + + thank_you_pages: Optional[Iterable[PlatformConfigMetaLeadFormConfigThankYouPage]] + """Thank you / ending pages (supports multiple for conditional routing).""" + + +class PlatformConfigMetaPromotedObject(TypedDict, total=False): + """The object this ad set promotes (pixel, page, etc.).""" + + custom_conversion_id: Optional[str] + """Custom conversion rule ID (numeric, from Meta Events Manager).""" + + custom_event_str: Optional[str] + """Pixel event name, used when custom_event_type is OTHER.""" + + custom_event_type: Optional[str] + """Custom event type (e.g., PURCHASE, COMPLETE_REGISTRATION, OTHER).""" + + page_id: Optional[str] + """Facebook Page ID.""" + + pixel_id: Optional[str] + """Meta Pixel ID for conversion tracking.""" + + whatsapp_phone_number: Optional[str] + """WhatsApp phone number for messaging campaigns.""" + + +class PlatformConfigMetaTargetingAutomation(TypedDict, total=False): + """Advantage+ audience expansion settings.""" + + advantage_audience: Optional[int] + """0 = off (use exact targeting), 1 = on (let Meta expand audience).""" + + +class PlatformConfigMeta(TypedDict, total=False): + """Meta (Facebook/Instagram) ad set configuration.""" + + android_devices: Optional[SequenceNotStr[str]] + + attribution_setting: Optional[str] + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + attribution_spec: Optional[Iterable[PlatformConfigMetaAttributionSpec]] + """Conversion attribution windows.""" + + audience_network_positions: Optional[SequenceNotStr[str]] + + audience_type: Optional[str] + """Audience type for retargeting.""" + + bid_amount: Optional[int] + """Bid amount in cents.""" + + bid_strategy: Optional[ + Literal["LOWEST_COST_WITHOUT_CAP", "LOWEST_COST_WITH_BID_CAP", "COST_CAP", "LOWEST_COST_WITH_MIN_ROAS"] + ] + """Meta bid strategy.""" + + billing_event: Optional[ + Literal[ + "APP_INSTALLS", + "CLICKS", + "IMPRESSIONS", + "LINK_CLICKS", + "NONE", + "OFFER_CLAIMS", + "PAGE_LIKES", + "POST_ENGAGEMENT", + "THRUPLAY", + "PURCHASE", + "LISTING_INTERACTION", + ] + ] + """How you are billed on Meta.""" + + brand_safety_content_filter_levels: Optional[SequenceNotStr[str]] + + budget_remaining: Optional[str] + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + cost_per_result_goal: Optional[float] + """ + Represents signed double-precision fractional values as specified by + [IEEE 754](https://en.wikipedia.org/wiki/IEEE_floating_point). + """ + + created_time: Optional[str] + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + daily_budget: Optional[int] + """Daily budget in cents.""" + + daily_min_spend_target: Optional[str] + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + daily_spend_cap: Optional[str] + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + destination_type: Optional[ + Literal[ + "UNDEFINED", + "WEBSITE", + "APP", + "FACEBOOK", + "MESSENGER", + "WHATSAPP", + "INSTAGRAM_DIRECT", + "INSTAGRAM_PROFILE", + "PHONE_CALL", + "SHOP_AUTOMATIC", + "APPLINKS_AUTOMATIC", + "ON_AD", + "ON_POST", + "ON_VIDEO", + "ON_PAGE", + "ON_EVENT", + "MESSAGING_MESSENGER_WHATSAPP", + "MESSAGING_INSTAGRAM_DIRECT_MESSENGER", + "MESSAGING_INSTAGRAM_DIRECT_WHATSAPP", + "MESSAGING_INSTAGRAM_DIRECT_MESSENGER_WHATSAPP", + "INSTAGRAM_PROFILE_AND_FACEBOOK_PAGE", + "FACEBOOK_PAGE", + "INSTAGRAM_LIVE", + "FACEBOOK_LIVE", + "IMAGINE", + "LEAD_FROM_IG_DIRECT", + "LEAD_FROM_MESSENGER", + "WEBSITE_AND_LEAD_FORM", + "WEBSITE_AND_PHONE_CALL", + "BROADCAST_CHANNEL", + ] + ] + """Where ads in this ad set direct people.""" + + dsa_beneficiary: Optional[str] + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + dsa_payor: Optional[str] + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + end_time: Optional[str] + """End time (ISO8601). Required for lifetime budgets.""" + + excluded_geo_locations: Optional[PlatformConfigMetaExcludedGeoLocations] + """Geo locations to exclude.""" + + facebook_positions: Optional[SequenceNotStr[str]] + """Facebook ad placements (feed, reels, stories, etc.).""" + + frequency_control_count: Optional[int] + """Represents non-fractional signed whole numeric values. + + Int can represent values between -(2^31) and 2^31 - 1. + """ + + frequency_control_days: Optional[int] + """Represents non-fractional signed whole numeric values. + + Int can represent values between -(2^31) and 2^31 - 1. + """ + + frequency_control_type: Optional[str] + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + geo_cities: Optional[Iterable[PlatformConfigMetaGeoCity]] + + geo_locations: Optional[PlatformConfigMetaGeoLocations] + """Geo targeting (countries, regions, cities, zips).""" + + geo_regions: Optional[Iterable[PlatformConfigMetaGeoRegion]] + + geo_zips: Optional[SequenceNotStr[str]] + + instagram_actor_id: Optional[str] + """Instagram account ID for this ad set.""" + + instagram_positions: Optional[SequenceNotStr[str]] + """Instagram ad placements (stream, story, reels, etc.).""" + + ios_devices: Optional[SequenceNotStr[str]] + + is_dynamic_creative: Optional[bool] + """Represents `true` or `false` values.""" + + lead_conversion_location: Optional[Literal["website", "instant_forms", "messenger", "instagram", "calls", "app"]] + + lead_form_config: Optional[PlatformConfigMetaLeadFormConfig] + """Configuration for a Meta lead gen instant form.""" + + lead_gen_form_id: Optional[str] + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + lifetime_budget: Optional[int] + """Lifetime budget in cents.""" + + lifetime_min_spend_target: Optional[str] + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + lifetime_spend_cap: Optional[str] + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + location_types: Optional[SequenceNotStr[str]] + + messenger_positions: Optional[SequenceNotStr[str]] + + optimization_goal: Optional[ + Literal[ + "NONE", + "APP_INSTALLS", + "AD_RECALL_LIFT", + "ENGAGED_USERS", + "EVENT_RESPONSES", + "IMPRESSIONS", + "LEAD_GENERATION", + "QUALITY_LEAD", + "LINK_CLICKS", + "OFFSITE_CONVERSIONS", + "PAGE_LIKES", + "POST_ENGAGEMENT", + "QUALITY_CALL", + "REACH", + "LANDING_PAGE_VIEWS", + "VISIT_INSTAGRAM_PROFILE", + "VALUE", + "THRUPLAY", + "DERIVED_EVENTS", + "APP_INSTALLS_AND_OFFSITE_CONVERSIONS", + "CONVERSATIONS", + "IN_APP_VALUE", + "MESSAGING_PURCHASE_CONVERSION", + "SUBSCRIBERS", + "REMINDERS_SET", + "MEANINGFUL_CALL_ATTEMPT", + "PROFILE_VISIT", + "PROFILE_AND_PAGE_ENGAGEMENT", + "TWO_SECOND_CONTINUOUS_VIDEO_VIEWS", + "ENGAGED_REACH", + "ENGAGED_PAGE_VIEWS", + "MESSAGING_DEEP_CONVERSATION_AND_FOLLOW", + "ADVERTISER_SILOED_VALUE", + "AUTOMATIC_OBJECTIVE", + "MESSAGING_APPOINTMENT_CONVERSION", + ] + ] + """What this ad set optimizes for on Meta.""" + + page_id: Optional[str] + """Facebook Page ID for this ad set.""" + + pixel_id: Optional[str] + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + promoted_object: Optional[PlatformConfigMetaPromotedObject] + """The object this ad set promotes (pixel, page, etc.).""" + + publisher_platforms: Optional[SequenceNotStr[str]] + """Platforms to publish on (facebook, instagram, messenger, audience_network).""" + + source_adset_id: Optional[str] + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + start_time: Optional[str] + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + status: Optional[Literal["ACTIVE", "PAUSED"]] + + targeting_automation: Optional[PlatformConfigMetaTargetingAutomation] + """Advantage+ audience expansion settings.""" + + threads_positions: Optional[SequenceNotStr[str]] + + updated_time: Optional[str] + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + user_device: Optional[SequenceNotStr[str]] + + user_os: Optional[SequenceNotStr[str]] + + whatsapp_phone_number: Optional[str] + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + whatsapp_positions: Optional[SequenceNotStr[str]] + + +class PlatformConfigTiktokAction(TypedDict, total=False): + """A single TikTok behavioral targeting entry. + + One category of past user behavior (what they did, over what window, on which kind of content). See docs/tiktok_api/ad_group.md § actions. + """ + + action_category_ids: Optional[SequenceNotStr[str]] + """Behavioral category IDs. Use /tool/action_category/ to list them.""" + + action_period: Optional[int] + """Lookback window in days. TikTok accepts 7, 15, 30, 60, 90, or 180.""" + + action_scene: Optional[Literal["VIDEO_RELATED", "CREATOR_RELATED", "HASHTAG_RELATED", "LIVE_RELATED"]] + """The category of TikTok content a behavioral targeting rule applies to. + + See docs/tiktok_api/ad_group.md § actions. + """ + + video_user_actions: Optional[ + List[Literal["WATCHED_TO_END", "LIKED", "COMMENTED", "SHARED", "FOLLOWED", "PROFILE_VISITED"]] + ] + """ + Specific video interactions (WATCHED_TO_END, LIKED, COMMENTED, SHARED, FOLLOWED, + PROFILE_VISITED). + """ + + +class PlatformConfigTiktokInstantFormConfigQuestion(TypedDict, total=False): + """A question for a TikTok instant form.""" + + field_type: Required[str] + """Question type (EMAIL, PHONE_NUMBER, NAME, CUSTOM).""" + + label: Optional[str] + """Custom label for the question.""" + + +class PlatformConfigTiktokInstantFormConfig(TypedDict, total=False): + """Instant form configuration for lead generation campaigns.""" + + privacy_policy_url: Required[str] + """URL to your privacy policy.""" + + questions: Required[Iterable[PlatformConfigTiktokInstantFormConfigQuestion]] + """Form questions (at least one required).""" + + button_text: Optional[str] + """Submit button text.""" + + greeting: Optional[str] + """Greeting text shown at the top of the form.""" + + name: Optional[str] + """Form name. Auto-generated if omitted.""" + + +class PlatformConfigTiktok(TypedDict, total=False): + """TikTok ad group configuration.""" + + actions: Optional[Iterable[PlatformConfigTiktokAction]] + + age_groups: Optional[List[Literal["AGE_13_17", "AGE_18_24", "AGE_25_34", "AGE_35_44", "AGE_45_54", "AGE_55_100"]]] + + app_id: Optional[str] + """App ID for app promotion campaigns.""" + + attribution_event_count: Optional[Literal["UNSET", "EVERY", "ONCE"]] + + audience_ids: Optional[SequenceNotStr[str]] + + audience_rule: Optional[Dict[str, object]] + """Represents untyped JSON""" + + audience_type: Optional[Literal["NORMAL", "SMART_INTERESTS_BEHAVIORS"]] + + bid_price: Optional[float] + """Bid price (cost per result for Cost Cap).""" + + bid_type: Optional[Literal["BID_TYPE_NO_BID", "BID_TYPE_CUSTOM"]] + """Bidding strategy (BID_TYPE_NO_BID, BID_TYPE_CUSTOM).""" + + billing_event: Optional[Literal["CPC", "CPM", "OCPM", "CPV"]] + """How you are billed on TikTok (CPC, CPM, OCPM, CPV).""" + + brand_safety_type: Optional[ + Literal["NO_BRAND_SAFETY", "STANDARD_INVENTORY", "LIMITED_INVENTORY", "FULL_INVENTORY", "EXPANDED_INVENTORY"] + ] + + budget_mode: Optional[Literal["BUDGET_MODE_DAY", "BUDGET_MODE_TOTAL", "BUDGET_MODE_DYNAMIC_DAILY_BUDGET"]] + """ + Budget mode (BUDGET_MODE_DAY, BUDGET_MODE_TOTAL, + BUDGET_MODE_DYNAMIC_DAILY_BUDGET). + """ + + carrier_ids: Optional[SequenceNotStr[str]] + + category_exclusion_ids: Optional[SequenceNotStr[str]] + + click_attribution_window: Optional[Literal["OFF", "ONE_DAY", "SEVEN_DAYS", "FOURTEEN_DAYS", "TWENTY_EIGHT_DAYS"]] + + comment_disabled: Optional[bool] + """Represents `true` or `false` values.""" + + contextual_tag_ids: Optional[SequenceNotStr[str]] + + conversion_bid_price: Optional[float] + """Target cost per conversion for oCPM.""" + + creative_material_mode: Optional[str] + """Creative delivery strategy.""" + + dayparting: Optional[str] + """Ad delivery schedule (48x7 character string).""" + + deep_funnel_event_source: Optional[str] + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + deep_funnel_event_source_id: Optional[str] + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + deep_funnel_optimization_status: Optional[Literal["ON", "OFF"]] + + device_model_ids: Optional[SequenceNotStr[str]] + + device_price_ranges: Optional[SequenceNotStr[str]] + + engaged_view_attribution_window: Optional[Literal["OFF", "ONE_DAY", "SEVEN_DAYS"]] + + excluded_audience_ids: Optional[SequenceNotStr[str]] + + excluded_location_ids: Optional[SequenceNotStr[str]] + """TikTok location/region IDs to exclude.""" + + frequency: Optional[int] + """Represents non-fractional signed whole numeric values. + + Int can represent values between -(2^31) and 2^31 - 1. + """ + + frequency_schedule: Optional[int] + """Represents non-fractional signed whole numeric values. + + Int can represent values between -(2^31) and 2^31 - 1. + """ + + gender: Optional[Literal["GENDER_UNLIMITED", "GENDER_MALE", "GENDER_FEMALE"]] + + identity_authorized_bc_id: Optional[str] + """Business Center ID for BC_AUTH_TT identity.""" + + identity_id: Optional[str] + """TikTok identity ID for the ad group.""" + + identity_type: Optional[str] + """Identity type (AUTH_CODE, TT_USER, BC_AUTH_TT).""" + + instant_form_config: Optional[PlatformConfigTiktokInstantFormConfig] + """Instant form configuration for lead generation campaigns.""" + + instant_form_id: Optional[str] + """ + TikTok instant form ID (set automatically when instant_form_config is provided). + """ + + interest_category_ids: Optional[SequenceNotStr[str]] + + interest_keyword_ids: Optional[SequenceNotStr[str]] + + inventory_filter_enabled: Optional[bool] + """Represents `true` or `false` values.""" + + ios14_targeting: Optional[Literal["UNSET", "IOS14_MINUS", "IOS14_PLUS", "ALL"]] + + isp_ids: Optional[SequenceNotStr[str]] + + languages: Optional[SequenceNotStr[str]] + + location_ids: Optional[SequenceNotStr[str]] + """TikTok location/region IDs for geo targeting.""" + + min_android_version: Optional[str] + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + min_ios_version: Optional[str] + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + network_types: Optional[SequenceNotStr[str]] + + operating_systems: Optional[List[Literal["ANDROID", "IOS"]]] + + operation_status: Optional[Literal["ENABLE", "DISABLE"]] + """Initial status (ENABLE, DISABLE).""" + + optimization_event: Optional[str] + """Conversion event (e.g., COMPLETE_PAYMENT).""" + + optimization_goal: Optional[ + Literal[ + "CLICK", + "CONVERT", + "INSTALL", + "IN_APP_EVENT", + "REACH", + "SHOW", + "VIDEO_VIEW", + "ENGAGED_VIEW", + "ENGAGED_VIEW_FIFTEEN", + "LEAD_GENERATION", + "PREFERRED_LEAD", + "CONVERSATION", + "FOLLOWERS", + "PROFILE_VIEWS", + "PAGE_VISIT", + "VALUE", + "AUTOMATIC_VALUE_OPTIMIZATION", + "TRAFFIC_LANDING_PAGE_VIEW", + "DESTINATION_VISIT", + "MT_LIVE_ROOM", + "PRODUCT_CLICK_IN_LIVE", + ] + ] + """What this ad group optimizes for on TikTok.""" + + pacing: Optional[Literal["PACING_MODE_SMOOTH", "PACING_MODE_FAST"]] + """Budget pacing (PACING_MODE_SMOOTH, PACING_MODE_FAST).""" + + pangle_audience_package_exclude_ids: Optional[SequenceNotStr[str]] + + pangle_audience_package_include_ids: Optional[SequenceNotStr[str]] + + pangle_block_app_ids: Optional[SequenceNotStr[str]] + + pixel_id: Optional[str] + """TikTok Pixel ID for conversion tracking.""" + + placement_type: Optional[Literal["PLACEMENT_TYPE_AUTOMATIC", "PLACEMENT_TYPE_NORMAL"]] + """Placement strategy (PLACEMENT_TYPE_AUTOMATIC, PLACEMENT_TYPE_NORMAL).""" + + placements: Optional[SequenceNotStr[str]] + """Placements (PLACEMENT_TIKTOK, PLACEMENT_PANGLE, etc.).""" + + product_set_id: Optional[str] + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + product_source: Optional[Literal["CATALOG", "STORE", "SHOWCASE"]] + + promotion_type: Optional[str] + """Promotion type (optimization location).""" + + schedule_end_time: Optional[str] + """Schedule end time (UTC, YYYY-MM-DD HH:MM:SS).""" + + schedule_start_time: Optional[str] + """Schedule start time (UTC, YYYY-MM-DD HH:MM:SS).""" + + schedule_type: Optional[Literal["SCHEDULE_START_END", "SCHEDULE_FROM_NOW"]] + """Schedule type (SCHEDULE_START_END, SCHEDULE_FROM_NOW).""" + + secondary_optimization_event: Optional[str] + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + shopping_ads_retargeting_actions_days: Optional[int] + """Represents non-fractional signed whole numeric values. + + Int can represent values between -(2^31) and 2^31 - 1. + """ + + shopping_ads_retargeting_type: Optional[Literal["OFF", "LAB1", "LAB2", "LAB3", "LAB4", "LAB5"]] + + spending_power: Optional[Literal["ALL", "HIGH"]] + + tiktok_subplacements: Optional[SequenceNotStr[str]] + """TikTok subplacements (IN_FEED, SEARCH_FEED, etc.).""" + + vertical_sensitivity_id: Optional[str] + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + video_download_disabled: Optional[bool] + """Represents `true` or `false` values.""" + + video_user_actions: Optional[SequenceNotStr[str]] + + view_attribution_window: Optional[Literal["OFF", "ONE_DAY", "SEVEN_DAYS"]] + + +class PlatformConfig(TypedDict, total=False): + """Platform-specific ad group configuration.""" + + meta: Optional[PlatformConfigMeta] + """Meta (Facebook/Instagram) ad set configuration.""" + + tiktok: Optional[PlatformConfigTiktok] + """TikTok ad group configuration.""" diff --git a/src/whop_sdk/types/ad_group_create_response.py b/src/whop_sdk/types/ad_group_create_response.py new file mode 100644 index 00000000..6c173684 --- /dev/null +++ b/src/whop_sdk/types/ad_group_create_response.py @@ -0,0 +1,438 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Dict, List, Union, Optional +from datetime import datetime +from typing_extensions import Literal, Annotated, TypeAlias + +from .._utils import PropertyInfo +from .._models import BaseModel + +__all__ = [ + "AdGroupCreateResponse", + "AdCampaign", + "Config", + "ConfigTargeting", + "PlatformConfig", + "PlatformConfigMetaAdGroupPlatformConfigType", + "PlatformConfigTiktokAdGroupPlatformConfigType", +] + + +class AdCampaign(BaseModel): + """The parent ad campaign""" + + id: str + """The unique identifier for the ad campaign.""" + + platform: Optional[Literal["meta", "tiktok"]] = None + """The platforms where an ad campaign can run.""" + + status: Literal[ + "active", "paused", "inactive", "stale", "pending_refund", "payment_failed", "draft", "in_review", "flagged" + ] + """Current status of the campaign (active, paused, or inactive)""" + + title: str + """The title of the ad campaign""" + + +class ConfigTargeting(BaseModel): + """Audience targeting settings (demographics, geo, interests, audiences, devices).""" + + age_max: Optional[int] = None + """Maximum age for demographic targeting.""" + + age_min: Optional[int] = None + """Minimum age for demographic targeting.""" + + countries: Optional[List[str]] = None + """ISO 3166-1 alpha-2 country codes targeted.""" + + device_platforms: Optional[List[Literal["mobile", "desktop"]]] = None + """Device platforms targeted.""" + + exclude_audience_ids: Optional[List[str]] = None + """Platform audience IDs excluded.""" + + genders: Optional[List[Literal["male", "female", "all"]]] = None + """Genders targeted.""" + + include_audience_ids: Optional[List[str]] = None + """Platform audience IDs included.""" + + interest_ids: Optional[List[str]] = None + """Platform-specific interest IDs targeted.""" + + languages: Optional[List[str]] = None + """Language codes targeted.""" + + placement_type: Optional[Literal["automatic", "manual"]] = None + """Placement strategy for ad delivery.""" + + +class Config(BaseModel): + """Unified ad group configuration (platform-agnostic)""" + + bid_amount: Optional[int] = None + """Bid cap amount in cents. Used when bid_strategy is bid_cap or cost_cap.""" + + bid_strategy: Optional[Literal["lowest_cost", "bid_cap", "cost_cap"]] = None + """Bid strategy: lowest_cost, bid_cap, or cost_cap.""" + + billing_event: Optional[Literal["impressions", "clicks", "optimized_cpm", "video_views"]] = None + """How you are billed (e.g., impressions, clicks).""" + + end_time: Optional[str] = None + """Scheduled end time (ISO8601). Required for lifetime budgets.""" + + frequency_cap: Optional[int] = None + """Maximum number of times to show ads to each person in the frequency interval.""" + + frequency_cap_interval_days: Optional[int] = None + """Number of days for the frequency cap interval.""" + + optimization_goal: Optional[ + Literal[ + "conversions", + "link_clicks", + "landing_page_views", + "reach", + "impressions", + "app_installs", + "video_views", + "lead_generation", + "value", + "page_likes", + "conversations", + "ad_recall_lift", + "two_second_continuous_video_views", + "post_engagement", + "event_responses", + "reminders_set", + "quality_lead", + ] + ] = None + """What the ad group optimizes for (e.g., conversions, link_clicks, reach).""" + + pacing: Optional[Literal["standard", "accelerated"]] = None + """Budget pacing: standard (even) or accelerated (fast).""" + + start_time: Optional[str] = None + """Scheduled start time (ISO8601).""" + + targeting: Optional[ConfigTargeting] = None + """Audience targeting settings (demographics, geo, interests, audiences, devices).""" + + +class PlatformConfigMetaAdGroupPlatformConfigType(BaseModel): + """Meta (Facebook/Instagram) ad set configuration.""" + + attribution_spec: Optional[List[Dict[str, object]]] = None + + bid_amount: Optional[int] = None + """Represents non-fractional signed whole numeric values. + + Int can represent values between -(2^31) and 2^31 - 1. + """ + + bid_strategy: Optional[ + Literal["LOWEST_COST_WITHOUT_CAP", "LOWEST_COST_WITH_BID_CAP", "COST_CAP", "LOWEST_COST_WITH_MIN_ROAS"] + ] = None + + billing_event: Optional[ + Literal[ + "APP_INSTALLS", + "CLICKS", + "IMPRESSIONS", + "LINK_CLICKS", + "NONE", + "OFFER_CLAIMS", + "PAGE_LIKES", + "POST_ENGAGEMENT", + "THRUPLAY", + "PURCHASE", + "LISTING_INTERACTION", + ] + ] = None + + daily_budget: Optional[int] = None + """Represents non-fractional signed whole numeric values. + + Int can represent values between -(2^31) and 2^31 - 1. + """ + + destination_type: Optional[ + Literal[ + "UNDEFINED", + "WEBSITE", + "APP", + "FACEBOOK", + "MESSENGER", + "WHATSAPP", + "INSTAGRAM_DIRECT", + "INSTAGRAM_PROFILE", + "PHONE_CALL", + "SHOP_AUTOMATIC", + "APPLINKS_AUTOMATIC", + "ON_AD", + "ON_POST", + "ON_VIDEO", + "ON_PAGE", + "ON_EVENT", + "MESSAGING_MESSENGER_WHATSAPP", + "MESSAGING_INSTAGRAM_DIRECT_MESSENGER", + "MESSAGING_INSTAGRAM_DIRECT_WHATSAPP", + "MESSAGING_INSTAGRAM_DIRECT_MESSENGER_WHATSAPP", + "INSTAGRAM_PROFILE_AND_FACEBOOK_PAGE", + "FACEBOOK_PAGE", + "INSTAGRAM_LIVE", + "FACEBOOK_LIVE", + "IMAGINE", + "LEAD_FROM_IG_DIRECT", + "LEAD_FROM_MESSENGER", + "WEBSITE_AND_LEAD_FORM", + "WEBSITE_AND_PHONE_CALL", + "BROADCAST_CHANNEL", + ] + ] = None + + end_time: Optional[str] = None + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + excluded_geo_locations: Optional[Dict[str, object]] = None + """Represents untyped JSON""" + + facebook_positions: Optional[List[str]] = None + + geo_locations: Optional[Dict[str, object]] = None + """Represents untyped JSON""" + + instagram_actor_id: Optional[str] = None + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + instagram_positions: Optional[List[str]] = None + + lifetime_budget: Optional[int] = None + """Represents non-fractional signed whole numeric values. + + Int can represent values between -(2^31) and 2^31 - 1. + """ + + optimization_goal: Optional[ + Literal[ + "NONE", + "APP_INSTALLS", + "AD_RECALL_LIFT", + "ENGAGED_USERS", + "EVENT_RESPONSES", + "IMPRESSIONS", + "LEAD_GENERATION", + "QUALITY_LEAD", + "LINK_CLICKS", + "OFFSITE_CONVERSIONS", + "PAGE_LIKES", + "POST_ENGAGEMENT", + "QUALITY_CALL", + "REACH", + "LANDING_PAGE_VIEWS", + "VISIT_INSTAGRAM_PROFILE", + "VALUE", + "THRUPLAY", + "DERIVED_EVENTS", + "APP_INSTALLS_AND_OFFSITE_CONVERSIONS", + "CONVERSATIONS", + "IN_APP_VALUE", + "MESSAGING_PURCHASE_CONVERSION", + "SUBSCRIBERS", + "REMINDERS_SET", + "MEANINGFUL_CALL_ATTEMPT", + "PROFILE_VISIT", + "PROFILE_AND_PAGE_ENGAGEMENT", + "TWO_SECOND_CONTINUOUS_VIDEO_VIEWS", + "ENGAGED_REACH", + "ENGAGED_PAGE_VIEWS", + "MESSAGING_DEEP_CONVERSATION_AND_FOLLOW", + "ADVERTISER_SILOED_VALUE", + "AUTOMATIC_OBJECTIVE", + "MESSAGING_APPOINTMENT_CONVERSION", + ] + ] = None + + page_id: Optional[str] = None + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + platform: Literal["meta", "tiktok"] + """The ad platform (meta, tiktok).""" + + promoted_object: Optional[Dict[str, object]] = None + """Represents untyped JSON""" + + publisher_platforms: Optional[List[str]] = None + + status: Optional[Literal["ACTIVE", "PAUSED"]] = None + + targeting_automation: Optional[Dict[str, object]] = None + """Represents untyped JSON""" + + typename: Literal["MetaAdGroupPlatformConfigType"] + """The typename of this object""" + + +class PlatformConfigTiktokAdGroupPlatformConfigType(BaseModel): + """TikTok ad group configuration.""" + + bid_price: Optional[float] = None + """ + Represents signed double-precision fractional values as specified by + [IEEE 754](https://en.wikipedia.org/wiki/IEEE_floating_point). + """ + + bid_type: Optional[Literal["BID_TYPE_NO_BID", "BID_TYPE_CUSTOM"]] = None + + billing_event: Optional[Literal["CPC", "CPM", "OCPM", "CPV"]] = None + + budget_mode: Optional[Literal["BUDGET_MODE_DAY", "BUDGET_MODE_TOTAL", "BUDGET_MODE_DYNAMIC_DAILY_BUDGET"]] = None + + conversion_bid_price: Optional[float] = None + """ + Represents signed double-precision fractional values as specified by + [IEEE 754](https://en.wikipedia.org/wiki/IEEE_floating_point). + """ + + identity_id: Optional[str] = None + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + identity_type: Optional[str] = None + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + operation_status: Optional[Literal["ENABLE", "DISABLE"]] = None + + optimization_event: Optional[str] = None + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + optimization_goal: Optional[ + Literal[ + "CLICK", + "CONVERT", + "INSTALL", + "IN_APP_EVENT", + "REACH", + "SHOW", + "VIDEO_VIEW", + "ENGAGED_VIEW", + "ENGAGED_VIEW_FIFTEEN", + "LEAD_GENERATION", + "PREFERRED_LEAD", + "CONVERSATION", + "FOLLOWERS", + "PROFILE_VIEWS", + "PAGE_VISIT", + "VALUE", + "AUTOMATIC_VALUE_OPTIMIZATION", + "TRAFFIC_LANDING_PAGE_VIEW", + "DESTINATION_VISIT", + "MT_LIVE_ROOM", + "PRODUCT_CLICK_IN_LIVE", + ] + ] = None + + pacing: Optional[Literal["PACING_MODE_SMOOTH", "PACING_MODE_FAST"]] = None + + pixel_id: Optional[str] = None + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + placement_type: Optional[Literal["PLACEMENT_TYPE_AUTOMATIC", "PLACEMENT_TYPE_NORMAL"]] = None + + placements: Optional[List[str]] = None + + platform: Literal["meta", "tiktok"] + """The ad platform (meta, tiktok).""" + + schedule_end_time: Optional[str] = None + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + schedule_start_time: Optional[str] = None + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + schedule_type: Optional[Literal["SCHEDULE_START_END", "SCHEDULE_FROM_NOW"]] = None + + typename: Literal["TiktokAdGroupPlatformConfigType"] + """The typename of this object""" + + +PlatformConfig: TypeAlias = Annotated[ + Union[ + Optional[PlatformConfigMetaAdGroupPlatformConfigType], Optional[PlatformConfigTiktokAdGroupPlatformConfigType] + ], + PropertyInfo(discriminator="typename"), +] + + +class AdGroupCreateResponse(BaseModel): + """An external ad group (ad set) belonging to an ad campaign""" + + id: str + """The unique identifier for the external ad group.""" + + ad_campaign: AdCampaign + """The parent ad campaign""" + + config: Optional[Config] = None + """Unified ad group configuration (platform-agnostic)""" + + created_at: datetime + """The datetime the external ad group was created.""" + + daily_budget: Optional[float] = None + """Daily budget in dollars (nil for lifetime budgets)""" + + name: Optional[str] = None + """Human-readable ad group name""" + + platform_config: PlatformConfig + """Typed platform-specific configuration. + + Use inline fragments (... on MetaAdGroupPlatformConfigType). + """ + + status: Literal["active", "paused", "inactive", "in_review", "rejected", "flagged"] + """Current operational status of the ad group""" + + updated_at: datetime + """The datetime the external ad group was last updated.""" diff --git a/src/whop_sdk/types/ad_group_delete_response.py b/src/whop_sdk/types/ad_group_delete_response.py new file mode 100644 index 00000000..c7869a56 --- /dev/null +++ b/src/whop_sdk/types/ad_group_delete_response.py @@ -0,0 +1,7 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import TypeAlias + +__all__ = ["AdGroupDeleteResponse"] + +AdGroupDeleteResponse: TypeAlias = bool diff --git a/src/whop_sdk/types/ad_group_list_params.py b/src/whop_sdk/types/ad_group_list_params.py new file mode 100644 index 00000000..cde96fb8 --- /dev/null +++ b/src/whop_sdk/types/ad_group_list_params.py @@ -0,0 +1,43 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Union, Optional +from datetime import datetime +from typing_extensions import Literal, Annotated, TypedDict + +from .._utils import PropertyInfo + +__all__ = ["AdGroupListParams"] + + +class AdGroupListParams(TypedDict, total=False): + after: Optional[str] + """Returns the elements in the list that come after the specified cursor.""" + + before: Optional[str] + """Returns the elements in the list that come before the specified cursor.""" + + campaign_id: Optional[str] + """Filter by campaign. Provide exactly one of campaign_id or company_id.""" + + company_id: Optional[str] + """Filter by company. Provide exactly one of campaign_id or company_id.""" + + created_after: Annotated[Union[str, datetime, None], PropertyInfo(format="iso8601")] + """Only return ad groups created after this timestamp.""" + + created_before: Annotated[Union[str, datetime, None], PropertyInfo(format="iso8601")] + """Only return ad groups created before this timestamp.""" + + first: Optional[int] + """Returns the first _n_ elements from the list.""" + + last: Optional[int] + """Returns the last _n_ elements from the list.""" + + query: Optional[str] + """Case-insensitive substring match against the ad group name.""" + + status: Optional[Literal["active", "paused", "inactive", "in_review", "rejected", "flagged"]] + """The status of an external ad group.""" diff --git a/src/whop_sdk/types/ad_group_list_response.py b/src/whop_sdk/types/ad_group_list_response.py new file mode 100644 index 00000000..26e39986 --- /dev/null +++ b/src/whop_sdk/types/ad_group_list_response.py @@ -0,0 +1,416 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Dict, List, Union, Optional +from datetime import datetime +from typing_extensions import Literal, Annotated, TypeAlias + +from .._utils import PropertyInfo +from .._models import BaseModel + +__all__ = [ + "AdGroupListResponse", + "Config", + "ConfigTargeting", + "PlatformConfig", + "PlatformConfigMetaAdGroupPlatformConfigType", + "PlatformConfigTiktokAdGroupPlatformConfigType", +] + + +class ConfigTargeting(BaseModel): + """Audience targeting settings (demographics, geo, interests, audiences, devices).""" + + age_max: Optional[int] = None + """Maximum age for demographic targeting.""" + + age_min: Optional[int] = None + """Minimum age for demographic targeting.""" + + countries: Optional[List[str]] = None + """ISO 3166-1 alpha-2 country codes targeted.""" + + device_platforms: Optional[List[Literal["mobile", "desktop"]]] = None + """Device platforms targeted.""" + + exclude_audience_ids: Optional[List[str]] = None + """Platform audience IDs excluded.""" + + genders: Optional[List[Literal["male", "female", "all"]]] = None + """Genders targeted.""" + + include_audience_ids: Optional[List[str]] = None + """Platform audience IDs included.""" + + interest_ids: Optional[List[str]] = None + """Platform-specific interest IDs targeted.""" + + languages: Optional[List[str]] = None + """Language codes targeted.""" + + placement_type: Optional[Literal["automatic", "manual"]] = None + """Placement strategy for ad delivery.""" + + +class Config(BaseModel): + """Unified ad group configuration (platform-agnostic)""" + + bid_amount: Optional[int] = None + """Bid cap amount in cents. Used when bid_strategy is bid_cap or cost_cap.""" + + bid_strategy: Optional[Literal["lowest_cost", "bid_cap", "cost_cap"]] = None + """Bid strategy: lowest_cost, bid_cap, or cost_cap.""" + + billing_event: Optional[Literal["impressions", "clicks", "optimized_cpm", "video_views"]] = None + """How you are billed (e.g., impressions, clicks).""" + + end_time: Optional[str] = None + """Scheduled end time (ISO8601). Required for lifetime budgets.""" + + frequency_cap: Optional[int] = None + """Maximum number of times to show ads to each person in the frequency interval.""" + + frequency_cap_interval_days: Optional[int] = None + """Number of days for the frequency cap interval.""" + + optimization_goal: Optional[ + Literal[ + "conversions", + "link_clicks", + "landing_page_views", + "reach", + "impressions", + "app_installs", + "video_views", + "lead_generation", + "value", + "page_likes", + "conversations", + "ad_recall_lift", + "two_second_continuous_video_views", + "post_engagement", + "event_responses", + "reminders_set", + "quality_lead", + ] + ] = None + """What the ad group optimizes for (e.g., conversions, link_clicks, reach).""" + + pacing: Optional[Literal["standard", "accelerated"]] = None + """Budget pacing: standard (even) or accelerated (fast).""" + + start_time: Optional[str] = None + """Scheduled start time (ISO8601).""" + + targeting: Optional[ConfigTargeting] = None + """Audience targeting settings (demographics, geo, interests, audiences, devices).""" + + +class PlatformConfigMetaAdGroupPlatformConfigType(BaseModel): + """Meta (Facebook/Instagram) ad set configuration.""" + + attribution_spec: Optional[List[Dict[str, object]]] = None + + bid_amount: Optional[int] = None + """Represents non-fractional signed whole numeric values. + + Int can represent values between -(2^31) and 2^31 - 1. + """ + + bid_strategy: Optional[ + Literal["LOWEST_COST_WITHOUT_CAP", "LOWEST_COST_WITH_BID_CAP", "COST_CAP", "LOWEST_COST_WITH_MIN_ROAS"] + ] = None + + billing_event: Optional[ + Literal[ + "APP_INSTALLS", + "CLICKS", + "IMPRESSIONS", + "LINK_CLICKS", + "NONE", + "OFFER_CLAIMS", + "PAGE_LIKES", + "POST_ENGAGEMENT", + "THRUPLAY", + "PURCHASE", + "LISTING_INTERACTION", + ] + ] = None + + daily_budget: Optional[int] = None + """Represents non-fractional signed whole numeric values. + + Int can represent values between -(2^31) and 2^31 - 1. + """ + + destination_type: Optional[ + Literal[ + "UNDEFINED", + "WEBSITE", + "APP", + "FACEBOOK", + "MESSENGER", + "WHATSAPP", + "INSTAGRAM_DIRECT", + "INSTAGRAM_PROFILE", + "PHONE_CALL", + "SHOP_AUTOMATIC", + "APPLINKS_AUTOMATIC", + "ON_AD", + "ON_POST", + "ON_VIDEO", + "ON_PAGE", + "ON_EVENT", + "MESSAGING_MESSENGER_WHATSAPP", + "MESSAGING_INSTAGRAM_DIRECT_MESSENGER", + "MESSAGING_INSTAGRAM_DIRECT_WHATSAPP", + "MESSAGING_INSTAGRAM_DIRECT_MESSENGER_WHATSAPP", + "INSTAGRAM_PROFILE_AND_FACEBOOK_PAGE", + "FACEBOOK_PAGE", + "INSTAGRAM_LIVE", + "FACEBOOK_LIVE", + "IMAGINE", + "LEAD_FROM_IG_DIRECT", + "LEAD_FROM_MESSENGER", + "WEBSITE_AND_LEAD_FORM", + "WEBSITE_AND_PHONE_CALL", + "BROADCAST_CHANNEL", + ] + ] = None + + end_time: Optional[str] = None + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + excluded_geo_locations: Optional[Dict[str, object]] = None + """Represents untyped JSON""" + + facebook_positions: Optional[List[str]] = None + + geo_locations: Optional[Dict[str, object]] = None + """Represents untyped JSON""" + + instagram_actor_id: Optional[str] = None + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + instagram_positions: Optional[List[str]] = None + + lifetime_budget: Optional[int] = None + """Represents non-fractional signed whole numeric values. + + Int can represent values between -(2^31) and 2^31 - 1. + """ + + optimization_goal: Optional[ + Literal[ + "NONE", + "APP_INSTALLS", + "AD_RECALL_LIFT", + "ENGAGED_USERS", + "EVENT_RESPONSES", + "IMPRESSIONS", + "LEAD_GENERATION", + "QUALITY_LEAD", + "LINK_CLICKS", + "OFFSITE_CONVERSIONS", + "PAGE_LIKES", + "POST_ENGAGEMENT", + "QUALITY_CALL", + "REACH", + "LANDING_PAGE_VIEWS", + "VISIT_INSTAGRAM_PROFILE", + "VALUE", + "THRUPLAY", + "DERIVED_EVENTS", + "APP_INSTALLS_AND_OFFSITE_CONVERSIONS", + "CONVERSATIONS", + "IN_APP_VALUE", + "MESSAGING_PURCHASE_CONVERSION", + "SUBSCRIBERS", + "REMINDERS_SET", + "MEANINGFUL_CALL_ATTEMPT", + "PROFILE_VISIT", + "PROFILE_AND_PAGE_ENGAGEMENT", + "TWO_SECOND_CONTINUOUS_VIDEO_VIEWS", + "ENGAGED_REACH", + "ENGAGED_PAGE_VIEWS", + "MESSAGING_DEEP_CONVERSATION_AND_FOLLOW", + "ADVERTISER_SILOED_VALUE", + "AUTOMATIC_OBJECTIVE", + "MESSAGING_APPOINTMENT_CONVERSION", + ] + ] = None + + page_id: Optional[str] = None + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + platform: Literal["meta", "tiktok"] + """The ad platform (meta, tiktok).""" + + promoted_object: Optional[Dict[str, object]] = None + """Represents untyped JSON""" + + publisher_platforms: Optional[List[str]] = None + + status: Optional[Literal["ACTIVE", "PAUSED"]] = None + + targeting_automation: Optional[Dict[str, object]] = None + """Represents untyped JSON""" + + typename: Literal["MetaAdGroupPlatformConfigType"] + """The typename of this object""" + + +class PlatformConfigTiktokAdGroupPlatformConfigType(BaseModel): + """TikTok ad group configuration.""" + + bid_price: Optional[float] = None + """ + Represents signed double-precision fractional values as specified by + [IEEE 754](https://en.wikipedia.org/wiki/IEEE_floating_point). + """ + + bid_type: Optional[Literal["BID_TYPE_NO_BID", "BID_TYPE_CUSTOM"]] = None + + billing_event: Optional[Literal["CPC", "CPM", "OCPM", "CPV"]] = None + + budget_mode: Optional[Literal["BUDGET_MODE_DAY", "BUDGET_MODE_TOTAL", "BUDGET_MODE_DYNAMIC_DAILY_BUDGET"]] = None + + conversion_bid_price: Optional[float] = None + """ + Represents signed double-precision fractional values as specified by + [IEEE 754](https://en.wikipedia.org/wiki/IEEE_floating_point). + """ + + identity_id: Optional[str] = None + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + identity_type: Optional[str] = None + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + operation_status: Optional[Literal["ENABLE", "DISABLE"]] = None + + optimization_event: Optional[str] = None + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + optimization_goal: Optional[ + Literal[ + "CLICK", + "CONVERT", + "INSTALL", + "IN_APP_EVENT", + "REACH", + "SHOW", + "VIDEO_VIEW", + "ENGAGED_VIEW", + "ENGAGED_VIEW_FIFTEEN", + "LEAD_GENERATION", + "PREFERRED_LEAD", + "CONVERSATION", + "FOLLOWERS", + "PROFILE_VIEWS", + "PAGE_VISIT", + "VALUE", + "AUTOMATIC_VALUE_OPTIMIZATION", + "TRAFFIC_LANDING_PAGE_VIEW", + "DESTINATION_VISIT", + "MT_LIVE_ROOM", + "PRODUCT_CLICK_IN_LIVE", + ] + ] = None + + pacing: Optional[Literal["PACING_MODE_SMOOTH", "PACING_MODE_FAST"]] = None + + pixel_id: Optional[str] = None + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + placement_type: Optional[Literal["PLACEMENT_TYPE_AUTOMATIC", "PLACEMENT_TYPE_NORMAL"]] = None + + placements: Optional[List[str]] = None + + platform: Literal["meta", "tiktok"] + """The ad platform (meta, tiktok).""" + + schedule_end_time: Optional[str] = None + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + schedule_start_time: Optional[str] = None + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + schedule_type: Optional[Literal["SCHEDULE_START_END", "SCHEDULE_FROM_NOW"]] = None + + typename: Literal["TiktokAdGroupPlatformConfigType"] + """The typename of this object""" + + +PlatformConfig: TypeAlias = Annotated[ + Union[ + Optional[PlatformConfigMetaAdGroupPlatformConfigType], Optional[PlatformConfigTiktokAdGroupPlatformConfigType] + ], + PropertyInfo(discriminator="typename"), +] + + +class AdGroupListResponse(BaseModel): + """An external ad group (ad set) belonging to an ad campaign""" + + id: str + """The unique identifier for the external ad group.""" + + config: Optional[Config] = None + """Unified ad group configuration (platform-agnostic)""" + + created_at: datetime + """The datetime the external ad group was created.""" + + daily_budget: Optional[float] = None + """Daily budget in dollars (nil for lifetime budgets)""" + + name: Optional[str] = None + """Human-readable ad group name""" + + platform_config: PlatformConfig + """Typed platform-specific configuration. + + Use inline fragments (... on MetaAdGroupPlatformConfigType). + """ + + status: Literal["active", "paused", "inactive", "in_review", "rejected", "flagged"] + """Current operational status of the ad group""" + + updated_at: datetime + """The datetime the external ad group was last updated.""" diff --git a/src/whop_sdk/types/ad_group_retrieve_response.py b/src/whop_sdk/types/ad_group_retrieve_response.py new file mode 100644 index 00000000..9a36fe02 --- /dev/null +++ b/src/whop_sdk/types/ad_group_retrieve_response.py @@ -0,0 +1,438 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Dict, List, Union, Optional +from datetime import datetime +from typing_extensions import Literal, Annotated, TypeAlias + +from .._utils import PropertyInfo +from .._models import BaseModel + +__all__ = [ + "AdGroupRetrieveResponse", + "AdCampaign", + "Config", + "ConfigTargeting", + "PlatformConfig", + "PlatformConfigMetaAdGroupPlatformConfigType", + "PlatformConfigTiktokAdGroupPlatformConfigType", +] + + +class AdCampaign(BaseModel): + """The parent ad campaign""" + + id: str + """The unique identifier for the ad campaign.""" + + platform: Optional[Literal["meta", "tiktok"]] = None + """The platforms where an ad campaign can run.""" + + status: Literal[ + "active", "paused", "inactive", "stale", "pending_refund", "payment_failed", "draft", "in_review", "flagged" + ] + """Current status of the campaign (active, paused, or inactive)""" + + title: str + """The title of the ad campaign""" + + +class ConfigTargeting(BaseModel): + """Audience targeting settings (demographics, geo, interests, audiences, devices).""" + + age_max: Optional[int] = None + """Maximum age for demographic targeting.""" + + age_min: Optional[int] = None + """Minimum age for demographic targeting.""" + + countries: Optional[List[str]] = None + """ISO 3166-1 alpha-2 country codes targeted.""" + + device_platforms: Optional[List[Literal["mobile", "desktop"]]] = None + """Device platforms targeted.""" + + exclude_audience_ids: Optional[List[str]] = None + """Platform audience IDs excluded.""" + + genders: Optional[List[Literal["male", "female", "all"]]] = None + """Genders targeted.""" + + include_audience_ids: Optional[List[str]] = None + """Platform audience IDs included.""" + + interest_ids: Optional[List[str]] = None + """Platform-specific interest IDs targeted.""" + + languages: Optional[List[str]] = None + """Language codes targeted.""" + + placement_type: Optional[Literal["automatic", "manual"]] = None + """Placement strategy for ad delivery.""" + + +class Config(BaseModel): + """Unified ad group configuration (platform-agnostic)""" + + bid_amount: Optional[int] = None + """Bid cap amount in cents. Used when bid_strategy is bid_cap or cost_cap.""" + + bid_strategy: Optional[Literal["lowest_cost", "bid_cap", "cost_cap"]] = None + """Bid strategy: lowest_cost, bid_cap, or cost_cap.""" + + billing_event: Optional[Literal["impressions", "clicks", "optimized_cpm", "video_views"]] = None + """How you are billed (e.g., impressions, clicks).""" + + end_time: Optional[str] = None + """Scheduled end time (ISO8601). Required for lifetime budgets.""" + + frequency_cap: Optional[int] = None + """Maximum number of times to show ads to each person in the frequency interval.""" + + frequency_cap_interval_days: Optional[int] = None + """Number of days for the frequency cap interval.""" + + optimization_goal: Optional[ + Literal[ + "conversions", + "link_clicks", + "landing_page_views", + "reach", + "impressions", + "app_installs", + "video_views", + "lead_generation", + "value", + "page_likes", + "conversations", + "ad_recall_lift", + "two_second_continuous_video_views", + "post_engagement", + "event_responses", + "reminders_set", + "quality_lead", + ] + ] = None + """What the ad group optimizes for (e.g., conversions, link_clicks, reach).""" + + pacing: Optional[Literal["standard", "accelerated"]] = None + """Budget pacing: standard (even) or accelerated (fast).""" + + start_time: Optional[str] = None + """Scheduled start time (ISO8601).""" + + targeting: Optional[ConfigTargeting] = None + """Audience targeting settings (demographics, geo, interests, audiences, devices).""" + + +class PlatformConfigMetaAdGroupPlatformConfigType(BaseModel): + """Meta (Facebook/Instagram) ad set configuration.""" + + attribution_spec: Optional[List[Dict[str, object]]] = None + + bid_amount: Optional[int] = None + """Represents non-fractional signed whole numeric values. + + Int can represent values between -(2^31) and 2^31 - 1. + """ + + bid_strategy: Optional[ + Literal["LOWEST_COST_WITHOUT_CAP", "LOWEST_COST_WITH_BID_CAP", "COST_CAP", "LOWEST_COST_WITH_MIN_ROAS"] + ] = None + + billing_event: Optional[ + Literal[ + "APP_INSTALLS", + "CLICKS", + "IMPRESSIONS", + "LINK_CLICKS", + "NONE", + "OFFER_CLAIMS", + "PAGE_LIKES", + "POST_ENGAGEMENT", + "THRUPLAY", + "PURCHASE", + "LISTING_INTERACTION", + ] + ] = None + + daily_budget: Optional[int] = None + """Represents non-fractional signed whole numeric values. + + Int can represent values between -(2^31) and 2^31 - 1. + """ + + destination_type: Optional[ + Literal[ + "UNDEFINED", + "WEBSITE", + "APP", + "FACEBOOK", + "MESSENGER", + "WHATSAPP", + "INSTAGRAM_DIRECT", + "INSTAGRAM_PROFILE", + "PHONE_CALL", + "SHOP_AUTOMATIC", + "APPLINKS_AUTOMATIC", + "ON_AD", + "ON_POST", + "ON_VIDEO", + "ON_PAGE", + "ON_EVENT", + "MESSAGING_MESSENGER_WHATSAPP", + "MESSAGING_INSTAGRAM_DIRECT_MESSENGER", + "MESSAGING_INSTAGRAM_DIRECT_WHATSAPP", + "MESSAGING_INSTAGRAM_DIRECT_MESSENGER_WHATSAPP", + "INSTAGRAM_PROFILE_AND_FACEBOOK_PAGE", + "FACEBOOK_PAGE", + "INSTAGRAM_LIVE", + "FACEBOOK_LIVE", + "IMAGINE", + "LEAD_FROM_IG_DIRECT", + "LEAD_FROM_MESSENGER", + "WEBSITE_AND_LEAD_FORM", + "WEBSITE_AND_PHONE_CALL", + "BROADCAST_CHANNEL", + ] + ] = None + + end_time: Optional[str] = None + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + excluded_geo_locations: Optional[Dict[str, object]] = None + """Represents untyped JSON""" + + facebook_positions: Optional[List[str]] = None + + geo_locations: Optional[Dict[str, object]] = None + """Represents untyped JSON""" + + instagram_actor_id: Optional[str] = None + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + instagram_positions: Optional[List[str]] = None + + lifetime_budget: Optional[int] = None + """Represents non-fractional signed whole numeric values. + + Int can represent values between -(2^31) and 2^31 - 1. + """ + + optimization_goal: Optional[ + Literal[ + "NONE", + "APP_INSTALLS", + "AD_RECALL_LIFT", + "ENGAGED_USERS", + "EVENT_RESPONSES", + "IMPRESSIONS", + "LEAD_GENERATION", + "QUALITY_LEAD", + "LINK_CLICKS", + "OFFSITE_CONVERSIONS", + "PAGE_LIKES", + "POST_ENGAGEMENT", + "QUALITY_CALL", + "REACH", + "LANDING_PAGE_VIEWS", + "VISIT_INSTAGRAM_PROFILE", + "VALUE", + "THRUPLAY", + "DERIVED_EVENTS", + "APP_INSTALLS_AND_OFFSITE_CONVERSIONS", + "CONVERSATIONS", + "IN_APP_VALUE", + "MESSAGING_PURCHASE_CONVERSION", + "SUBSCRIBERS", + "REMINDERS_SET", + "MEANINGFUL_CALL_ATTEMPT", + "PROFILE_VISIT", + "PROFILE_AND_PAGE_ENGAGEMENT", + "TWO_SECOND_CONTINUOUS_VIDEO_VIEWS", + "ENGAGED_REACH", + "ENGAGED_PAGE_VIEWS", + "MESSAGING_DEEP_CONVERSATION_AND_FOLLOW", + "ADVERTISER_SILOED_VALUE", + "AUTOMATIC_OBJECTIVE", + "MESSAGING_APPOINTMENT_CONVERSION", + ] + ] = None + + page_id: Optional[str] = None + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + platform: Literal["meta", "tiktok"] + """The ad platform (meta, tiktok).""" + + promoted_object: Optional[Dict[str, object]] = None + """Represents untyped JSON""" + + publisher_platforms: Optional[List[str]] = None + + status: Optional[Literal["ACTIVE", "PAUSED"]] = None + + targeting_automation: Optional[Dict[str, object]] = None + """Represents untyped JSON""" + + typename: Literal["MetaAdGroupPlatformConfigType"] + """The typename of this object""" + + +class PlatformConfigTiktokAdGroupPlatformConfigType(BaseModel): + """TikTok ad group configuration.""" + + bid_price: Optional[float] = None + """ + Represents signed double-precision fractional values as specified by + [IEEE 754](https://en.wikipedia.org/wiki/IEEE_floating_point). + """ + + bid_type: Optional[Literal["BID_TYPE_NO_BID", "BID_TYPE_CUSTOM"]] = None + + billing_event: Optional[Literal["CPC", "CPM", "OCPM", "CPV"]] = None + + budget_mode: Optional[Literal["BUDGET_MODE_DAY", "BUDGET_MODE_TOTAL", "BUDGET_MODE_DYNAMIC_DAILY_BUDGET"]] = None + + conversion_bid_price: Optional[float] = None + """ + Represents signed double-precision fractional values as specified by + [IEEE 754](https://en.wikipedia.org/wiki/IEEE_floating_point). + """ + + identity_id: Optional[str] = None + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + identity_type: Optional[str] = None + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + operation_status: Optional[Literal["ENABLE", "DISABLE"]] = None + + optimization_event: Optional[str] = None + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + optimization_goal: Optional[ + Literal[ + "CLICK", + "CONVERT", + "INSTALL", + "IN_APP_EVENT", + "REACH", + "SHOW", + "VIDEO_VIEW", + "ENGAGED_VIEW", + "ENGAGED_VIEW_FIFTEEN", + "LEAD_GENERATION", + "PREFERRED_LEAD", + "CONVERSATION", + "FOLLOWERS", + "PROFILE_VIEWS", + "PAGE_VISIT", + "VALUE", + "AUTOMATIC_VALUE_OPTIMIZATION", + "TRAFFIC_LANDING_PAGE_VIEW", + "DESTINATION_VISIT", + "MT_LIVE_ROOM", + "PRODUCT_CLICK_IN_LIVE", + ] + ] = None + + pacing: Optional[Literal["PACING_MODE_SMOOTH", "PACING_MODE_FAST"]] = None + + pixel_id: Optional[str] = None + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + placement_type: Optional[Literal["PLACEMENT_TYPE_AUTOMATIC", "PLACEMENT_TYPE_NORMAL"]] = None + + placements: Optional[List[str]] = None + + platform: Literal["meta", "tiktok"] + """The ad platform (meta, tiktok).""" + + schedule_end_time: Optional[str] = None + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + schedule_start_time: Optional[str] = None + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + schedule_type: Optional[Literal["SCHEDULE_START_END", "SCHEDULE_FROM_NOW"]] = None + + typename: Literal["TiktokAdGroupPlatformConfigType"] + """The typename of this object""" + + +PlatformConfig: TypeAlias = Annotated[ + Union[ + Optional[PlatformConfigMetaAdGroupPlatformConfigType], Optional[PlatformConfigTiktokAdGroupPlatformConfigType] + ], + PropertyInfo(discriminator="typename"), +] + + +class AdGroupRetrieveResponse(BaseModel): + """An external ad group (ad set) belonging to an ad campaign""" + + id: str + """The unique identifier for the external ad group.""" + + ad_campaign: AdCampaign + """The parent ad campaign""" + + config: Optional[Config] = None + """Unified ad group configuration (platform-agnostic)""" + + created_at: datetime + """The datetime the external ad group was created.""" + + daily_budget: Optional[float] = None + """Daily budget in dollars (nil for lifetime budgets)""" + + name: Optional[str] = None + """Human-readable ad group name""" + + platform_config: PlatformConfig + """Typed platform-specific configuration. + + Use inline fragments (... on MetaAdGroupPlatformConfigType). + """ + + status: Literal["active", "paused", "inactive", "in_review", "rejected", "flagged"] + """Current operational status of the ad group""" + + updated_at: datetime + """The datetime the external ad group was last updated.""" diff --git a/src/whop_sdk/types/ad_group_update_params.py b/src/whop_sdk/types/ad_group_update_params.py new file mode 100644 index 00000000..bb7e7265 --- /dev/null +++ b/src/whop_sdk/types/ad_group_update_params.py @@ -0,0 +1,1216 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Dict, List, Iterable, Optional +from typing_extensions import Literal, Required, TypedDict + +from .._types import SequenceNotStr + +__all__ = [ + "AdGroupUpdateParams", + "Config", + "ConfigTargeting", + "PlatformConfig", + "PlatformConfigMeta", + "PlatformConfigMetaAttributionSpec", + "PlatformConfigMetaExcludedGeoLocations", + "PlatformConfigMetaExcludedGeoLocationsCity", + "PlatformConfigMetaExcludedGeoLocationsRegion", + "PlatformConfigMetaExcludedGeoLocationsZip", + "PlatformConfigMetaGeoCity", + "PlatformConfigMetaGeoLocations", + "PlatformConfigMetaGeoLocationsCity", + "PlatformConfigMetaGeoLocationsRegion", + "PlatformConfigMetaGeoLocationsZip", + "PlatformConfigMetaGeoRegion", + "PlatformConfigMetaLeadFormConfig", + "PlatformConfigMetaLeadFormConfigQuestion", + "PlatformConfigMetaLeadFormConfigQuestionDependentConditionalQuestion", + "PlatformConfigMetaLeadFormConfigQuestionDependentConditionalQuestionOption", + "PlatformConfigMetaLeadFormConfigQuestionDependentConditionalQuestionOptionLogic", + "PlatformConfigMetaLeadFormConfigQuestionOption", + "PlatformConfigMetaLeadFormConfigQuestionOptionLogic", + "PlatformConfigMetaLeadFormConfigCustomDisclaimerCheckbox", + "PlatformConfigMetaLeadFormConfigThankYouPage", + "PlatformConfigMetaPromotedObject", + "PlatformConfigMetaTargetingAutomation", + "PlatformConfigTiktok", + "PlatformConfigTiktokAction", + "PlatformConfigTiktokInstantFormConfig", + "PlatformConfigTiktokInstantFormConfigQuestion", +] + + +class AdGroupUpdateParams(TypedDict, total=False): + budget: Optional[float] + """Budget amount in dollars.""" + + budget_type: Optional[Literal["daily", "lifetime"]] + """The budget type for an ad campaign or ad group.""" + + config: Optional[Config] + """Unified ad group configuration (bidding, optimization, targeting).""" + + daily_budget: Optional[float] + """Daily budget in dollars.""" + + name: Optional[str] + """Human-readable ad group name.""" + + platform_config: Optional[PlatformConfig] + """Platform-specific ad group configuration.""" + + status: Optional[Literal["active", "paused", "inactive", "in_review", "rejected", "flagged"]] + """The status of an external ad group.""" + + +class ConfigTargeting(TypedDict, total=False): + """Audience targeting settings (demographics, geo, interests, audiences, devices).""" + + age_max: Optional[int] + """Maximum age for demographic targeting.""" + + age_min: Optional[int] + """Minimum age for demographic targeting.""" + + countries: Optional[SequenceNotStr[str]] + """ISO 3166-1 alpha-2 country codes to target.""" + + device_platforms: Optional[List[Literal["mobile", "desktop"]]] + """Device platforms to target.""" + + exclude_audience_ids: Optional[SequenceNotStr[str]] + """Platform audience IDs to exclude.""" + + genders: Optional[List[Literal["male", "female", "all"]]] + """Genders to target.""" + + include_audience_ids: Optional[SequenceNotStr[str]] + """Platform audience IDs to include.""" + + interest_ids: Optional[SequenceNotStr[str]] + """Platform-specific interest IDs to target.""" + + languages: Optional[SequenceNotStr[str]] + """Language codes to target.""" + + placement_type: Optional[Literal["automatic", "manual"]] + """Placement strategy for ad delivery.""" + + +class Config(TypedDict, total=False): + """Unified ad group configuration (bidding, optimization, targeting).""" + + bid_amount: Optional[int] + """Bid cap amount in cents. Used when bid_strategy is bid_cap or cost_cap.""" + + bid_strategy: Optional[Literal["lowest_cost", "bid_cap", "cost_cap"]] + """Bid strategy: lowest_cost, bid_cap, or cost_cap.""" + + billing_event: Optional[Literal["impressions", "clicks", "optimized_cpm", "video_views"]] + """How you are billed (e.g., impressions, clicks).""" + + end_time: Optional[str] + """Scheduled end time (ISO8601). Required for lifetime budgets.""" + + frequency_cap: Optional[int] + """Maximum number of times to show ads to each person in the frequency interval.""" + + frequency_cap_interval_days: Optional[int] + """Number of days for the frequency cap interval.""" + + optimization_goal: Optional[ + Literal[ + "conversions", + "link_clicks", + "landing_page_views", + "reach", + "impressions", + "app_installs", + "video_views", + "lead_generation", + "value", + "page_likes", + "conversations", + "ad_recall_lift", + "two_second_continuous_video_views", + "post_engagement", + "event_responses", + "reminders_set", + "quality_lead", + ] + ] + """What the ad group optimizes for (e.g., conversions, link_clicks, reach).""" + + pacing: Optional[Literal["standard", "accelerated"]] + """Budget pacing: standard (even) or accelerated (fast).""" + + start_time: Optional[str] + """Scheduled start time (ISO8601).""" + + targeting: Optional[ConfigTargeting] + """Audience targeting settings (demographics, geo, interests, audiences, devices).""" + + +class PlatformConfigMetaAttributionSpec(TypedDict, total=False): + """Meta conversion attribution window.""" + + event_type: Required[str] + """Attribution event type (e.g., CLICK_THROUGH, VIEW_THROUGH).""" + + window_days: Required[int] + """Attribution window in days (1, 7, 28).""" + + +class PlatformConfigMetaExcludedGeoLocationsCity(TypedDict, total=False): + """A Meta geo target entry (region, city, or zip).""" + + key: Required[str] + """Meta geo target key/ID.""" + + country: Optional[str] + """Country code for this entry.""" + + name: Optional[str] + """Display name.""" + + radius: Optional[int] + """Radius in miles (cities only).""" + + +class PlatformConfigMetaExcludedGeoLocationsRegion(TypedDict, total=False): + """A Meta geo target entry (region, city, or zip).""" + + key: Required[str] + """Meta geo target key/ID.""" + + country: Optional[str] + """Country code for this entry.""" + + name: Optional[str] + """Display name.""" + + radius: Optional[int] + """Radius in miles (cities only).""" + + +class PlatformConfigMetaExcludedGeoLocationsZip(TypedDict, total=False): + """A Meta geo target entry (region, city, or zip).""" + + key: Required[str] + """Meta geo target key/ID.""" + + country: Optional[str] + """Country code for this entry.""" + + name: Optional[str] + """Display name.""" + + radius: Optional[int] + """Radius in miles (cities only).""" + + +class PlatformConfigMetaExcludedGeoLocations(TypedDict, total=False): + """Geo locations to exclude.""" + + cities: Optional[Iterable[PlatformConfigMetaExcludedGeoLocationsCity]] + """City targets.""" + + countries: Optional[SequenceNotStr[str]] + """ISO 3166-1 alpha-2 country codes.""" + + location_types: Optional[SequenceNotStr[str]] + """Location types (home, recent, travel_in).""" + + regions: Optional[Iterable[PlatformConfigMetaExcludedGeoLocationsRegion]] + """Region/state targets.""" + + zips: Optional[Iterable[PlatformConfigMetaExcludedGeoLocationsZip]] + """Zip/postal code targets.""" + + +class PlatformConfigMetaGeoCity(TypedDict, total=False): + """A Meta geo target entry (region, city, or zip).""" + + key: Required[str] + """Meta geo target key/ID.""" + + country: Optional[str] + """Country code for this entry.""" + + name: Optional[str] + """Display name.""" + + radius: Optional[int] + """Radius in miles (cities only).""" + + +class PlatformConfigMetaGeoLocationsCity(TypedDict, total=False): + """A Meta geo target entry (region, city, or zip).""" + + key: Required[str] + """Meta geo target key/ID.""" + + country: Optional[str] + """Country code for this entry.""" + + name: Optional[str] + """Display name.""" + + radius: Optional[int] + """Radius in miles (cities only).""" + + +class PlatformConfigMetaGeoLocationsRegion(TypedDict, total=False): + """A Meta geo target entry (region, city, or zip).""" + + key: Required[str] + """Meta geo target key/ID.""" + + country: Optional[str] + """Country code for this entry.""" + + name: Optional[str] + """Display name.""" + + radius: Optional[int] + """Radius in miles (cities only).""" + + +class PlatformConfigMetaGeoLocationsZip(TypedDict, total=False): + """A Meta geo target entry (region, city, or zip).""" + + key: Required[str] + """Meta geo target key/ID.""" + + country: Optional[str] + """Country code for this entry.""" + + name: Optional[str] + """Display name.""" + + radius: Optional[int] + """Radius in miles (cities only).""" + + +class PlatformConfigMetaGeoLocations(TypedDict, total=False): + """Geo targeting (countries, regions, cities, zips).""" + + cities: Optional[Iterable[PlatformConfigMetaGeoLocationsCity]] + """City targets.""" + + countries: Optional[SequenceNotStr[str]] + """ISO 3166-1 alpha-2 country codes.""" + + location_types: Optional[SequenceNotStr[str]] + """Location types (home, recent, travel_in).""" + + regions: Optional[Iterable[PlatformConfigMetaGeoLocationsRegion]] + """Region/state targets.""" + + zips: Optional[Iterable[PlatformConfigMetaGeoLocationsZip]] + """Zip/postal code targets.""" + + +class PlatformConfigMetaGeoRegion(TypedDict, total=False): + """A Meta geo target entry (region, city, or zip).""" + + key: Required[str] + """Meta geo target key/ID.""" + + country: Optional[str] + """Country code for this entry.""" + + name: Optional[str] + """Display name.""" + + radius: Optional[int] + """Radius in miles (cities only).""" + + +class PlatformConfigMetaLeadFormConfigQuestionDependentConditionalQuestionOptionLogic(TypedDict, total=False): + """Conditional logic routing for this answer option.""" + + type: Required[str] + """Logic type: go_to_question, submit_form, or close_form.""" + + target_end_page_index: Optional[int] + """Index of the end page to route to (for submit_form type).""" + + target_question_index: Optional[int] + """Index of the question to route to (for go_to_question type).""" + + +class PlatformConfigMetaLeadFormConfigQuestionDependentConditionalQuestionOption(TypedDict, total=False): + """An answer option for a multiple choice lead form question.""" + + key: Required[str] + """Unique key for this option.""" + + value: Required[str] + """Display text for this option.""" + + logic: Optional[PlatformConfigMetaLeadFormConfigQuestionDependentConditionalQuestionOptionLogic] + """Conditional logic routing for this answer option.""" + + +class PlatformConfigMetaLeadFormConfigQuestionDependentConditionalQuestion(TypedDict, total=False): + """A dependent conditional question (non-recursive to avoid schema recursion).""" + + type: Required[str] + """Question type (EMAIL, FULL_NAME, PHONE, CUSTOM, DATE_TIME, etc.).""" + + inline_context: Optional[str] + """Helper text shown below the question.""" + + key: Optional[str] + """Unique key for this question.""" + + label: Optional[str] + """Custom label for CUSTOM questions.""" + + options: Optional[Iterable[PlatformConfigMetaLeadFormConfigQuestionDependentConditionalQuestionOption]] + """Answer options for multiple choice questions.""" + + +class PlatformConfigMetaLeadFormConfigQuestionOptionLogic(TypedDict, total=False): + """Conditional logic routing for this answer option.""" + + type: Required[str] + """Logic type: go_to_question, submit_form, or close_form.""" + + target_end_page_index: Optional[int] + """Index of the end page to route to (for submit_form type).""" + + target_question_index: Optional[int] + """Index of the question to route to (for go_to_question type).""" + + +class PlatformConfigMetaLeadFormConfigQuestionOption(TypedDict, total=False): + """An answer option for a multiple choice lead form question.""" + + key: Required[str] + """Unique key for this option.""" + + value: Required[str] + """Display text for this option.""" + + logic: Optional[PlatformConfigMetaLeadFormConfigQuestionOptionLogic] + """Conditional logic routing for this answer option.""" + + +class PlatformConfigMetaLeadFormConfigQuestion(TypedDict, total=False): + """A question on a Meta lead gen form.""" + + type: Required[str] + """Question type (EMAIL, FULL_NAME, PHONE, CUSTOM, DATE_TIME, etc.).""" + + conditional_questions_group_id: Optional[str] + """Group ID for conditional question routing.""" + + dependent_conditional_questions: Optional[ + Iterable[PlatformConfigMetaLeadFormConfigQuestionDependentConditionalQuestion] + ] + """Questions shown conditionally based on this question's answer.""" + + inline_context: Optional[str] + """Helper text shown below the question.""" + + key: Optional[str] + """Unique key for this question.""" + + label: Optional[str] + """Custom label for CUSTOM questions.""" + + options: Optional[Iterable[PlatformConfigMetaLeadFormConfigQuestionOption]] + """Answer options for multiple choice CUSTOM questions.""" + + question_format: Optional[str] + """UI hint: short_answer, multiple_choice, or appointment.""" + + +class PlatformConfigMetaLeadFormConfigCustomDisclaimerCheckbox(TypedDict, total=False): + """A consent checkbox for the custom disclaimer section.""" + + key: Required[str] + """Unique key for this checkbox.""" + + text: Required[str] + """Label text for the checkbox.""" + + is_checked_by_default: Optional[bool] + """Whether the checkbox is checked by default.""" + + is_required: Optional[bool] + """Whether the checkbox must be checked to submit.""" + + +class PlatformConfigMetaLeadFormConfigThankYouPage(TypedDict, total=False): + """A thank-you / ending page for a Meta lead gen form.""" + + body: Optional[str] + """Body text for this ending page.""" + + business_phone: Optional[str] + """Business phone number for call CTA.""" + + button_text: Optional[str] + """Custom button text.""" + + button_type: Optional[str] + """CTA button type: VIEW_WEBSITE, CALL_BUSINESS, DOWNLOAD.""" + + conditional_question_group_id: Optional[str] + """Question group ID for conditional routing to this page.""" + + enable_messenger: Optional[bool] + """Enable Messenger follow-up.""" + + gated_file_url: Optional[str] + """Uploaded file URL for gated content download.""" + + link: Optional[str] + """URL the button links to.""" + + name: Optional[str] + """Internal name for this ending page.""" + + title: Optional[str] + """Headline for this ending page.""" + + +class PlatformConfigMetaLeadFormConfig(TypedDict, total=False): + """Configuration for a Meta lead gen instant form.""" + + name: Required[str] + """Name of the lead form.""" + + privacy_policy_url: Required[str] + """URL to your privacy policy. Required by Meta.""" + + questions: Required[Iterable[PlatformConfigMetaLeadFormConfigQuestion]] + """Questions to ask on the form.""" + + background_image_source: Optional[str] + """Background image source: from_ad or custom.""" + + background_image_url: Optional[str] + """URL of custom background image.""" + + conditional_logic_enabled: Optional[bool] + """Whether conditional logic is enabled for questions.""" + + context_card_button_text: Optional[str] + """CTA button text on the greeting card.""" + + context_card_content: Optional[SequenceNotStr[str]] + """Optional greeting card bullet points.""" + + context_card_style: Optional[str] + """Greeting layout: PARAGRAPH_STYLE or LIST_STYLE.""" + + context_card_title: Optional[str] + """Optional greeting card title.""" + + custom_disclaimer_body: Optional[str] + """Custom disclaimer body text.""" + + custom_disclaimer_checkboxes: Optional[Iterable[PlatformConfigMetaLeadFormConfigCustomDisclaimerCheckbox]] + """Consent checkboxes for the custom disclaimer.""" + + custom_disclaimer_title: Optional[str] + """Custom disclaimer section title.""" + + form_type: Optional[str] + """Form type: more_volume, higher_intent, or rich_creative.""" + + messenger_enabled: Optional[bool] + """Enable Messenger follow-up after form submission.""" + + phone_verification_enabled: Optional[bool] + """Require phone number verification via OTP (higher_intent only).""" + + privacy_policy_link_text: Optional[str] + """Custom link text for privacy policy (max 70 chars).""" + + question_page_custom_headline: Optional[str] + """Custom headline for the questions page.""" + + rich_creative_headline: Optional[str] + """Headline for rich creative form intro.""" + + rich_creative_overview: Optional[str] + """Overview description for rich creative form intro.""" + + rich_creative_url: Optional[str] + """Uploaded image URL for rich creative form type.""" + + thank_you_pages: Optional[Iterable[PlatformConfigMetaLeadFormConfigThankYouPage]] + """Thank you / ending pages (supports multiple for conditional routing).""" + + +class PlatformConfigMetaPromotedObject(TypedDict, total=False): + """The object this ad set promotes (pixel, page, etc.).""" + + custom_conversion_id: Optional[str] + """Custom conversion rule ID (numeric, from Meta Events Manager).""" + + custom_event_str: Optional[str] + """Pixel event name, used when custom_event_type is OTHER.""" + + custom_event_type: Optional[str] + """Custom event type (e.g., PURCHASE, COMPLETE_REGISTRATION, OTHER).""" + + page_id: Optional[str] + """Facebook Page ID.""" + + pixel_id: Optional[str] + """Meta Pixel ID for conversion tracking.""" + + whatsapp_phone_number: Optional[str] + """WhatsApp phone number for messaging campaigns.""" + + +class PlatformConfigMetaTargetingAutomation(TypedDict, total=False): + """Advantage+ audience expansion settings.""" + + advantage_audience: Optional[int] + """0 = off (use exact targeting), 1 = on (let Meta expand audience).""" + + +class PlatformConfigMeta(TypedDict, total=False): + """Meta (Facebook/Instagram) ad set configuration.""" + + android_devices: Optional[SequenceNotStr[str]] + + attribution_setting: Optional[str] + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + attribution_spec: Optional[Iterable[PlatformConfigMetaAttributionSpec]] + """Conversion attribution windows.""" + + audience_network_positions: Optional[SequenceNotStr[str]] + + audience_type: Optional[str] + """Audience type for retargeting.""" + + bid_amount: Optional[int] + """Bid amount in cents.""" + + bid_strategy: Optional[ + Literal["LOWEST_COST_WITHOUT_CAP", "LOWEST_COST_WITH_BID_CAP", "COST_CAP", "LOWEST_COST_WITH_MIN_ROAS"] + ] + """Meta bid strategy.""" + + billing_event: Optional[ + Literal[ + "APP_INSTALLS", + "CLICKS", + "IMPRESSIONS", + "LINK_CLICKS", + "NONE", + "OFFER_CLAIMS", + "PAGE_LIKES", + "POST_ENGAGEMENT", + "THRUPLAY", + "PURCHASE", + "LISTING_INTERACTION", + ] + ] + """How you are billed on Meta.""" + + brand_safety_content_filter_levels: Optional[SequenceNotStr[str]] + + budget_remaining: Optional[str] + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + cost_per_result_goal: Optional[float] + """ + Represents signed double-precision fractional values as specified by + [IEEE 754](https://en.wikipedia.org/wiki/IEEE_floating_point). + """ + + created_time: Optional[str] + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + daily_budget: Optional[int] + """Daily budget in cents.""" + + daily_min_spend_target: Optional[str] + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + daily_spend_cap: Optional[str] + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + destination_type: Optional[ + Literal[ + "UNDEFINED", + "WEBSITE", + "APP", + "FACEBOOK", + "MESSENGER", + "WHATSAPP", + "INSTAGRAM_DIRECT", + "INSTAGRAM_PROFILE", + "PHONE_CALL", + "SHOP_AUTOMATIC", + "APPLINKS_AUTOMATIC", + "ON_AD", + "ON_POST", + "ON_VIDEO", + "ON_PAGE", + "ON_EVENT", + "MESSAGING_MESSENGER_WHATSAPP", + "MESSAGING_INSTAGRAM_DIRECT_MESSENGER", + "MESSAGING_INSTAGRAM_DIRECT_WHATSAPP", + "MESSAGING_INSTAGRAM_DIRECT_MESSENGER_WHATSAPP", + "INSTAGRAM_PROFILE_AND_FACEBOOK_PAGE", + "FACEBOOK_PAGE", + "INSTAGRAM_LIVE", + "FACEBOOK_LIVE", + "IMAGINE", + "LEAD_FROM_IG_DIRECT", + "LEAD_FROM_MESSENGER", + "WEBSITE_AND_LEAD_FORM", + "WEBSITE_AND_PHONE_CALL", + "BROADCAST_CHANNEL", + ] + ] + """Where ads in this ad set direct people.""" + + dsa_beneficiary: Optional[str] + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + dsa_payor: Optional[str] + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + end_time: Optional[str] + """End time (ISO8601). Required for lifetime budgets.""" + + excluded_geo_locations: Optional[PlatformConfigMetaExcludedGeoLocations] + """Geo locations to exclude.""" + + facebook_positions: Optional[SequenceNotStr[str]] + """Facebook ad placements (feed, reels, stories, etc.).""" + + frequency_control_count: Optional[int] + """Represents non-fractional signed whole numeric values. + + Int can represent values between -(2^31) and 2^31 - 1. + """ + + frequency_control_days: Optional[int] + """Represents non-fractional signed whole numeric values. + + Int can represent values between -(2^31) and 2^31 - 1. + """ + + frequency_control_type: Optional[str] + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + geo_cities: Optional[Iterable[PlatformConfigMetaGeoCity]] + + geo_locations: Optional[PlatformConfigMetaGeoLocations] + """Geo targeting (countries, regions, cities, zips).""" + + geo_regions: Optional[Iterable[PlatformConfigMetaGeoRegion]] + + geo_zips: Optional[SequenceNotStr[str]] + + instagram_actor_id: Optional[str] + """Instagram account ID for this ad set.""" + + instagram_positions: Optional[SequenceNotStr[str]] + """Instagram ad placements (stream, story, reels, etc.).""" + + ios_devices: Optional[SequenceNotStr[str]] + + is_dynamic_creative: Optional[bool] + """Represents `true` or `false` values.""" + + lead_conversion_location: Optional[Literal["website", "instant_forms", "messenger", "instagram", "calls", "app"]] + + lead_form_config: Optional[PlatformConfigMetaLeadFormConfig] + """Configuration for a Meta lead gen instant form.""" + + lead_gen_form_id: Optional[str] + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + lifetime_budget: Optional[int] + """Lifetime budget in cents.""" + + lifetime_min_spend_target: Optional[str] + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + lifetime_spend_cap: Optional[str] + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + location_types: Optional[SequenceNotStr[str]] + + messenger_positions: Optional[SequenceNotStr[str]] + + optimization_goal: Optional[ + Literal[ + "NONE", + "APP_INSTALLS", + "AD_RECALL_LIFT", + "ENGAGED_USERS", + "EVENT_RESPONSES", + "IMPRESSIONS", + "LEAD_GENERATION", + "QUALITY_LEAD", + "LINK_CLICKS", + "OFFSITE_CONVERSIONS", + "PAGE_LIKES", + "POST_ENGAGEMENT", + "QUALITY_CALL", + "REACH", + "LANDING_PAGE_VIEWS", + "VISIT_INSTAGRAM_PROFILE", + "VALUE", + "THRUPLAY", + "DERIVED_EVENTS", + "APP_INSTALLS_AND_OFFSITE_CONVERSIONS", + "CONVERSATIONS", + "IN_APP_VALUE", + "MESSAGING_PURCHASE_CONVERSION", + "SUBSCRIBERS", + "REMINDERS_SET", + "MEANINGFUL_CALL_ATTEMPT", + "PROFILE_VISIT", + "PROFILE_AND_PAGE_ENGAGEMENT", + "TWO_SECOND_CONTINUOUS_VIDEO_VIEWS", + "ENGAGED_REACH", + "ENGAGED_PAGE_VIEWS", + "MESSAGING_DEEP_CONVERSATION_AND_FOLLOW", + "ADVERTISER_SILOED_VALUE", + "AUTOMATIC_OBJECTIVE", + "MESSAGING_APPOINTMENT_CONVERSION", + ] + ] + """What this ad set optimizes for on Meta.""" + + page_id: Optional[str] + """Facebook Page ID for this ad set.""" + + pixel_id: Optional[str] + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + promoted_object: Optional[PlatformConfigMetaPromotedObject] + """The object this ad set promotes (pixel, page, etc.).""" + + publisher_platforms: Optional[SequenceNotStr[str]] + """Platforms to publish on (facebook, instagram, messenger, audience_network).""" + + source_adset_id: Optional[str] + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + start_time: Optional[str] + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + status: Optional[Literal["ACTIVE", "PAUSED"]] + + targeting_automation: Optional[PlatformConfigMetaTargetingAutomation] + """Advantage+ audience expansion settings.""" + + threads_positions: Optional[SequenceNotStr[str]] + + updated_time: Optional[str] + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + user_device: Optional[SequenceNotStr[str]] + + user_os: Optional[SequenceNotStr[str]] + + whatsapp_phone_number: Optional[str] + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + whatsapp_positions: Optional[SequenceNotStr[str]] + + +class PlatformConfigTiktokAction(TypedDict, total=False): + """A single TikTok behavioral targeting entry. + + One category of past user behavior (what they did, over what window, on which kind of content). See docs/tiktok_api/ad_group.md § actions. + """ + + action_category_ids: Optional[SequenceNotStr[str]] + """Behavioral category IDs. Use /tool/action_category/ to list them.""" + + action_period: Optional[int] + """Lookback window in days. TikTok accepts 7, 15, 30, 60, 90, or 180.""" + + action_scene: Optional[Literal["VIDEO_RELATED", "CREATOR_RELATED", "HASHTAG_RELATED", "LIVE_RELATED"]] + """The category of TikTok content a behavioral targeting rule applies to. + + See docs/tiktok_api/ad_group.md § actions. + """ + + video_user_actions: Optional[ + List[Literal["WATCHED_TO_END", "LIKED", "COMMENTED", "SHARED", "FOLLOWED", "PROFILE_VISITED"]] + ] + """ + Specific video interactions (WATCHED_TO_END, LIKED, COMMENTED, SHARED, FOLLOWED, + PROFILE_VISITED). + """ + + +class PlatformConfigTiktokInstantFormConfigQuestion(TypedDict, total=False): + """A question for a TikTok instant form.""" + + field_type: Required[str] + """Question type (EMAIL, PHONE_NUMBER, NAME, CUSTOM).""" + + label: Optional[str] + """Custom label for the question.""" + + +class PlatformConfigTiktokInstantFormConfig(TypedDict, total=False): + """Instant form configuration for lead generation campaigns.""" + + privacy_policy_url: Required[str] + """URL to your privacy policy.""" + + questions: Required[Iterable[PlatformConfigTiktokInstantFormConfigQuestion]] + """Form questions (at least one required).""" + + button_text: Optional[str] + """Submit button text.""" + + greeting: Optional[str] + """Greeting text shown at the top of the form.""" + + name: Optional[str] + """Form name. Auto-generated if omitted.""" + + +class PlatformConfigTiktok(TypedDict, total=False): + """TikTok ad group configuration.""" + + actions: Optional[Iterable[PlatformConfigTiktokAction]] + + age_groups: Optional[List[Literal["AGE_13_17", "AGE_18_24", "AGE_25_34", "AGE_35_44", "AGE_45_54", "AGE_55_100"]]] + + app_id: Optional[str] + """App ID for app promotion campaigns.""" + + attribution_event_count: Optional[Literal["UNSET", "EVERY", "ONCE"]] + + audience_ids: Optional[SequenceNotStr[str]] + + audience_rule: Optional[Dict[str, object]] + """Represents untyped JSON""" + + audience_type: Optional[Literal["NORMAL", "SMART_INTERESTS_BEHAVIORS"]] + + bid_price: Optional[float] + """Bid price (cost per result for Cost Cap).""" + + bid_type: Optional[Literal["BID_TYPE_NO_BID", "BID_TYPE_CUSTOM"]] + """Bidding strategy (BID_TYPE_NO_BID, BID_TYPE_CUSTOM).""" + + billing_event: Optional[Literal["CPC", "CPM", "OCPM", "CPV"]] + """How you are billed on TikTok (CPC, CPM, OCPM, CPV).""" + + brand_safety_type: Optional[ + Literal["NO_BRAND_SAFETY", "STANDARD_INVENTORY", "LIMITED_INVENTORY", "FULL_INVENTORY", "EXPANDED_INVENTORY"] + ] + + budget_mode: Optional[Literal["BUDGET_MODE_DAY", "BUDGET_MODE_TOTAL", "BUDGET_MODE_DYNAMIC_DAILY_BUDGET"]] + """ + Budget mode (BUDGET_MODE_DAY, BUDGET_MODE_TOTAL, + BUDGET_MODE_DYNAMIC_DAILY_BUDGET). + """ + + carrier_ids: Optional[SequenceNotStr[str]] + + category_exclusion_ids: Optional[SequenceNotStr[str]] + + click_attribution_window: Optional[Literal["OFF", "ONE_DAY", "SEVEN_DAYS", "FOURTEEN_DAYS", "TWENTY_EIGHT_DAYS"]] + + comment_disabled: Optional[bool] + """Represents `true` or `false` values.""" + + contextual_tag_ids: Optional[SequenceNotStr[str]] + + conversion_bid_price: Optional[float] + """Target cost per conversion for oCPM.""" + + creative_material_mode: Optional[str] + """Creative delivery strategy.""" + + dayparting: Optional[str] + """Ad delivery schedule (48x7 character string).""" + + deep_funnel_event_source: Optional[str] + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + deep_funnel_event_source_id: Optional[str] + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + deep_funnel_optimization_status: Optional[Literal["ON", "OFF"]] + + device_model_ids: Optional[SequenceNotStr[str]] + + device_price_ranges: Optional[SequenceNotStr[str]] + + engaged_view_attribution_window: Optional[Literal["OFF", "ONE_DAY", "SEVEN_DAYS"]] + + excluded_audience_ids: Optional[SequenceNotStr[str]] + + excluded_location_ids: Optional[SequenceNotStr[str]] + """TikTok location/region IDs to exclude.""" + + frequency: Optional[int] + """Represents non-fractional signed whole numeric values. + + Int can represent values between -(2^31) and 2^31 - 1. + """ + + frequency_schedule: Optional[int] + """Represents non-fractional signed whole numeric values. + + Int can represent values between -(2^31) and 2^31 - 1. + """ + + gender: Optional[Literal["GENDER_UNLIMITED", "GENDER_MALE", "GENDER_FEMALE"]] + + identity_authorized_bc_id: Optional[str] + """Business Center ID for BC_AUTH_TT identity.""" + + identity_id: Optional[str] + """TikTok identity ID for the ad group.""" + + identity_type: Optional[str] + """Identity type (AUTH_CODE, TT_USER, BC_AUTH_TT).""" + + instant_form_config: Optional[PlatformConfigTiktokInstantFormConfig] + """Instant form configuration for lead generation campaigns.""" + + instant_form_id: Optional[str] + """ + TikTok instant form ID (set automatically when instant_form_config is provided). + """ + + interest_category_ids: Optional[SequenceNotStr[str]] + + interest_keyword_ids: Optional[SequenceNotStr[str]] + + inventory_filter_enabled: Optional[bool] + """Represents `true` or `false` values.""" + + ios14_targeting: Optional[Literal["UNSET", "IOS14_MINUS", "IOS14_PLUS", "ALL"]] + + isp_ids: Optional[SequenceNotStr[str]] + + languages: Optional[SequenceNotStr[str]] + + location_ids: Optional[SequenceNotStr[str]] + """TikTok location/region IDs for geo targeting.""" + + min_android_version: Optional[str] + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + min_ios_version: Optional[str] + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + network_types: Optional[SequenceNotStr[str]] + + operating_systems: Optional[List[Literal["ANDROID", "IOS"]]] + + operation_status: Optional[Literal["ENABLE", "DISABLE"]] + """Initial status (ENABLE, DISABLE).""" + + optimization_event: Optional[str] + """Conversion event (e.g., COMPLETE_PAYMENT).""" + + optimization_goal: Optional[ + Literal[ + "CLICK", + "CONVERT", + "INSTALL", + "IN_APP_EVENT", + "REACH", + "SHOW", + "VIDEO_VIEW", + "ENGAGED_VIEW", + "ENGAGED_VIEW_FIFTEEN", + "LEAD_GENERATION", + "PREFERRED_LEAD", + "CONVERSATION", + "FOLLOWERS", + "PROFILE_VIEWS", + "PAGE_VISIT", + "VALUE", + "AUTOMATIC_VALUE_OPTIMIZATION", + "TRAFFIC_LANDING_PAGE_VIEW", + "DESTINATION_VISIT", + "MT_LIVE_ROOM", + "PRODUCT_CLICK_IN_LIVE", + ] + ] + """What this ad group optimizes for on TikTok.""" + + pacing: Optional[Literal["PACING_MODE_SMOOTH", "PACING_MODE_FAST"]] + """Budget pacing (PACING_MODE_SMOOTH, PACING_MODE_FAST).""" + + pangle_audience_package_exclude_ids: Optional[SequenceNotStr[str]] + + pangle_audience_package_include_ids: Optional[SequenceNotStr[str]] + + pangle_block_app_ids: Optional[SequenceNotStr[str]] + + pixel_id: Optional[str] + """TikTok Pixel ID for conversion tracking.""" + + placement_type: Optional[Literal["PLACEMENT_TYPE_AUTOMATIC", "PLACEMENT_TYPE_NORMAL"]] + """Placement strategy (PLACEMENT_TYPE_AUTOMATIC, PLACEMENT_TYPE_NORMAL).""" + + placements: Optional[SequenceNotStr[str]] + """Placements (PLACEMENT_TIKTOK, PLACEMENT_PANGLE, etc.).""" + + product_set_id: Optional[str] + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + product_source: Optional[Literal["CATALOG", "STORE", "SHOWCASE"]] + + promotion_type: Optional[str] + """Promotion type (optimization location).""" + + schedule_end_time: Optional[str] + """Schedule end time (UTC, YYYY-MM-DD HH:MM:SS).""" + + schedule_start_time: Optional[str] + """Schedule start time (UTC, YYYY-MM-DD HH:MM:SS).""" + + schedule_type: Optional[Literal["SCHEDULE_START_END", "SCHEDULE_FROM_NOW"]] + """Schedule type (SCHEDULE_START_END, SCHEDULE_FROM_NOW).""" + + secondary_optimization_event: Optional[str] + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + shopping_ads_retargeting_actions_days: Optional[int] + """Represents non-fractional signed whole numeric values. + + Int can represent values between -(2^31) and 2^31 - 1. + """ + + shopping_ads_retargeting_type: Optional[Literal["OFF", "LAB1", "LAB2", "LAB3", "LAB4", "LAB5"]] + + spending_power: Optional[Literal["ALL", "HIGH"]] + + tiktok_subplacements: Optional[SequenceNotStr[str]] + """TikTok subplacements (IN_FEED, SEARCH_FEED, etc.).""" + + vertical_sensitivity_id: Optional[str] + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + video_download_disabled: Optional[bool] + """Represents `true` or `false` values.""" + + video_user_actions: Optional[SequenceNotStr[str]] + + view_attribution_window: Optional[Literal["OFF", "ONE_DAY", "SEVEN_DAYS"]] + + +class PlatformConfig(TypedDict, total=False): + """Platform-specific ad group configuration.""" + + meta: Optional[PlatformConfigMeta] + """Meta (Facebook/Instagram) ad set configuration.""" + + tiktok: Optional[PlatformConfigTiktok] + """TikTok ad group configuration.""" diff --git a/src/whop_sdk/types/ad_group_update_response.py b/src/whop_sdk/types/ad_group_update_response.py new file mode 100644 index 00000000..78caea6c --- /dev/null +++ b/src/whop_sdk/types/ad_group_update_response.py @@ -0,0 +1,438 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Dict, List, Union, Optional +from datetime import datetime +from typing_extensions import Literal, Annotated, TypeAlias + +from .._utils import PropertyInfo +from .._models import BaseModel + +__all__ = [ + "AdGroupUpdateResponse", + "AdCampaign", + "Config", + "ConfigTargeting", + "PlatformConfig", + "PlatformConfigMetaAdGroupPlatformConfigType", + "PlatformConfigTiktokAdGroupPlatformConfigType", +] + + +class AdCampaign(BaseModel): + """The parent ad campaign""" + + id: str + """The unique identifier for the ad campaign.""" + + platform: Optional[Literal["meta", "tiktok"]] = None + """The platforms where an ad campaign can run.""" + + status: Literal[ + "active", "paused", "inactive", "stale", "pending_refund", "payment_failed", "draft", "in_review", "flagged" + ] + """Current status of the campaign (active, paused, or inactive)""" + + title: str + """The title of the ad campaign""" + + +class ConfigTargeting(BaseModel): + """Audience targeting settings (demographics, geo, interests, audiences, devices).""" + + age_max: Optional[int] = None + """Maximum age for demographic targeting.""" + + age_min: Optional[int] = None + """Minimum age for demographic targeting.""" + + countries: Optional[List[str]] = None + """ISO 3166-1 alpha-2 country codes targeted.""" + + device_platforms: Optional[List[Literal["mobile", "desktop"]]] = None + """Device platforms targeted.""" + + exclude_audience_ids: Optional[List[str]] = None + """Platform audience IDs excluded.""" + + genders: Optional[List[Literal["male", "female", "all"]]] = None + """Genders targeted.""" + + include_audience_ids: Optional[List[str]] = None + """Platform audience IDs included.""" + + interest_ids: Optional[List[str]] = None + """Platform-specific interest IDs targeted.""" + + languages: Optional[List[str]] = None + """Language codes targeted.""" + + placement_type: Optional[Literal["automatic", "manual"]] = None + """Placement strategy for ad delivery.""" + + +class Config(BaseModel): + """Unified ad group configuration (platform-agnostic)""" + + bid_amount: Optional[int] = None + """Bid cap amount in cents. Used when bid_strategy is bid_cap or cost_cap.""" + + bid_strategy: Optional[Literal["lowest_cost", "bid_cap", "cost_cap"]] = None + """Bid strategy: lowest_cost, bid_cap, or cost_cap.""" + + billing_event: Optional[Literal["impressions", "clicks", "optimized_cpm", "video_views"]] = None + """How you are billed (e.g., impressions, clicks).""" + + end_time: Optional[str] = None + """Scheduled end time (ISO8601). Required for lifetime budgets.""" + + frequency_cap: Optional[int] = None + """Maximum number of times to show ads to each person in the frequency interval.""" + + frequency_cap_interval_days: Optional[int] = None + """Number of days for the frequency cap interval.""" + + optimization_goal: Optional[ + Literal[ + "conversions", + "link_clicks", + "landing_page_views", + "reach", + "impressions", + "app_installs", + "video_views", + "lead_generation", + "value", + "page_likes", + "conversations", + "ad_recall_lift", + "two_second_continuous_video_views", + "post_engagement", + "event_responses", + "reminders_set", + "quality_lead", + ] + ] = None + """What the ad group optimizes for (e.g., conversions, link_clicks, reach).""" + + pacing: Optional[Literal["standard", "accelerated"]] = None + """Budget pacing: standard (even) or accelerated (fast).""" + + start_time: Optional[str] = None + """Scheduled start time (ISO8601).""" + + targeting: Optional[ConfigTargeting] = None + """Audience targeting settings (demographics, geo, interests, audiences, devices).""" + + +class PlatformConfigMetaAdGroupPlatformConfigType(BaseModel): + """Meta (Facebook/Instagram) ad set configuration.""" + + attribution_spec: Optional[List[Dict[str, object]]] = None + + bid_amount: Optional[int] = None + """Represents non-fractional signed whole numeric values. + + Int can represent values between -(2^31) and 2^31 - 1. + """ + + bid_strategy: Optional[ + Literal["LOWEST_COST_WITHOUT_CAP", "LOWEST_COST_WITH_BID_CAP", "COST_CAP", "LOWEST_COST_WITH_MIN_ROAS"] + ] = None + + billing_event: Optional[ + Literal[ + "APP_INSTALLS", + "CLICKS", + "IMPRESSIONS", + "LINK_CLICKS", + "NONE", + "OFFER_CLAIMS", + "PAGE_LIKES", + "POST_ENGAGEMENT", + "THRUPLAY", + "PURCHASE", + "LISTING_INTERACTION", + ] + ] = None + + daily_budget: Optional[int] = None + """Represents non-fractional signed whole numeric values. + + Int can represent values between -(2^31) and 2^31 - 1. + """ + + destination_type: Optional[ + Literal[ + "UNDEFINED", + "WEBSITE", + "APP", + "FACEBOOK", + "MESSENGER", + "WHATSAPP", + "INSTAGRAM_DIRECT", + "INSTAGRAM_PROFILE", + "PHONE_CALL", + "SHOP_AUTOMATIC", + "APPLINKS_AUTOMATIC", + "ON_AD", + "ON_POST", + "ON_VIDEO", + "ON_PAGE", + "ON_EVENT", + "MESSAGING_MESSENGER_WHATSAPP", + "MESSAGING_INSTAGRAM_DIRECT_MESSENGER", + "MESSAGING_INSTAGRAM_DIRECT_WHATSAPP", + "MESSAGING_INSTAGRAM_DIRECT_MESSENGER_WHATSAPP", + "INSTAGRAM_PROFILE_AND_FACEBOOK_PAGE", + "FACEBOOK_PAGE", + "INSTAGRAM_LIVE", + "FACEBOOK_LIVE", + "IMAGINE", + "LEAD_FROM_IG_DIRECT", + "LEAD_FROM_MESSENGER", + "WEBSITE_AND_LEAD_FORM", + "WEBSITE_AND_PHONE_CALL", + "BROADCAST_CHANNEL", + ] + ] = None + + end_time: Optional[str] = None + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + excluded_geo_locations: Optional[Dict[str, object]] = None + """Represents untyped JSON""" + + facebook_positions: Optional[List[str]] = None + + geo_locations: Optional[Dict[str, object]] = None + """Represents untyped JSON""" + + instagram_actor_id: Optional[str] = None + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + instagram_positions: Optional[List[str]] = None + + lifetime_budget: Optional[int] = None + """Represents non-fractional signed whole numeric values. + + Int can represent values between -(2^31) and 2^31 - 1. + """ + + optimization_goal: Optional[ + Literal[ + "NONE", + "APP_INSTALLS", + "AD_RECALL_LIFT", + "ENGAGED_USERS", + "EVENT_RESPONSES", + "IMPRESSIONS", + "LEAD_GENERATION", + "QUALITY_LEAD", + "LINK_CLICKS", + "OFFSITE_CONVERSIONS", + "PAGE_LIKES", + "POST_ENGAGEMENT", + "QUALITY_CALL", + "REACH", + "LANDING_PAGE_VIEWS", + "VISIT_INSTAGRAM_PROFILE", + "VALUE", + "THRUPLAY", + "DERIVED_EVENTS", + "APP_INSTALLS_AND_OFFSITE_CONVERSIONS", + "CONVERSATIONS", + "IN_APP_VALUE", + "MESSAGING_PURCHASE_CONVERSION", + "SUBSCRIBERS", + "REMINDERS_SET", + "MEANINGFUL_CALL_ATTEMPT", + "PROFILE_VISIT", + "PROFILE_AND_PAGE_ENGAGEMENT", + "TWO_SECOND_CONTINUOUS_VIDEO_VIEWS", + "ENGAGED_REACH", + "ENGAGED_PAGE_VIEWS", + "MESSAGING_DEEP_CONVERSATION_AND_FOLLOW", + "ADVERTISER_SILOED_VALUE", + "AUTOMATIC_OBJECTIVE", + "MESSAGING_APPOINTMENT_CONVERSION", + ] + ] = None + + page_id: Optional[str] = None + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + platform: Literal["meta", "tiktok"] + """The ad platform (meta, tiktok).""" + + promoted_object: Optional[Dict[str, object]] = None + """Represents untyped JSON""" + + publisher_platforms: Optional[List[str]] = None + + status: Optional[Literal["ACTIVE", "PAUSED"]] = None + + targeting_automation: Optional[Dict[str, object]] = None + """Represents untyped JSON""" + + typename: Literal["MetaAdGroupPlatformConfigType"] + """The typename of this object""" + + +class PlatformConfigTiktokAdGroupPlatformConfigType(BaseModel): + """TikTok ad group configuration.""" + + bid_price: Optional[float] = None + """ + Represents signed double-precision fractional values as specified by + [IEEE 754](https://en.wikipedia.org/wiki/IEEE_floating_point). + """ + + bid_type: Optional[Literal["BID_TYPE_NO_BID", "BID_TYPE_CUSTOM"]] = None + + billing_event: Optional[Literal["CPC", "CPM", "OCPM", "CPV"]] = None + + budget_mode: Optional[Literal["BUDGET_MODE_DAY", "BUDGET_MODE_TOTAL", "BUDGET_MODE_DYNAMIC_DAILY_BUDGET"]] = None + + conversion_bid_price: Optional[float] = None + """ + Represents signed double-precision fractional values as specified by + [IEEE 754](https://en.wikipedia.org/wiki/IEEE_floating_point). + """ + + identity_id: Optional[str] = None + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + identity_type: Optional[str] = None + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + operation_status: Optional[Literal["ENABLE", "DISABLE"]] = None + + optimization_event: Optional[str] = None + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + optimization_goal: Optional[ + Literal[ + "CLICK", + "CONVERT", + "INSTALL", + "IN_APP_EVENT", + "REACH", + "SHOW", + "VIDEO_VIEW", + "ENGAGED_VIEW", + "ENGAGED_VIEW_FIFTEEN", + "LEAD_GENERATION", + "PREFERRED_LEAD", + "CONVERSATION", + "FOLLOWERS", + "PROFILE_VIEWS", + "PAGE_VISIT", + "VALUE", + "AUTOMATIC_VALUE_OPTIMIZATION", + "TRAFFIC_LANDING_PAGE_VIEW", + "DESTINATION_VISIT", + "MT_LIVE_ROOM", + "PRODUCT_CLICK_IN_LIVE", + ] + ] = None + + pacing: Optional[Literal["PACING_MODE_SMOOTH", "PACING_MODE_FAST"]] = None + + pixel_id: Optional[str] = None + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + placement_type: Optional[Literal["PLACEMENT_TYPE_AUTOMATIC", "PLACEMENT_TYPE_NORMAL"]] = None + + placements: Optional[List[str]] = None + + platform: Literal["meta", "tiktok"] + """The ad platform (meta, tiktok).""" + + schedule_end_time: Optional[str] = None + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + schedule_start_time: Optional[str] = None + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + schedule_type: Optional[Literal["SCHEDULE_START_END", "SCHEDULE_FROM_NOW"]] = None + + typename: Literal["TiktokAdGroupPlatformConfigType"] + """The typename of this object""" + + +PlatformConfig: TypeAlias = Annotated[ + Union[ + Optional[PlatformConfigMetaAdGroupPlatformConfigType], Optional[PlatformConfigTiktokAdGroupPlatformConfigType] + ], + PropertyInfo(discriminator="typename"), +] + + +class AdGroupUpdateResponse(BaseModel): + """An external ad group (ad set) belonging to an ad campaign""" + + id: str + """The unique identifier for the external ad group.""" + + ad_campaign: AdCampaign + """The parent ad campaign""" + + config: Optional[Config] = None + """Unified ad group configuration (platform-agnostic)""" + + created_at: datetime + """The datetime the external ad group was created.""" + + daily_budget: Optional[float] = None + """Daily budget in dollars (nil for lifetime budgets)""" + + name: Optional[str] = None + """Human-readable ad group name""" + + platform_config: PlatformConfig + """Typed platform-specific configuration. + + Use inline fragments (... on MetaAdGroupPlatformConfigType). + """ + + status: Literal["active", "paused", "inactive", "in_review", "rejected", "flagged"] + """Current operational status of the ad group""" + + updated_at: datetime + """The datetime the external ad group was last updated.""" diff --git a/src/whop_sdk/types/ad_list_params.py b/src/whop_sdk/types/ad_list_params.py new file mode 100644 index 00000000..17c36e1b --- /dev/null +++ b/src/whop_sdk/types/ad_list_params.py @@ -0,0 +1,52 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Union, Optional +from datetime import datetime +from typing_extensions import Literal, Annotated, TypedDict + +from .._utils import PropertyInfo + +__all__ = ["AdListParams"] + + +class AdListParams(TypedDict, total=False): + ad_group_id: Optional[str] + """Filter by ad group. + + Provide exactly one of ad_group_id, campaign_id, or company_id. + """ + + after: Optional[str] + """Returns the elements in the list that come after the specified cursor.""" + + before: Optional[str] + """Returns the elements in the list that come before the specified cursor.""" + + campaign_id: Optional[str] + """Filter by campaign. + + Provide exactly one of ad_group_id, campaign_id, or company_id. + """ + + company_id: Optional[str] + """Filter by company. + + Provide exactly one of ad_group_id, campaign_id, or company_id. + """ + + created_after: Annotated[Union[str, datetime, None], PropertyInfo(format="iso8601")] + """Only return ads created after this timestamp.""" + + created_before: Annotated[Union[str, datetime, None], PropertyInfo(format="iso8601")] + """Only return ads created before this timestamp.""" + + first: Optional[int] + """Returns the first _n_ elements from the list.""" + + last: Optional[int] + """Returns the last _n_ elements from the list.""" + + status: Optional[Literal["active", "paused", "inactive", "in_review", "rejected", "flagged"]] + """The status of an external ad.""" diff --git a/src/whop_sdk/types/ad_list_response.py b/src/whop_sdk/types/ad_list_response.py new file mode 100644 index 00000000..5711b6dd --- /dev/null +++ b/src/whop_sdk/types/ad_list_response.py @@ -0,0 +1,223 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Union, Optional +from datetime import datetime +from typing_extensions import Literal, Annotated, TypeAlias + +from .._utils import PropertyInfo +from .._models import BaseModel + +__all__ = [ + "AdListResponse", + "PlatformConfig", + "PlatformConfigMetaAdPlatformConfigType", + "PlatformConfigTiktokAdPlatformConfigType", +] + + +class PlatformConfigMetaAdPlatformConfigType(BaseModel): + """Meta (Facebook/Instagram) ad configuration.""" + + call_to_action_type: Optional[ + Literal[ + "LEARN_MORE", + "SHOP_NOW", + "SIGN_UP", + "SUBSCRIBE", + "GET_STARTED", + "BOOK_NOW", + "APPLY_NOW", + "CONTACT_US", + "DOWNLOAD", + "ORDER_NOW", + "BUY_NOW", + "GET_QUOTE", + "MESSAGE_PAGE", + "WHATSAPP_MESSAGE", + "INSTAGRAM_MESSAGE", + "CALL_NOW", + "GET_DIRECTIONS", + "SEND_UPDATES", + "GET_OFFER", + "WATCH_MORE", + "LISTEN_NOW", + "PLAY_GAME", + "OPEN_LINK", + "NO_BUTTON", + "GET_OFFER_VIEW", + "GET_EVENT_TICKETS", + "SEE_MENU", + "REQUEST_TIME", + "EVENT_RSVP", + "SEE_DETAILS", + "VIEW_INSTAGRAM_PROFILE", + ] + ] = None + + headline: Optional[str] = None + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + instagram_actor_id: Optional[str] = None + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + link_url: Optional[str] = None + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + name: Optional[str] = None + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + page_id: Optional[str] = None + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + platform: Literal["meta", "tiktok"] + """The ad platform.""" + + primary_text: Optional[str] = None + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + typename: Literal["MetaAdPlatformConfigType"] + """The typename of this object""" + + url_tags: Optional[str] = None + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + +class PlatformConfigTiktokAdPlatformConfigType(BaseModel): + """TikTok ad configuration.""" + + ad_format: Optional[Literal["SINGLE_IMAGE", "SINGLE_VIDEO", "CAROUSEL_ADS", "CATALOG_CAROUSEL", "LIVE_CONTENT"]] = ( + None + ) + + ad_name: Optional[str] = None + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + ad_text: Optional[str] = None + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + call_to_action: Optional[ + Literal[ + "LEARN_MORE", + "DOWNLOAD", + "SHOP_NOW", + "SIGN_UP", + "CONTACT_US", + "APPLY_NOW", + "BOOK_NOW", + "PLAY_GAME", + "WATCH_NOW", + "READ_MORE", + "VIEW_NOW", + "GET_QUOTE", + "ORDER_NOW", + "INSTALL_NOW", + "GET_SHOWTIMES", + "LISTEN_NOW", + "INTERESTED", + "SUBSCRIBE", + "GET_TICKETS_NOW", + "EXPERIENCE_NOW", + "PRE_ORDER_NOW", + "VISIT_STORE", + ] + ] = None + """TikTok call-to-action button text. See docs/tiktok_api/ad.md § call_to_action.""" + + identity_authorized_bc_id: Optional[str] = None + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + identity_id: Optional[str] = None + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + identity_type: Optional[Literal["CUSTOMIZED_USER", "AUTH_CODE", "TT_USER", "BC_AUTH_TT"]] = None + + image_ids: Optional[List[str]] = None + + landing_page_url: Optional[str] = None + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + platform: Literal["meta", "tiktok"] + """The ad platform.""" + + typename: Literal["TiktokAdPlatformConfigType"] + """The typename of this object""" + + video_id: Optional[str] = None + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + +PlatformConfig: TypeAlias = Annotated[ + Union[Optional[PlatformConfigMetaAdPlatformConfigType], Optional[PlatformConfigTiktokAdPlatformConfigType]], + PropertyInfo(discriminator="typename"), +] + + +class AdListResponse(BaseModel): + """An ad belonging to an ad group""" + + id: str + """Unique identifier for the ad.""" + + created_at: datetime + """When the ad was created.""" + + platform_config: PlatformConfig + """Typed platform-specific configuration.""" + + status: Literal["active", "paused", "inactive", "in_review", "rejected", "flagged"] + """Current status of the ad.""" + + updated_at: datetime + """When the ad was last updated.""" diff --git a/src/whop_sdk/types/ad_retrieve_response.py b/src/whop_sdk/types/ad_retrieve_response.py new file mode 100644 index 00000000..ba0008e4 --- /dev/null +++ b/src/whop_sdk/types/ad_retrieve_response.py @@ -0,0 +1,251 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Union, Optional +from datetime import datetime +from typing_extensions import Literal, Annotated, TypeAlias + +from .._utils import PropertyInfo +from .._models import BaseModel + +__all__ = [ + "AdRetrieveResponse", + "ExternalAdCreativeSet", + "ExternalAdGroup", + "PlatformConfig", + "PlatformConfigMetaAdPlatformConfigType", + "PlatformConfigTiktokAdPlatformConfigType", +] + + +class ExternalAdCreativeSet(BaseModel): + """The creative set used by this ad.""" + + id: str + """The unique identifier for the external ad creative set.""" + + +class ExternalAdGroup(BaseModel): + """The parent ad group.""" + + id: str + """The unique identifier for the external ad group.""" + + name: Optional[str] = None + """Human-readable ad group name""" + + status: Literal["active", "paused", "inactive", "in_review", "rejected", "flagged"] + """Current operational status of the ad group""" + + +class PlatformConfigMetaAdPlatformConfigType(BaseModel): + """Meta (Facebook/Instagram) ad configuration.""" + + call_to_action_type: Optional[ + Literal[ + "LEARN_MORE", + "SHOP_NOW", + "SIGN_UP", + "SUBSCRIBE", + "GET_STARTED", + "BOOK_NOW", + "APPLY_NOW", + "CONTACT_US", + "DOWNLOAD", + "ORDER_NOW", + "BUY_NOW", + "GET_QUOTE", + "MESSAGE_PAGE", + "WHATSAPP_MESSAGE", + "INSTAGRAM_MESSAGE", + "CALL_NOW", + "GET_DIRECTIONS", + "SEND_UPDATES", + "GET_OFFER", + "WATCH_MORE", + "LISTEN_NOW", + "PLAY_GAME", + "OPEN_LINK", + "NO_BUTTON", + "GET_OFFER_VIEW", + "GET_EVENT_TICKETS", + "SEE_MENU", + "REQUEST_TIME", + "EVENT_RSVP", + "SEE_DETAILS", + "VIEW_INSTAGRAM_PROFILE", + ] + ] = None + + headline: Optional[str] = None + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + instagram_actor_id: Optional[str] = None + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + link_url: Optional[str] = None + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + name: Optional[str] = None + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + page_id: Optional[str] = None + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + platform: Literal["meta", "tiktok"] + """The ad platform.""" + + primary_text: Optional[str] = None + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + typename: Literal["MetaAdPlatformConfigType"] + """The typename of this object""" + + url_tags: Optional[str] = None + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + +class PlatformConfigTiktokAdPlatformConfigType(BaseModel): + """TikTok ad configuration.""" + + ad_format: Optional[Literal["SINGLE_IMAGE", "SINGLE_VIDEO", "CAROUSEL_ADS", "CATALOG_CAROUSEL", "LIVE_CONTENT"]] = ( + None + ) + + ad_name: Optional[str] = None + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + ad_text: Optional[str] = None + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + call_to_action: Optional[ + Literal[ + "LEARN_MORE", + "DOWNLOAD", + "SHOP_NOW", + "SIGN_UP", + "CONTACT_US", + "APPLY_NOW", + "BOOK_NOW", + "PLAY_GAME", + "WATCH_NOW", + "READ_MORE", + "VIEW_NOW", + "GET_QUOTE", + "ORDER_NOW", + "INSTALL_NOW", + "GET_SHOWTIMES", + "LISTEN_NOW", + "INTERESTED", + "SUBSCRIBE", + "GET_TICKETS_NOW", + "EXPERIENCE_NOW", + "PRE_ORDER_NOW", + "VISIT_STORE", + ] + ] = None + """TikTok call-to-action button text. See docs/tiktok_api/ad.md § call_to_action.""" + + identity_authorized_bc_id: Optional[str] = None + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + identity_id: Optional[str] = None + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + identity_type: Optional[Literal["CUSTOMIZED_USER", "AUTH_CODE", "TT_USER", "BC_AUTH_TT"]] = None + + image_ids: Optional[List[str]] = None + + landing_page_url: Optional[str] = None + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + platform: Literal["meta", "tiktok"] + """The ad platform.""" + + typename: Literal["TiktokAdPlatformConfigType"] + """The typename of this object""" + + video_id: Optional[str] = None + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + +PlatformConfig: TypeAlias = Annotated[ + Union[Optional[PlatformConfigMetaAdPlatformConfigType], Optional[PlatformConfigTiktokAdPlatformConfigType]], + PropertyInfo(discriminator="typename"), +] + + +class AdRetrieveResponse(BaseModel): + """An ad belonging to an ad group""" + + id: str + """Unique identifier for the ad.""" + + created_at: datetime + """When the ad was created.""" + + external_ad_creative_set: Optional[ExternalAdCreativeSet] = None + """The creative set used by this ad.""" + + external_ad_group: ExternalAdGroup + """The parent ad group.""" + + platform_config: PlatformConfig + """Typed platform-specific configuration.""" + + status: Literal["active", "paused", "inactive", "in_review", "rejected", "flagged"] + """Current status of the ad.""" + + updated_at: datetime + """When the ad was last updated.""" diff --git a/src/whop_sdk/types/bounty_create_params.py b/src/whop_sdk/types/bounty_create_params.py new file mode 100644 index 00000000..0934b79b --- /dev/null +++ b/src/whop_sdk/types/bounty_create_params.py @@ -0,0 +1,50 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Optional +from typing_extensions import Required, TypedDict + +from .._types import SequenceNotStr +from .shared.currency import Currency + +__all__ = ["BountyCreateParams"] + + +class BountyCreateParams(TypedDict, total=False): + base_unit_amount: Required[float] + """The amount paid to each approved submission. + + The total bounty pool funded is this amount times accepted_submissions_limit. + """ + + currency: Required[Currency] + """The currency for the bounty pool funding amount.""" + + description: Required[str] + """The description of the bounty.""" + + title: Required[str] + """The title of the bounty.""" + + accepted_submissions_limit: Optional[int] + """The number of submissions that can be approved before the bounty closes. + + Defaults to 1. + """ + + allowed_country_codes: Optional[SequenceNotStr[str]] + """The ISO3166 country codes where this bounty should be visible. + + Empty means globally visible. + """ + + experience_id: Optional[str] + """An optional experience to scope the bounty to.""" + + origin_account_id: Optional[str] + """The user (user*\\**) or company (biz*\\**) tag whose balance funds this bounty pool. + + Defaults to the requester's personal balance when omitted. The requester must be + the user themself or an owner/admin of the company. + """ diff --git a/src/whop_sdk/types/bounty_create_response.py b/src/whop_sdk/types/bounty_create_response.py new file mode 100644 index 00000000..94c4f5e4 --- /dev/null +++ b/src/whop_sdk/types/bounty_create_response.py @@ -0,0 +1,46 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from datetime import datetime +from typing_extensions import Literal + +from .._models import BaseModel +from .shared.currency import Currency + +__all__ = ["BountyCreateResponse"] + + +class BountyCreateResponse(BaseModel): + """A privately accessible bounty.""" + + id: str + """The unique identifier for the bounty.""" + + bounty_type: Literal["classic", "user_funded", "workforce"] + """The underlying bounty implementation type.""" + + created_at: datetime + """The datetime the bounty was created.""" + + currency: Currency + """The currency used for the bounty funds.""" + + description: str + """The description of the bounty.""" + + status: Literal["published", "archived"] + """The current lifecycle status of the bounty.""" + + title: str + """The title of the bounty.""" + + total_available: float + """The total amount currently funded in the bounty pool for payout.""" + + total_paid: float + """The total amount paid out for this bounty.""" + + updated_at: datetime + """The datetime the bounty was last updated.""" + + vote_threshold: int + """The number of watcher votes required before the submission can resolve.""" diff --git a/src/whop_sdk/types/bounty_list_params.py b/src/whop_sdk/types/bounty_list_params.py new file mode 100644 index 00000000..cb80eaba --- /dev/null +++ b/src/whop_sdk/types/bounty_list_params.py @@ -0,0 +1,36 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Optional +from typing_extensions import Literal, TypedDict + +from .shared.direction import Direction + +__all__ = ["BountyListParams"] + + +class BountyListParams(TypedDict, total=False): + after: Optional[str] + """Returns the elements in the list that come after the specified cursor.""" + + before: Optional[str] + """Returns the elements in the list that come before the specified cursor.""" + + direction: Optional[Direction] + """The direction of the sort.""" + + experience_id: Optional[str] + """The experience to list bounties for. + + When omitted, returns bounties with no experience. + """ + + first: Optional[int] + """Returns the first _n_ elements from the list.""" + + last: Optional[int] + """Returns the last _n_ elements from the list.""" + + status: Optional[Literal["published", "archived"]] + """The available bounty statuses to choose from.""" diff --git a/src/whop_sdk/types/bounty_list_response.py b/src/whop_sdk/types/bounty_list_response.py new file mode 100644 index 00000000..0117a1ba --- /dev/null +++ b/src/whop_sdk/types/bounty_list_response.py @@ -0,0 +1,46 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from datetime import datetime +from typing_extensions import Literal + +from .._models import BaseModel +from .shared.currency import Currency + +__all__ = ["BountyListResponse"] + + +class BountyListResponse(BaseModel): + """A privately accessible bounty.""" + + id: str + """The unique identifier for the bounty.""" + + bounty_type: Literal["classic", "user_funded", "workforce"] + """The underlying bounty implementation type.""" + + created_at: datetime + """The datetime the bounty was created.""" + + currency: Currency + """The currency used for the bounty funds.""" + + description: str + """The description of the bounty.""" + + status: Literal["published", "archived"] + """The current lifecycle status of the bounty.""" + + title: str + """The title of the bounty.""" + + total_available: float + """The total amount currently funded in the bounty pool for payout.""" + + total_paid: float + """The total amount paid out for this bounty.""" + + updated_at: datetime + """The datetime the bounty was last updated.""" + + vote_threshold: int + """The number of watcher votes required before the submission can resolve.""" diff --git a/src/whop_sdk/types/bounty_retrieve_response.py b/src/whop_sdk/types/bounty_retrieve_response.py new file mode 100644 index 00000000..d9df060a --- /dev/null +++ b/src/whop_sdk/types/bounty_retrieve_response.py @@ -0,0 +1,46 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from datetime import datetime +from typing_extensions import Literal + +from .._models import BaseModel +from .shared.currency import Currency + +__all__ = ["BountyRetrieveResponse"] + + +class BountyRetrieveResponse(BaseModel): + """A privately accessible bounty.""" + + id: str + """The unique identifier for the bounty.""" + + bounty_type: Literal["classic", "user_funded", "workforce"] + """The underlying bounty implementation type.""" + + created_at: datetime + """The datetime the bounty was created.""" + + currency: Currency + """The currency used for the bounty funds.""" + + description: str + """The description of the bounty.""" + + status: Literal["published", "archived"] + """The current lifecycle status of the bounty.""" + + title: str + """The title of the bounty.""" + + total_available: float + """The total amount currently funded in the bounty pool for payout.""" + + total_paid: float + """The total amount paid out for this bounty.""" + + updated_at: datetime + """The datetime the bounty was last updated.""" + + vote_threshold: int + """The number of watcher votes required before the submission can resolve.""" diff --git a/src/whop_sdk/types/checkout_configuration_create_params.py b/src/whop_sdk/types/checkout_configuration_create_params.py index bb990dc5..ec80485f 100644 --- a/src/whop_sdk/types/checkout_configuration_create_params.py +++ b/src/whop_sdk/types/checkout_configuration_create_params.py @@ -124,7 +124,7 @@ class CreateCheckoutSessionInputModePaymentWithPlanPlanPaymentMethodConfiguratio defaults with additional methods. """ - include_platform_defaults: Required[bool] + include_platform_defaults: Optional[bool] """ Whether Whop's platform default payment method enablement settings are included in this configuration. The full list of default payment methods can be found in @@ -195,6 +195,9 @@ class CreateCheckoutSessionInputModePaymentWithPlanPlan(TypedDict, total=False): currency: Required[Currency] """The respective currency identifier for the plan.""" + adaptive_pricing_enabled: Optional[bool] + """Whether this plan accepts local currency payments via adaptive pricing.""" + application_fee_amount: Optional[float] """The application fee amount collected by the platform from this connected account. @@ -298,6 +301,12 @@ class CreateCheckoutSessionInputModePaymentWithPlanCheckoutStyling(TypedDict, to Overrides plan and company defaults. """ + background_color: Optional[str] + """ + A hex color code for the checkout page background, applied to the order summary + panel (e.g. #F4F4F5). + """ + border_style: Optional[CheckoutShape] """The different border-radius styles available for checkout pages.""" @@ -328,7 +337,7 @@ class CreateCheckoutSessionInputModePaymentWithPlanPaymentMethodConfiguration(Ty defaults with additional methods. """ - include_platform_defaults: Required[bool] + include_platform_defaults: Optional[bool] """ Whether Whop's platform default payment method enablement settings are included in this configuration. The full list of default payment methods can be found in @@ -386,6 +395,12 @@ class CreateCheckoutSessionInputModePaymentWithPlanIDCheckoutStyling(TypedDict, Overrides plan and company defaults. """ + background_color: Optional[str] + """ + A hex color code for the checkout page background, applied to the order summary + panel (e.g. #F4F4F5). + """ + border_style: Optional[CheckoutShape] """The different border-radius styles available for checkout pages.""" @@ -416,7 +431,7 @@ class CreateCheckoutSessionInputModePaymentWithPlanIDPaymentMethodConfiguration( defaults with additional methods. """ - include_platform_defaults: Required[bool] + include_platform_defaults: Optional[bool] """ Whether Whop's platform default payment method enablement settings are included in this configuration. The full list of default payment methods can be found in @@ -471,6 +486,12 @@ class CreateCheckoutSessionInputModeSetupCheckoutStyling(TypedDict, total=False) Overrides plan and company defaults. """ + background_color: Optional[str] + """ + A hex color code for the checkout page background, applied to the order summary + panel (e.g. #F4F4F5). + """ + border_style: Optional[CheckoutShape] """The different border-radius styles available for checkout pages.""" @@ -501,7 +522,7 @@ class CreateCheckoutSessionInputModeSetupPaymentMethodConfiguration(TypedDict, t defaults with additional methods. """ - include_platform_defaults: Required[bool] + include_platform_defaults: Optional[bool] """ Whether Whop's platform default payment method enablement settings are included in this configuration. The full list of default payment methods can be found in diff --git a/src/whop_sdk/types/checkout_configuration_list_response.py b/src/whop_sdk/types/checkout_configuration_list_response.py index 41b67426..bb54ed0a 100644 --- a/src/whop_sdk/types/checkout_configuration_list_response.py +++ b/src/whop_sdk/types/checkout_configuration_list_response.py @@ -47,6 +47,12 @@ class Plan(BaseModel): id: str """The unique identifier for the plan.""" + adaptive_pricing_enabled: bool + """Whether the creator has turned on adaptive pricing for this plan. + + Raw setting — does not check processor compatibility or feature flags. + """ + billing_period: Optional[int] = None """The number of days between each recurring charge. diff --git a/src/whop_sdk/types/company_create_api_key_params.py b/src/whop_sdk/types/company_create_api_key_params.py new file mode 100644 index 00000000..a9085044 --- /dev/null +++ b/src/whop_sdk/types/company_create_api_key_params.py @@ -0,0 +1,47 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Iterable, Optional +from typing_extensions import Literal, Required, TypedDict + +from .._types import SequenceNotStr + +__all__ = ["CompanyCreateAPIKeyParams", "Permission"] + + +class CompanyCreateAPIKeyParams(TypedDict, total=False): + child_company_id: Required[str] + """The unique identifier of the connected account to create the API key for (e.g. + + 'biz_xxx'). + """ + + name: Optional[str] + """A human-readable name for the API key, such as 'Production API Key'.""" + + permissions: Optional[Iterable[Permission]] + """Granular permission statements defining which actions this API key can perform. + + Either permissions or role must be provided. + """ + + role: Optional[Literal["owner", "admin", "moderator", "sales_manager", "advertiser"]] + """The different system roles that can be assigned.""" + + +class Permission(TypedDict, total=False): + """Input for a single permissions statement""" + + actions: Required[SequenceNotStr[str]] + """Actions covered by this statement""" + + grant: Required[bool] + """Whether the actions are granted or denied""" + + resources: Required[SequenceNotStr[str]] + """Resource identifiers. + + Can look like 'biz*xxxx' or 'biz_xxx|pass*_|exp*xxx' or 'biz_xxx|app_xxx' or + 'biz_xxx|pass_xxx|exp_xxx' or 'biz_xxx|pass_xxx' or 'biz_xxx|pass*_' + """ diff --git a/src/whop_sdk/types/company_create_api_key_response.py b/src/whop_sdk/types/company_create_api_key_response.py new file mode 100644 index 00000000..691c6ecb --- /dev/null +++ b/src/whop_sdk/types/company_create_api_key_response.py @@ -0,0 +1,20 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional + +from .._models import BaseModel + +__all__ = ["CompanyCreateAPIKeyResponse"] + + +class CompanyCreateAPIKeyResponse(BaseModel): + """An API key created for a child company, including the one-time secret key.""" + + id: str + """The unique identifier for the authorized api key.""" + + name: Optional[str] = None + """A user set name to identify an API key""" + + secret_key: str + """The secret key used to authenticate requests. Only returned at creation time.""" diff --git a/src/whop_sdk/types/conversion_create_params.py b/src/whop_sdk/types/conversion_create_params.py new file mode 100644 index 00000000..e833dba0 --- /dev/null +++ b/src/whop_sdk/types/conversion_create_params.py @@ -0,0 +1,183 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Union, Optional +from datetime import datetime +from typing_extensions import Literal, Required, Annotated, TypedDict + +from .._utils import PropertyInfo +from .shared.currency import Currency + +__all__ = ["ConversionCreateParams", "Context", "User"] + + +class ConversionCreateParams(TypedDict, total=False): + company_id: Required[str] + """The company to associate with this event.""" + + event_name: Required[ + Literal["lead", "submit_application", "contact", "complete_registration", "schedule", "custom"] + ] + """The type of event.""" + + action_source: Optional[ + Literal[ + "email", + "website", + "app", + "phone_call", + "chat", + "physical_store", + "system_generated", + "business_messaging", + "other", + ] + ] + """The channel where an event originated""" + + context: Optional[Context] + """Tracking and attribution context.""" + + currency: Optional[Currency] + """The available currencies on the platform""" + + custom_name: Optional[str] + """Custom event name when event_name is 'custom'.""" + + event_id: Optional[str] + """Client-provided identifier for deduplication. Generated if omitted.""" + + event_time: Annotated[Union[str, datetime, None], PropertyInfo(format="iso8601")] + """When the event occurred. Defaults to now.""" + + plan_id: Optional[str] + """The plan associated with the event.""" + + product_id: Optional[str] + """The product associated with the event.""" + + referrer_url: Optional[str] + """The referring URL.""" + + url: Optional[str] + """The URL where the event occurred.""" + + user: Optional[User] + """User identity and profile data.""" + + value: Optional[float] + """Monetary value associated with the event.""" + + +class Context(TypedDict, total=False): + """Tracking and attribution context.""" + + ad_campaign_id: Optional[str] + """Ad campaign ID.""" + + ad_id: Optional[str] + """Ad ID.""" + + ad_set_id: Optional[str] + """Ad set ID.""" + + fbclid: Optional[str] + """Facebook click ID.""" + + fbp: Optional[str] + """Facebook browser pixel ID.""" + + ga: Optional[str] + """Google Analytics client ID.""" + + gclid: Optional[str] + """Google click ID.""" + + ig_sid: Optional[str] + """Instagram session ID.""" + + ip_address: Optional[str] + """IP address.""" + + ttclid: Optional[str] + """TikTok click ID.""" + + ttp: Optional[str] + """TikTok pixel ID.""" + + user_agent: Optional[str] + """Browser user agent string.""" + + utm_campaign: Optional[str] + """UTM campaign parameter.""" + + utm_content: Optional[str] + """UTM content parameter.""" + + utm_id: Optional[str] + """UTM ID parameter.""" + + utm_medium: Optional[str] + """UTM medium parameter.""" + + utm_source: Optional[str] + """UTM source parameter.""" + + utm_term: Optional[str] + """UTM term parameter.""" + + +class User(TypedDict, total=False): + """User identity and profile data.""" + + anonymous_id: Optional[str] + """An anonymous identifier for the user.""" + + birthdate: Optional[str] + """Date of birth (YYYY-MM-DD).""" + + city: Optional[str] + """City.""" + + country: Optional[str] + """Country.""" + + email: Optional[str] + """Email address.""" + + external_id: Optional[str] + """An external identifier for the user.""" + + first_name: Optional[str] + """First name.""" + + gender: Optional[Literal["male", "female"]] + """Gender""" + + last_name: Optional[str] + """Last name.""" + + member_id: Optional[str] + """The Whop member ID.""" + + membership_id: Optional[str] + """The Whop membership ID.""" + + name: Optional[str] + """Full display name.""" + + phone: Optional[str] + """Phone number.""" + + postal_code: Optional[str] + """Postal code.""" + + state: Optional[str] + """State or region.""" + + user_id: Optional[str] + """The Whop user ID.""" + + username: Optional[str] + """Username.""" diff --git a/src/whop_sdk/types/conversion_create_response.py b/src/whop_sdk/types/conversion_create_response.py new file mode 100644 index 00000000..eeef3e89 --- /dev/null +++ b/src/whop_sdk/types/conversion_create_response.py @@ -0,0 +1,12 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from .._models import BaseModel + +__all__ = ["ConversionCreateResponse"] + + +class ConversionCreateResponse(BaseModel): + """A tracked conversion event""" + + id: str + """The unique identifier for the conversion""" diff --git a/src/whop_sdk/types/file_create_params.py b/src/whop_sdk/types/file_create_params.py index c598f20f..6df14110 100644 --- a/src/whop_sdk/types/file_create_params.py +++ b/src/whop_sdk/types/file_create_params.py @@ -2,7 +2,8 @@ from __future__ import annotations -from typing_extensions import Required, TypedDict +from typing import Optional +from typing_extensions import Literal, Required, TypedDict __all__ = ["FileCreateParams"] @@ -13,3 +14,9 @@ class FileCreateParams(TypedDict, total=False): The name of the file including its extension (e.g., "photo.png" or "document.pdf"). """ + + visibility: Optional[Literal["public", "private"]] + """ + Controls whether an uploaded file is publicly accessible or requires + authentication to access. + """ diff --git a/src/whop_sdk/types/file_create_response.py b/src/whop_sdk/types/file_create_response.py index 51ac1b41..5c9e2fa4 100644 --- a/src/whop_sdk/types/file_create_response.py +++ b/src/whop_sdk/types/file_create_response.py @@ -1,6 +1,7 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from typing import Dict, Optional +from typing_extensions import Literal from .._models import BaseModel from .upload_status import UploadStatus @@ -39,7 +40,11 @@ class FileCreateResponse(BaseModel): """ url: Optional[str] = None - """The CDN URL for accessing the file. + """The URL for accessing the file. - Null if the file has not finished uploading. + For public files, this is a permanent CDN URL. For private files, this is a + signed URL that expires. Null if the file has not finished uploading. """ + + visibility: Literal["public", "private"] + """Whether the file is publicly accessible or requires authentication.""" diff --git a/src/whop_sdk/types/file_retrieve_response.py b/src/whop_sdk/types/file_retrieve_response.py index f5c5c6b1..c60e895e 100644 --- a/src/whop_sdk/types/file_retrieve_response.py +++ b/src/whop_sdk/types/file_retrieve_response.py @@ -1,6 +1,7 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from typing import Optional +from typing_extensions import Literal from .._models import BaseModel from .upload_status import UploadStatus @@ -27,7 +28,11 @@ class FileRetrieveResponse(BaseModel): """The current upload status of the file (e.g., pending, ready).""" url: Optional[str] = None - """The CDN URL for accessing the file. + """The URL for accessing the file. - Null if the file has not finished uploading. + For public files, this is a permanent CDN URL. For private files, this is a + signed URL that expires. Null if the file has not finished uploading. """ + + visibility: Literal["public", "private"] + """Whether the file is publicly accessible or requires authentication.""" diff --git a/src/whop_sdk/types/forum_post_list_params.py b/src/whop_sdk/types/forum_post_list_params.py index cb4725f1..84dcc7ed 100644 --- a/src/whop_sdk/types/forum_post_list_params.py +++ b/src/whop_sdk/types/forum_post_list_params.py @@ -21,6 +21,9 @@ class ForumPostListParams(TypedDict, total=False): first: Optional[int] """Returns the first _n_ elements from the list.""" + include_bounty_anchors: Optional[bool] + """Whether to include top-level bounty discussion anchors as rich forum items.""" + last: Optional[int] """Returns the last _n_ elements from the list.""" diff --git a/src/whop_sdk/types/invoice_create_params.py b/src/whop_sdk/types/invoice_create_params.py index ac722bbd..8ff9b337 100644 --- a/src/whop_sdk/types/invoice_create_params.py +++ b/src/whop_sdk/types/invoice_create_params.py @@ -176,7 +176,7 @@ class CreateInvoiceInputWithProductPlanPaymentMethodConfiguration(TypedDict, tot defaults with additional methods. """ - include_platform_defaults: Required[bool] + include_platform_defaults: Optional[bool] """ Whether Whop's platform default payment method enablement settings are included in this configuration. The full list of default payment methods can be found in @@ -462,7 +462,7 @@ class CreateInvoiceInputWithProductIDPlanPaymentMethodConfiguration(TypedDict, t defaults with additional methods. """ - include_platform_defaults: Required[bool] + include_platform_defaults: Optional[bool] """ Whether Whop's platform default payment method enablement settings are included in this configuration. The full list of default payment methods can be found in diff --git a/src/whop_sdk/types/invoice_update_params.py b/src/whop_sdk/types/invoice_update_params.py index 81482eff..ba46184f 100644 --- a/src/whop_sdk/types/invoice_update_params.py +++ b/src/whop_sdk/types/invoice_update_params.py @@ -65,6 +65,12 @@ class InvoiceUpdateParams(TypedDict, total=False): plan: Optional[Plan] """Updated plan attributes.""" + product_id: Optional[str] + """The unique identifier of an existing product to attach to this invoice. + + Only allowed while the invoice is still a draft. + """ + subscription_billing_anchor_at: Annotated[Union[str, datetime, None], PropertyInfo(format="iso8601")] """The date that defines when the subscription billing cycle should start.""" @@ -161,7 +167,7 @@ class PlanPaymentMethodConfiguration(TypedDict, total=False): defaults with additional methods. """ - include_platform_defaults: Required[bool] + include_platform_defaults: Optional[bool] """ Whether Whop's platform default payment method enablement settings are included in this configuration. The full list of default payment methods can be found in diff --git a/src/whop_sdk/types/membership_list_response.py b/src/whop_sdk/types/membership_list_response.py index 13586e58..683d67d7 100644 --- a/src/whop_sdk/types/membership_list_response.py +++ b/src/whop_sdk/types/membership_list_response.py @@ -114,6 +114,13 @@ class MembershipListResponse(BaseModel): Null if the customer did not provide a reason. """ + checkout_configuration_id: Optional[str] = None + """ + The ID of the checkout session/configuration that produced this membership, if + any. Use this to map memberships back to the checkout configuration that created + them. + """ + company: Company """The company this membership belongs to.""" diff --git a/src/whop_sdk/types/payment_list_response.py b/src/whop_sdk/types/payment_list_response.py index 1c12050c..2af1a1d6 100644 --- a/src/whop_sdk/types/payment_list_response.py +++ b/src/whop_sdk/types/payment_list_response.py @@ -259,6 +259,12 @@ class PaymentListResponse(BaseModel): Null if the payment was not made with a card. """ + checkout_configuration_id: Optional[str] = None + """The ID of the checkout session/configuration that produced this payment, if any. + + Use this to map payments back to the checkout configuration that created them. + """ + company: Optional[Company] = None """The company for the payment.""" @@ -339,6 +345,13 @@ class PaymentListResponse(BaseModel): otherwise false. Used to decide if Whop can attempt the charge again. """ + settlement_currency: str + """ + The currency in which the creator receives payouts and fees are charged (e.g., + 'usd', 'eur'). For multi-currency payments this differs from the payment + currency. + """ + status: Optional[ReceiptStatus] = None """The status of a receipt""" diff --git a/src/whop_sdk/types/payment_method_types.py b/src/whop_sdk/types/payment_method_types.py index 66d23567..859feebe 100644 --- a/src/whop_sdk/types/payment_method_types.py +++ b/src/whop_sdk/types/payment_method_types.py @@ -22,6 +22,7 @@ "bizum", "blik", "boleto", + "ca_bank_transfer", "capchase_pay", "card", "cashapp", @@ -45,6 +46,7 @@ "interac", "kakao_pay", "klarna", + "klarna_pay_now", "konbini", "kr_card", "kr_market", diff --git a/src/whop_sdk/types/payment_refund_params.py b/src/whop_sdk/types/payment_refund_params.py index 9d7d2bc8..df2eef7e 100644 --- a/src/whop_sdk/types/payment_refund_params.py +++ b/src/whop_sdk/types/payment_refund_params.py @@ -10,7 +10,9 @@ class PaymentRefundParams(TypedDict, total=False): partial_amount: Optional[float] - """The amount to refund in the payment currency. + """The amount to refund. - If omitted, the full payment amount is refunded. + For multi-currency payments, this is in the charge currency (what the buyer + paid). For single-currency, this is in the payment currency. If omitted, the + full payment amount is refunded. """ diff --git a/src/whop_sdk/types/plan_create_params.py b/src/whop_sdk/types/plan_create_params.py index d22e29e7..c0d6f89c 100644 --- a/src/whop_sdk/types/plan_create_params.py +++ b/src/whop_sdk/types/plan_create_params.py @@ -24,6 +24,9 @@ class PlanCreateParams(TypedDict, total=False): product_id: Required[str] """The unique identifier of the product to attach this plan to.""" + adaptive_pricing_enabled: Optional[bool] + """Whether this plan accepts local currency payments via adaptive pricing.""" + billing_period: Optional[int] """The number of days between recurring charges. @@ -123,6 +126,12 @@ class CheckoutStyling(TypedDict, total=False): Pass null to inherit from the company default. """ + background_color: Optional[str] + """ + A hex color code for the checkout page background, applied to the order summary + panel (e.g. #F4F4F5). + """ + border_style: Optional[CheckoutShape] """The different border-radius styles available for checkout pages.""" @@ -180,7 +189,7 @@ class PaymentMethodConfiguration(TypedDict, total=False): defaults with additional methods. """ - include_platform_defaults: Required[bool] + include_platform_defaults: Optional[bool] """ Whether Whop's platform default payment method enablement settings are included in this configuration. The full list of default payment methods can be found in diff --git a/src/whop_sdk/types/plan_list_response.py b/src/whop_sdk/types/plan_list_response.py index 688d7f43..b034018e 100644 --- a/src/whop_sdk/types/plan_list_response.py +++ b/src/whop_sdk/types/plan_list_response.py @@ -88,6 +88,12 @@ class PlanListResponse(BaseModel): id: str """The unique identifier for the plan.""" + adaptive_pricing_enabled: bool + """Whether the creator has turned on adaptive pricing for this plan. + + Raw setting — does not check processor compatibility or feature flags. + """ + billing_period: Optional[int] = None """The number of days between each recurring charge. diff --git a/src/whop_sdk/types/plan_update_params.py b/src/whop_sdk/types/plan_update_params.py index 240f022f..d150d067 100644 --- a/src/whop_sdk/types/plan_update_params.py +++ b/src/whop_sdk/types/plan_update_params.py @@ -16,6 +16,9 @@ class PlanUpdateParams(TypedDict, total=False): + adaptive_pricing_enabled: Optional[bool] + """Whether this plan accepts local currency payments via adaptive pricing.""" + billing_period: Optional[int] """The number of days between recurring charges. @@ -116,6 +119,12 @@ class CheckoutStyling(TypedDict, total=False): Pass null to remove all overrides and inherit from the company default. """ + background_color: Optional[str] + """ + A hex color code for the checkout page background, applied to the order summary + panel (e.g. #F4F4F5). + """ + border_style: Optional[CheckoutShape] """The different border-radius styles available for checkout pages.""" @@ -173,7 +182,7 @@ class PaymentMethodConfiguration(TypedDict, total=False): defaults with additional methods. """ - include_platform_defaults: Required[bool] + include_platform_defaults: Optional[bool] """ Whether Whop's platform default payment method enablement settings are included in this configuration. The full list of default payment methods can be found in diff --git a/src/whop_sdk/types/shared/authorized_user_roles.py b/src/whop_sdk/types/shared/authorized_user_roles.py index 877db6d2..3003b97e 100644 --- a/src/whop_sdk/types/shared/authorized_user_roles.py +++ b/src/whop_sdk/types/shared/authorized_user_roles.py @@ -5,5 +5,5 @@ __all__ = ["AuthorizedUserRoles"] AuthorizedUserRoles: TypeAlias = Literal[ - "owner", "admin", "sales_manager", "moderator", "app_manager", "support", "manager" + "owner", "admin", "sales_manager", "moderator", "advertiser", "app_manager", "support", "manager" ] diff --git a/src/whop_sdk/types/shared/checkout_configuration.py b/src/whop_sdk/types/shared/checkout_configuration.py index e1fcc4c0..28dc3174 100644 --- a/src/whop_sdk/types/shared/checkout_configuration.py +++ b/src/whop_sdk/types/shared/checkout_configuration.py @@ -47,6 +47,12 @@ class Plan(BaseModel): id: str """The unique identifier for the plan.""" + adaptive_pricing_enabled: bool + """Whether the creator has turned on adaptive pricing for this plan. + + Raw setting — does not check processor compatibility or feature flags. + """ + billing_period: Optional[int] = None """The number of days between each recurring charge. diff --git a/src/whop_sdk/types/shared/currency.py b/src/whop_sdk/types/shared/currency.py index dbed1ad2..43442a3b 100644 --- a/src/whop_sdk/types/shared/currency.py +++ b/src/whop_sdk/types/shared/currency.py @@ -91,4 +91,5 @@ "btc", "cny", "usdt", + "kzt", ] diff --git a/src/whop_sdk/types/shared/membership.py b/src/whop_sdk/types/shared/membership.py index ac7d285c..c2bcea69 100644 --- a/src/whop_sdk/types/shared/membership.py +++ b/src/whop_sdk/types/shared/membership.py @@ -127,6 +127,13 @@ class Membership(BaseModel): Null if the customer did not provide a reason. """ + checkout_configuration_id: Optional[str] = None + """ + The ID of the checkout session/configuration that produced this membership, if + any. Use this to map memberships back to the checkout configuration that created + them. + """ + company: Company """The company this membership belongs to.""" diff --git a/src/whop_sdk/types/shared/payment.py b/src/whop_sdk/types/shared/payment.py index 8a5aa2f3..bc9a6dcb 100644 --- a/src/whop_sdk/types/shared/payment.py +++ b/src/whop_sdk/types/shared/payment.py @@ -397,6 +397,12 @@ class Payment(BaseModel): Null if the payment was not made with a card. """ + checkout_configuration_id: Optional[str] = None + """The ID of the checkout session/configuration that produced this payment, if any. + + Use this to map payments back to the checkout configuration that created them. + """ + company: Optional[Company] = None """The company for the payment.""" @@ -502,6 +508,25 @@ class Payment(BaseModel): otherwise false. Used to decide if Whop can attempt the charge again. """ + settlement_amount: float + """ + The payment amount in the creator's settlement currency (what the creator priced + in). Equal to final_amount for single-currency payments. + """ + + settlement_currency: str + """ + The currency in which the creator receives payouts and fees are charged (e.g., + 'usd', 'eur'). For multi-currency payments this differs from the payment + currency. + """ + + settlement_exchange_rate: Optional[float] = None + """ + The locked exchange rate used to convert from the buyer's payment currency to + the creator's settlement currency. Null for single-currency payments. + """ + status: Optional[ReceiptStatus] = None """The status of a receipt""" diff --git a/src/whop_sdk/types/shared/plan.py b/src/whop_sdk/types/shared/plan.py index a132f587..c603ac95 100644 --- a/src/whop_sdk/types/shared/plan.py +++ b/src/whop_sdk/types/shared/plan.py @@ -112,6 +112,12 @@ class Plan(BaseModel): id: str """The unique identifier for the plan.""" + adaptive_pricing_enabled: bool + """Whether the creator has turned on adaptive pricing for this plan. + + Raw setting — does not check processor compatibility or feature flags. + """ + billing_period: Optional[int] = None """The number of days between each recurring charge. diff --git a/src/whop_sdk/types/shared_params/authorized_user_roles.py b/src/whop_sdk/types/shared_params/authorized_user_roles.py index 2235fde7..9b42c5a9 100644 --- a/src/whop_sdk/types/shared_params/authorized_user_roles.py +++ b/src/whop_sdk/types/shared_params/authorized_user_roles.py @@ -7,5 +7,5 @@ __all__ = ["AuthorizedUserRoles"] AuthorizedUserRoles: TypeAlias = Literal[ - "owner", "admin", "sales_manager", "moderator", "app_manager", "support", "manager" + "owner", "admin", "sales_manager", "moderator", "advertiser", "app_manager", "support", "manager" ] diff --git a/src/whop_sdk/types/shared_params/currency.py b/src/whop_sdk/types/shared_params/currency.py index 4efdb1e2..3d0b58a5 100644 --- a/src/whop_sdk/types/shared_params/currency.py +++ b/src/whop_sdk/types/shared_params/currency.py @@ -93,4 +93,5 @@ "btc", "cny", "usdt", + "kzt", ] diff --git a/src/whop_sdk/types/stat_describe_params.py b/src/whop_sdk/types/stat_describe_params.py new file mode 100644 index 00000000..b97e5979 --- /dev/null +++ b/src/whop_sdk/types/stat_describe_params.py @@ -0,0 +1,22 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Optional +from typing_extensions import TypedDict + +__all__ = ["StatDescribeParams"] + + +class StatDescribeParams(TypedDict, total=False): + company_id: Optional[str] + """Scope query to a specific company.""" + + resource: Optional[str] + """ + Resource path using : as separator (e.g., 'receipts', 'payments:membership', + 'receipts:gross_revenue'). + """ + + user_id: Optional[str] + """Scope query to a specific user.""" diff --git a/src/whop_sdk/types/stat_describe_response.py b/src/whop_sdk/types/stat_describe_response.py new file mode 100644 index 00000000..029b5b30 --- /dev/null +++ b/src/whop_sdk/types/stat_describe_response.py @@ -0,0 +1,256 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Dict, List, Union, Optional +from typing_extensions import Literal, Annotated, TypeAlias + +from .._utils import PropertyInfo +from .._models import BaseModel + +__all__ = [ + "StatDescribeResponse", + "DescribeRoot", + "DescribeRootDebug", + "DescribeRootMetric", + "DescribeNode", + "DescribeNodeAssociation", + "DescribeNodeDebug", + "DescribeNodeMetric", + "DescribeMetric", + "DescribeMetricDebug", + "DescribeView", + "DescribeViewAssociation", + "DescribeViewDebug", + "DescribeViewMetric", +] + + +class DescribeRootDebug(BaseModel): + """Debug information.""" + + request_id: Optional[str] = None + """Unique request identifier for debugging.""" + + +class DescribeRootMetric(BaseModel): + """A metric available for querying.""" + + name: str + """The metric name.""" + + node_path: str + """The node path this metric operates on.""" + + supported_engines: List[str] + """Query engines that support this metric.""" + + +class DescribeRoot(BaseModel): + """Root schema description showing available nodes, views, and metrics.""" + + debug: Optional[DescribeRootDebug] = None + """Debug information.""" + + metrics: List[DescribeRootMetric] + """Available metrics.""" + + nodes: List[str] + """Available root nodes.""" + + typename: Literal["DescribeRoot"] + """The typename of this object""" + + views: List[str] + """Available API resource views.""" + + +class DescribeNodeAssociation(BaseModel): + """An association or child path available for navigation.""" + + event_name: Optional[str] = None + """The event name (for event type).""" + + model: Optional[str] = None + """The associated model class name (for model associations).""" + + name: str + """The association name.""" + + path: Optional[str] = None + """The full path (for event associations).""" + + type: str + """The type (belongs_to, has_many, has_one, event, namespace).""" + + +class DescribeNodeDebug(BaseModel): + """Debug information.""" + + request_id: Optional[str] = None + """Unique request identifier for debugging.""" + + +class DescribeNodeMetric(BaseModel): + """A metric available for querying.""" + + name: str + """The metric name.""" + + node_path: str + """The node path this metric operates on.""" + + supported_engines: List[str] + """Query engines that support this metric.""" + + +class DescribeNode(BaseModel): + """Description of a node (model) including its columns and associations.""" + + associations: List[DescribeNodeAssociation] + """Available associations or child paths.""" + + columns: List[str] + """Available columns.""" + + debug: Optional[DescribeNodeDebug] = None + """Debug information.""" + + engine: str + """The query engine being used.""" + + metrics: List[DescribeNodeMetric] + """Available metrics for this node.""" + + node: str + """The node path being described.""" + + sample: Optional[List[Dict[str, object]]] = None + """Sample data rows.""" + + sortable_columns: List[str] + """Columns that can be used for sorting.""" + + typename: Literal["DescribeNode"] + """The typename of this object""" + + +class DescribeMetricDebug(BaseModel): + """Debug information.""" + + request_id: Optional[str] = None + """Unique request identifier for debugging.""" + + +class DescribeMetric(BaseModel): + """Description of a metric including its configuration and SQL.""" + + breakdownable_columns: List[str] + """Columns that can be used for breakdowns.""" + + debug: Optional[DescribeMetricDebug] = None + """Debug information.""" + + engine: str + """The query engine being used.""" + + filterable_columns: List[str] + """Columns that can be used for filtering.""" + + metric: str + """The metric name.""" + + node: str + """The node path this metric operates on.""" + + sql: Optional[str] = None + """The generated SQL query.""" + + supported_engines: List[str] + """Query engines that support this metric.""" + + timestamp_column: str + """The timestamp column used for time filtering.""" + + typename: Literal["DescribeMetric"] + """The typename of this object""" + + +class DescribeViewAssociation(BaseModel): + """An association or child path available for navigation.""" + + event_name: Optional[str] = None + """The event name (for event type).""" + + model: Optional[str] = None + """The associated model class name (for model associations).""" + + name: str + """The association name.""" + + path: Optional[str] = None + """The full path (for event associations).""" + + type: str + """The type (belongs_to, has_many, has_one, event, namespace).""" + + +class DescribeViewDebug(BaseModel): + """Debug information.""" + + request_id: Optional[str] = None + """Unique request identifier for debugging.""" + + +class DescribeViewMetric(BaseModel): + """A metric available for querying.""" + + name: str + """The metric name.""" + + node_path: str + """The node path this metric operates on.""" + + supported_engines: List[str] + """Query engines that support this metric.""" + + +class DescribeView(BaseModel): + """Description of an API resource view including its columns and associations.""" + + associations: List[DescribeViewAssociation] + """Available associations.""" + + columns: List[str] + """Available columns.""" + + debug: Optional[DescribeViewDebug] = None + """Debug information.""" + + engine: str + """The query engine being used.""" + + metrics: List[DescribeViewMetric] + """Available metrics.""" + + model: str + """The underlying model class.""" + + resource: str + """The API resource name.""" + + sample: Optional[List[Dict[str, object]]] = None + """Sample data rows.""" + + sortable_columns: List[str] + """Columns that can be used for sorting.""" + + typename: Literal["DescribeView"] + """The typename of this object""" + + view: str + """The view name being described.""" + + +StatDescribeResponse: TypeAlias = Annotated[ + Union[Optional[DescribeRoot], Optional[DescribeNode], Optional[DescribeMetric], Optional[DescribeView]], + PropertyInfo(discriminator="typename"), +] diff --git a/src/whop_sdk/types/stat_query_metric_params.py b/src/whop_sdk/types/stat_query_metric_params.py new file mode 100644 index 00000000..0e77b1aa --- /dev/null +++ b/src/whop_sdk/types/stat_query_metric_params.py @@ -0,0 +1,47 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Dict, Union, Optional +from datetime import datetime +from typing_extensions import Required, Annotated, TypedDict + +from .._types import SequenceNotStr +from .._utils import PropertyInfo + +__all__ = ["StatQueryMetricParams"] + + +class StatQueryMetricParams(TypedDict, total=False): + resource: Required[str] + """ + Metric resource using : as separator (e.g., 'receipts:gross_revenue', + 'members:new_users'). + """ + + breakdowns: Optional[SequenceNotStr[str]] + """Columns to break down the metric by.""" + + company_id: Optional[str] + """Scope query to a specific company.""" + + filters: Optional[Dict[str, object]] + """Key-value pairs to filter the data.""" + + from_: Annotated[Union[str, datetime, None], PropertyInfo(alias="from", format="iso8601")] + """Start of time range (unix timestamp).""" + + granularity: Optional[str] + """Time granularity (daily, weekly, monthly).""" + + time_zone: Optional[str] + """IANA timezone for period bucketing (e.g. + + 'America/New_York'). Defaults to UTC. Only applies to ClickHouse metrics. + """ + + to: Annotated[Union[str, datetime, None], PropertyInfo(format="iso8601")] + """End of time range (unix timestamp).""" + + user_id: Optional[str] + """Scope query to a specific user.""" diff --git a/src/whop_sdk/types/stat_query_metric_response.py b/src/whop_sdk/types/stat_query_metric_response.py new file mode 100644 index 00000000..c9879203 --- /dev/null +++ b/src/whop_sdk/types/stat_query_metric_response.py @@ -0,0 +1,53 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Dict, List, Optional +from typing_extensions import Literal + +from .._models import BaseModel + +__all__ = ["StatQueryMetricResponse", "Debug", "Pagination"] + + +class Debug(BaseModel): + """Debug information including engine and SQL.""" + + engine: Optional[str] = None + """The query engine used.""" + + request_id: Optional[str] = None + """Unique request identifier for debugging.""" + + sql: Optional[str] = None + """The generated SQL query (with IDs sanitized).""" + + +class Pagination(BaseModel): + """Pagination information.""" + + next_cursor: Optional[str] = None + """Cursor for the next page of results.""" + + +class StatQueryMetricResponse(BaseModel): + """Result from a stats query (raw, metric, or SQL).""" + + columns: Optional[List[str]] = None + """Column names in the order they appear in each data row.""" + + data: Optional[List[Dict[str, object]]] = None + """ + Array of data rows, where each row is an array of values matching the columns + order. + """ + + debug: Optional[Debug] = None + """Debug information including engine and SQL.""" + + node: Optional[str] = None + """The node path that was queried.""" + + pagination: Optional[Pagination] = None + """Pagination information.""" + + typename: Literal["Result"] + """The typename of this object""" diff --git a/src/whop_sdk/types/stat_query_raw_params.py b/src/whop_sdk/types/stat_query_raw_params.py new file mode 100644 index 00000000..f9296c4e --- /dev/null +++ b/src/whop_sdk/types/stat_query_raw_params.py @@ -0,0 +1,41 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Union, Optional +from datetime import datetime +from typing_extensions import Required, Annotated, TypedDict + +from .._utils import PropertyInfo +from .shared.direction import Direction + +__all__ = ["StatQueryRawParams"] + + +class StatQueryRawParams(TypedDict, total=False): + resource: Required[str] + """Resource path using : as separator (e.g., 'members', 'payments:membership').""" + + company_id: Optional[str] + """Scope query to a specific company.""" + + cursor: Optional[str] + """Pagination cursor for next page.""" + + from_: Annotated[Union[str, datetime, None], PropertyInfo(alias="from", format="iso8601")] + """Start of time range (unix timestamp).""" + + limit: Optional[int] + """Number of records to return (max 10000).""" + + sort: Optional[str] + """Column to sort by.""" + + sort_direction: Optional[Direction] + """The direction of the sort.""" + + to: Annotated[Union[str, datetime, None], PropertyInfo(format="iso8601")] + """End of time range (unix timestamp).""" + + user_id: Optional[str] + """Scope query to a specific user.""" diff --git a/src/whop_sdk/types/stat_query_raw_response.py b/src/whop_sdk/types/stat_query_raw_response.py new file mode 100644 index 00000000..19879532 --- /dev/null +++ b/src/whop_sdk/types/stat_query_raw_response.py @@ -0,0 +1,49 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Dict, List, Optional + +from .._models import BaseModel + +__all__ = ["StatQueryRawResponse", "Debug", "Pagination"] + + +class Debug(BaseModel): + """Debug information including engine and SQL.""" + + engine: Optional[str] = None + """The query engine used.""" + + request_id: Optional[str] = None + """Unique request identifier for debugging.""" + + sql: Optional[str] = None + """The generated SQL query (with IDs sanitized).""" + + +class Pagination(BaseModel): + """Pagination information.""" + + next_cursor: Optional[str] = None + """Cursor for the next page of results.""" + + +class StatQueryRawResponse(BaseModel): + """Result from a stats query (raw, metric, or SQL).""" + + columns: Optional[List[str]] = None + """Column names in the order they appear in each data row.""" + + data: Optional[List[Dict[str, object]]] = None + """ + Array of data rows, where each row is an array of values matching the columns + order. + """ + + debug: Optional[Debug] = None + """Debug information including engine and SQL.""" + + node: Optional[str] = None + """The node path that was queried.""" + + pagination: Optional[Pagination] = None + """Pagination information.""" diff --git a/src/whop_sdk/types/stat_run_sql_params.py b/src/whop_sdk/types/stat_run_sql_params.py new file mode 100644 index 00000000..f06046d7 --- /dev/null +++ b/src/whop_sdk/types/stat_run_sql_params.py @@ -0,0 +1,44 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Union, Optional +from datetime import datetime +from typing_extensions import Required, Annotated, TypedDict + +from .._utils import PropertyInfo +from .shared.direction import Direction + +__all__ = ["StatRunSqlParams"] + + +class StatRunSqlParams(TypedDict, total=False): + resource: Required[str] + """Resource path using : as separator (e.g., 'receipts', 'payments:membership').""" + + sql: Required[str] + """SQL query. Use SCOPED_DATA as the table name.""" + + company_id: Optional[str] + """Scope query to a specific company.""" + + cursor: Optional[str] + """Pagination cursor for next page.""" + + from_: Annotated[Union[str, datetime, None], PropertyInfo(alias="from", format="iso8601")] + """Start of time range (unix timestamp).""" + + limit: Optional[int] + """Number of records to return (max 10000).""" + + sort: Optional[str] + """Column to sort by.""" + + sort_direction: Optional[Direction] + """The direction of the sort.""" + + to: Annotated[Union[str, datetime, None], PropertyInfo(format="iso8601")] + """End of time range (unix timestamp).""" + + user_id: Optional[str] + """Scope query to a specific user.""" diff --git a/src/whop_sdk/types/stat_run_sql_response.py b/src/whop_sdk/types/stat_run_sql_response.py new file mode 100644 index 00000000..d0f46683 --- /dev/null +++ b/src/whop_sdk/types/stat_run_sql_response.py @@ -0,0 +1,53 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Dict, List, Optional +from typing_extensions import Literal + +from .._models import BaseModel + +__all__ = ["StatRunSqlResponse", "Debug", "Pagination"] + + +class Debug(BaseModel): + """Debug information including engine and SQL.""" + + engine: Optional[str] = None + """The query engine used.""" + + request_id: Optional[str] = None + """Unique request identifier for debugging.""" + + sql: Optional[str] = None + """The generated SQL query (with IDs sanitized).""" + + +class Pagination(BaseModel): + """Pagination information.""" + + next_cursor: Optional[str] = None + """Cursor for the next page of results.""" + + +class StatRunSqlResponse(BaseModel): + """Result from a stats query (raw, metric, or SQL).""" + + columns: Optional[List[str]] = None + """Column names in the order they appear in each data row.""" + + data: Optional[List[Dict[str, object]]] = None + """ + Array of data rows, where each row is an array of values matching the columns + order. + """ + + debug: Optional[Debug] = None + """Debug information including engine and SQL.""" + + node: Optional[str] = None + """The node path that was queried.""" + + pagination: Optional[Pagination] = None + """Pagination information.""" + + typename: Literal["Result"] + """The typename of this object""" diff --git a/src/whop_sdk/types/support_channel_list_params.py b/src/whop_sdk/types/support_channel_list_params.py index 46b2300d..fb189f00 100644 --- a/src/whop_sdk/types/support_channel_list_params.py +++ b/src/whop_sdk/types/support_channel_list_params.py @@ -3,7 +3,7 @@ from __future__ import annotations from typing import Optional -from typing_extensions import Literal, Required, TypedDict +from typing_extensions import Literal, TypedDict from .shared.direction import Direction @@ -11,15 +11,19 @@ class SupportChannelListParams(TypedDict, total=False): - company_id: Required[str] - """The unique identifier of the company to list support channels for.""" - after: Optional[str] """Returns the elements in the list that come after the specified cursor.""" before: Optional[str] """Returns the elements in the list that come before the specified cursor.""" + company_id: Optional[str] + """The unique identifier of the company to list support channels for. + + Includes channels of child companies. When omitted, returns support channels + across all companies the user has access to. + """ + direction: Optional[Direction] """The direction of the sort.""" @@ -38,3 +42,6 @@ class SupportChannelListParams(TypedDict, total=False): order: Optional[Literal["created_at", "last_post_sent_at"]] """Sort options for message channels""" + + view: Optional[Literal["all", "admin", "customer"]] + """The perspective to filter support channels by.""" diff --git a/tests/api_resources/test_ad_campaigns.py b/tests/api_resources/test_ad_campaigns.py new file mode 100644 index 00000000..d609a91a --- /dev/null +++ b/tests/api_resources/test_ad_campaigns.py @@ -0,0 +1,661 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import os +from typing import Any, cast + +import pytest + +from whop_sdk import Whop, AsyncWhop +from tests.utils import assert_matches_type +from whop_sdk.types import ( + AdCampaignListResponse, + AdCampaignPauseResponse, + AdCampaignCreateResponse, + AdCampaignUpdateResponse, + AdCampaignUnpauseResponse, + AdCampaignRetrieveResponse, +) +from whop_sdk._utils import parse_datetime +from whop_sdk.pagination import SyncCursorPage, AsyncCursorPage + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestAdCampaigns: + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_create(self, client: Whop) -> None: + ad_campaign = client.ad_campaigns.create( + company_id="biz_xxxxxxxxxxxxxx", + config={}, + platform="meta", + title="title", + ) + assert_matches_type(AdCampaignCreateResponse, ad_campaign, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_create_with_all_params(self, client: Whop) -> None: + ad_campaign = client.ad_campaigns.create( + company_id="biz_xxxxxxxxxxxxxx", + config={ + "bid_amount": 42, + "bid_strategy": "lowest_cost", + "budget_optimization": True, + "end_time": "end_time", + "lifetime_budget": 42, + "objective": "awareness", + "special_categories": ["string"], + "start_time": "start_time", + "status": "active", + }, + platform="meta", + title="title", + ad_creative_set_ids=["string"], + budget=6.9, + budget_type="daily", + daily_budget=6.9, + product_id="prod_xxxxxxxxxxxxx", + target_country_codes=["string"], + ) + assert_matches_type(AdCampaignCreateResponse, ad_campaign, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_raw_response_create(self, client: Whop) -> None: + response = client.ad_campaigns.with_raw_response.create( + company_id="biz_xxxxxxxxxxxxxx", + config={}, + platform="meta", + title="title", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + ad_campaign = response.parse() + assert_matches_type(AdCampaignCreateResponse, ad_campaign, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_streaming_response_create(self, client: Whop) -> None: + with client.ad_campaigns.with_streaming_response.create( + company_id="biz_xxxxxxxxxxxxxx", + config={}, + platform="meta", + title="title", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + ad_campaign = response.parse() + assert_matches_type(AdCampaignCreateResponse, ad_campaign, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_retrieve(self, client: Whop) -> None: + ad_campaign = client.ad_campaigns.retrieve( + "adcamp_xxxxxxxxxxx", + ) + assert_matches_type(AdCampaignRetrieveResponse, ad_campaign, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_raw_response_retrieve(self, client: Whop) -> None: + response = client.ad_campaigns.with_raw_response.retrieve( + "adcamp_xxxxxxxxxxx", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + ad_campaign = response.parse() + assert_matches_type(AdCampaignRetrieveResponse, ad_campaign, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_streaming_response_retrieve(self, client: Whop) -> None: + with client.ad_campaigns.with_streaming_response.retrieve( + "adcamp_xxxxxxxxxxx", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + ad_campaign = response.parse() + assert_matches_type(AdCampaignRetrieveResponse, ad_campaign, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_path_params_retrieve(self, client: Whop) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + client.ad_campaigns.with_raw_response.retrieve( + "", + ) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_update(self, client: Whop) -> None: + ad_campaign = client.ad_campaigns.update( + id="adcamp_xxxxxxxxxxx", + ) + assert_matches_type(AdCampaignUpdateResponse, ad_campaign, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_update_with_all_params(self, client: Whop) -> None: + ad_campaign = client.ad_campaigns.update( + id="adcamp_xxxxxxxxxxx", + ad_creative_set_ids=["string"], + budget=6.9, + budget_type="daily", + config={ + "bid_amount": 42, + "bid_strategy": "lowest_cost", + "budget_optimization": True, + "end_time": "end_time", + "lifetime_budget": 42, + "objective": "awareness", + "special_categories": ["string"], + "start_time": "start_time", + "status": "active", + }, + daily_budget=6.9, + product_id="prod_xxxxxxxxxxxxx", + target_country_codes=["string"], + title="title", + ) + assert_matches_type(AdCampaignUpdateResponse, ad_campaign, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_raw_response_update(self, client: Whop) -> None: + response = client.ad_campaigns.with_raw_response.update( + id="adcamp_xxxxxxxxxxx", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + ad_campaign = response.parse() + assert_matches_type(AdCampaignUpdateResponse, ad_campaign, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_streaming_response_update(self, client: Whop) -> None: + with client.ad_campaigns.with_streaming_response.update( + id="adcamp_xxxxxxxxxxx", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + ad_campaign = response.parse() + assert_matches_type(AdCampaignUpdateResponse, ad_campaign, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_path_params_update(self, client: Whop) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + client.ad_campaigns.with_raw_response.update( + id="", + ) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_list(self, client: Whop) -> None: + ad_campaign = client.ad_campaigns.list( + company_id="biz_xxxxxxxxxxxxxx", + ) + assert_matches_type(SyncCursorPage[AdCampaignListResponse], ad_campaign, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_list_with_all_params(self, client: Whop) -> None: + ad_campaign = client.ad_campaigns.list( + company_id="biz_xxxxxxxxxxxxxx", + after="after", + before="before", + created_after=parse_datetime("2023-12-01T05:00:00.401Z"), + created_before=parse_datetime("2023-12-01T05:00:00.401Z"), + first=42, + last=42, + query="query", + status="active", + ) + assert_matches_type(SyncCursorPage[AdCampaignListResponse], ad_campaign, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_raw_response_list(self, client: Whop) -> None: + response = client.ad_campaigns.with_raw_response.list( + company_id="biz_xxxxxxxxxxxxxx", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + ad_campaign = response.parse() + assert_matches_type(SyncCursorPage[AdCampaignListResponse], ad_campaign, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_streaming_response_list(self, client: Whop) -> None: + with client.ad_campaigns.with_streaming_response.list( + company_id="biz_xxxxxxxxxxxxxx", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + ad_campaign = response.parse() + assert_matches_type(SyncCursorPage[AdCampaignListResponse], ad_campaign, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_pause(self, client: Whop) -> None: + ad_campaign = client.ad_campaigns.pause( + "adcamp_xxxxxxxxxxx", + ) + assert_matches_type(AdCampaignPauseResponse, ad_campaign, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_raw_response_pause(self, client: Whop) -> None: + response = client.ad_campaigns.with_raw_response.pause( + "adcamp_xxxxxxxxxxx", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + ad_campaign = response.parse() + assert_matches_type(AdCampaignPauseResponse, ad_campaign, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_streaming_response_pause(self, client: Whop) -> None: + with client.ad_campaigns.with_streaming_response.pause( + "adcamp_xxxxxxxxxxx", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + ad_campaign = response.parse() + assert_matches_type(AdCampaignPauseResponse, ad_campaign, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_path_params_pause(self, client: Whop) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + client.ad_campaigns.with_raw_response.pause( + "", + ) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_unpause(self, client: Whop) -> None: + ad_campaign = client.ad_campaigns.unpause( + "adcamp_xxxxxxxxxxx", + ) + assert_matches_type(AdCampaignUnpauseResponse, ad_campaign, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_raw_response_unpause(self, client: Whop) -> None: + response = client.ad_campaigns.with_raw_response.unpause( + "adcamp_xxxxxxxxxxx", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + ad_campaign = response.parse() + assert_matches_type(AdCampaignUnpauseResponse, ad_campaign, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_streaming_response_unpause(self, client: Whop) -> None: + with client.ad_campaigns.with_streaming_response.unpause( + "adcamp_xxxxxxxxxxx", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + ad_campaign = response.parse() + assert_matches_type(AdCampaignUnpauseResponse, ad_campaign, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_path_params_unpause(self, client: Whop) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + client.ad_campaigns.with_raw_response.unpause( + "", + ) + + +class TestAsyncAdCampaigns: + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_create(self, async_client: AsyncWhop) -> None: + ad_campaign = await async_client.ad_campaigns.create( + company_id="biz_xxxxxxxxxxxxxx", + config={}, + platform="meta", + title="title", + ) + assert_matches_type(AdCampaignCreateResponse, ad_campaign, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_create_with_all_params(self, async_client: AsyncWhop) -> None: + ad_campaign = await async_client.ad_campaigns.create( + company_id="biz_xxxxxxxxxxxxxx", + config={ + "bid_amount": 42, + "bid_strategy": "lowest_cost", + "budget_optimization": True, + "end_time": "end_time", + "lifetime_budget": 42, + "objective": "awareness", + "special_categories": ["string"], + "start_time": "start_time", + "status": "active", + }, + platform="meta", + title="title", + ad_creative_set_ids=["string"], + budget=6.9, + budget_type="daily", + daily_budget=6.9, + product_id="prod_xxxxxxxxxxxxx", + target_country_codes=["string"], + ) + assert_matches_type(AdCampaignCreateResponse, ad_campaign, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_raw_response_create(self, async_client: AsyncWhop) -> None: + response = await async_client.ad_campaigns.with_raw_response.create( + company_id="biz_xxxxxxxxxxxxxx", + config={}, + platform="meta", + title="title", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + ad_campaign = await response.parse() + assert_matches_type(AdCampaignCreateResponse, ad_campaign, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_streaming_response_create(self, async_client: AsyncWhop) -> None: + async with async_client.ad_campaigns.with_streaming_response.create( + company_id="biz_xxxxxxxxxxxxxx", + config={}, + platform="meta", + title="title", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + ad_campaign = await response.parse() + assert_matches_type(AdCampaignCreateResponse, ad_campaign, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_retrieve(self, async_client: AsyncWhop) -> None: + ad_campaign = await async_client.ad_campaigns.retrieve( + "adcamp_xxxxxxxxxxx", + ) + assert_matches_type(AdCampaignRetrieveResponse, ad_campaign, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_raw_response_retrieve(self, async_client: AsyncWhop) -> None: + response = await async_client.ad_campaigns.with_raw_response.retrieve( + "adcamp_xxxxxxxxxxx", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + ad_campaign = await response.parse() + assert_matches_type(AdCampaignRetrieveResponse, ad_campaign, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_streaming_response_retrieve(self, async_client: AsyncWhop) -> None: + async with async_client.ad_campaigns.with_streaming_response.retrieve( + "adcamp_xxxxxxxxxxx", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + ad_campaign = await response.parse() + assert_matches_type(AdCampaignRetrieveResponse, ad_campaign, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_path_params_retrieve(self, async_client: AsyncWhop) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + await async_client.ad_campaigns.with_raw_response.retrieve( + "", + ) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_update(self, async_client: AsyncWhop) -> None: + ad_campaign = await async_client.ad_campaigns.update( + id="adcamp_xxxxxxxxxxx", + ) + assert_matches_type(AdCampaignUpdateResponse, ad_campaign, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_update_with_all_params(self, async_client: AsyncWhop) -> None: + ad_campaign = await async_client.ad_campaigns.update( + id="adcamp_xxxxxxxxxxx", + ad_creative_set_ids=["string"], + budget=6.9, + budget_type="daily", + config={ + "bid_amount": 42, + "bid_strategy": "lowest_cost", + "budget_optimization": True, + "end_time": "end_time", + "lifetime_budget": 42, + "objective": "awareness", + "special_categories": ["string"], + "start_time": "start_time", + "status": "active", + }, + daily_budget=6.9, + product_id="prod_xxxxxxxxxxxxx", + target_country_codes=["string"], + title="title", + ) + assert_matches_type(AdCampaignUpdateResponse, ad_campaign, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_raw_response_update(self, async_client: AsyncWhop) -> None: + response = await async_client.ad_campaigns.with_raw_response.update( + id="adcamp_xxxxxxxxxxx", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + ad_campaign = await response.parse() + assert_matches_type(AdCampaignUpdateResponse, ad_campaign, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_streaming_response_update(self, async_client: AsyncWhop) -> None: + async with async_client.ad_campaigns.with_streaming_response.update( + id="adcamp_xxxxxxxxxxx", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + ad_campaign = await response.parse() + assert_matches_type(AdCampaignUpdateResponse, ad_campaign, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_path_params_update(self, async_client: AsyncWhop) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + await async_client.ad_campaigns.with_raw_response.update( + id="", + ) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_list(self, async_client: AsyncWhop) -> None: + ad_campaign = await async_client.ad_campaigns.list( + company_id="biz_xxxxxxxxxxxxxx", + ) + assert_matches_type(AsyncCursorPage[AdCampaignListResponse], ad_campaign, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_list_with_all_params(self, async_client: AsyncWhop) -> None: + ad_campaign = await async_client.ad_campaigns.list( + company_id="biz_xxxxxxxxxxxxxx", + after="after", + before="before", + created_after=parse_datetime("2023-12-01T05:00:00.401Z"), + created_before=parse_datetime("2023-12-01T05:00:00.401Z"), + first=42, + last=42, + query="query", + status="active", + ) + assert_matches_type(AsyncCursorPage[AdCampaignListResponse], ad_campaign, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_raw_response_list(self, async_client: AsyncWhop) -> None: + response = await async_client.ad_campaigns.with_raw_response.list( + company_id="biz_xxxxxxxxxxxxxx", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + ad_campaign = await response.parse() + assert_matches_type(AsyncCursorPage[AdCampaignListResponse], ad_campaign, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_streaming_response_list(self, async_client: AsyncWhop) -> None: + async with async_client.ad_campaigns.with_streaming_response.list( + company_id="biz_xxxxxxxxxxxxxx", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + ad_campaign = await response.parse() + assert_matches_type(AsyncCursorPage[AdCampaignListResponse], ad_campaign, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_pause(self, async_client: AsyncWhop) -> None: + ad_campaign = await async_client.ad_campaigns.pause( + "adcamp_xxxxxxxxxxx", + ) + assert_matches_type(AdCampaignPauseResponse, ad_campaign, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_raw_response_pause(self, async_client: AsyncWhop) -> None: + response = await async_client.ad_campaigns.with_raw_response.pause( + "adcamp_xxxxxxxxxxx", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + ad_campaign = await response.parse() + assert_matches_type(AdCampaignPauseResponse, ad_campaign, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_streaming_response_pause(self, async_client: AsyncWhop) -> None: + async with async_client.ad_campaigns.with_streaming_response.pause( + "adcamp_xxxxxxxxxxx", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + ad_campaign = await response.parse() + assert_matches_type(AdCampaignPauseResponse, ad_campaign, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_path_params_pause(self, async_client: AsyncWhop) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + await async_client.ad_campaigns.with_raw_response.pause( + "", + ) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_unpause(self, async_client: AsyncWhop) -> None: + ad_campaign = await async_client.ad_campaigns.unpause( + "adcamp_xxxxxxxxxxx", + ) + assert_matches_type(AdCampaignUnpauseResponse, ad_campaign, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_raw_response_unpause(self, async_client: AsyncWhop) -> None: + response = await async_client.ad_campaigns.with_raw_response.unpause( + "adcamp_xxxxxxxxxxx", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + ad_campaign = await response.parse() + assert_matches_type(AdCampaignUnpauseResponse, ad_campaign, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_streaming_response_unpause(self, async_client: AsyncWhop) -> None: + async with async_client.ad_campaigns.with_streaming_response.unpause( + "adcamp_xxxxxxxxxxx", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + ad_campaign = await response.parse() + assert_matches_type(AdCampaignUnpauseResponse, ad_campaign, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_path_params_unpause(self, async_client: AsyncWhop) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + await async_client.ad_campaigns.with_raw_response.unpause( + "", + ) diff --git a/tests/api_resources/test_ad_groups.py b/tests/api_resources/test_ad_groups.py new file mode 100644 index 00000000..dbe0dcc2 --- /dev/null +++ b/tests/api_resources/test_ad_groups.py @@ -0,0 +1,1838 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import os +from typing import Any, cast + +import pytest + +from whop_sdk import Whop, AsyncWhop +from tests.utils import assert_matches_type +from whop_sdk.types import ( + AdGroupListResponse, + AdGroupCreateResponse, + AdGroupDeleteResponse, + AdGroupUpdateResponse, + AdGroupRetrieveResponse, +) +from whop_sdk._utils import parse_datetime +from whop_sdk.pagination import SyncCursorPage, AsyncCursorPage + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestAdGroups: + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_create(self, client: Whop) -> None: + ad_group = client.ad_groups.create( + campaign_id="campaign_id", + ) + assert_matches_type(AdGroupCreateResponse, ad_group, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_create_with_all_params(self, client: Whop) -> None: + ad_group = client.ad_groups.create( + campaign_id="campaign_id", + budget=6.9, + budget_type="daily", + config={ + "bid_amount": 42, + "bid_strategy": "lowest_cost", + "billing_event": "impressions", + "end_time": "end_time", + "frequency_cap": 42, + "frequency_cap_interval_days": 42, + "optimization_goal": "conversions", + "pacing": "standard", + "start_time": "start_time", + "targeting": { + "age_max": 42, + "age_min": 42, + "countries": ["string"], + "device_platforms": ["mobile"], + "exclude_audience_ids": ["string"], + "genders": ["male"], + "include_audience_ids": ["string"], + "interest_ids": ["string"], + "languages": ["string"], + "placement_type": "automatic", + }, + }, + daily_budget=6.9, + name="name", + platform_config={ + "meta": { + "android_devices": ["string"], + "attribution_setting": "attribution_setting", + "attribution_spec": [ + { + "event_type": "event_type", + "window_days": 42, + } + ], + "audience_network_positions": ["string"], + "audience_type": "audience_type", + "bid_amount": 42, + "bid_strategy": "LOWEST_COST_WITHOUT_CAP", + "billing_event": "APP_INSTALLS", + "brand_safety_content_filter_levels": ["string"], + "budget_remaining": "budget_remaining", + "cost_per_result_goal": 6.9, + "created_time": "created_time", + "daily_budget": 42, + "daily_min_spend_target": "daily_min_spend_target", + "daily_spend_cap": "daily_spend_cap", + "destination_type": "UNDEFINED", + "dsa_beneficiary": "dsa_beneficiary", + "dsa_payor": "dsa_payor", + "end_time": "end_time", + "excluded_geo_locations": { + "cities": [ + { + "key": "key", + "country": "country", + "name": "name", + "radius": 42, + } + ], + "countries": ["string"], + "location_types": ["string"], + "regions": [ + { + "key": "key", + "country": "country", + "name": "name", + "radius": 42, + } + ], + "zips": [ + { + "key": "key", + "country": "country", + "name": "name", + "radius": 42, + } + ], + }, + "facebook_positions": ["string"], + "frequency_control_count": 42, + "frequency_control_days": 42, + "frequency_control_type": "frequency_control_type", + "geo_cities": [ + { + "key": "key", + "country": "country", + "name": "name", + "radius": 42, + } + ], + "geo_locations": { + "cities": [ + { + "key": "key", + "country": "country", + "name": "name", + "radius": 42, + } + ], + "countries": ["string"], + "location_types": ["string"], + "regions": [ + { + "key": "key", + "country": "country", + "name": "name", + "radius": 42, + } + ], + "zips": [ + { + "key": "key", + "country": "country", + "name": "name", + "radius": 42, + } + ], + }, + "geo_regions": [ + { + "key": "key", + "country": "country", + "name": "name", + "radius": 42, + } + ], + "geo_zips": ["string"], + "instagram_actor_id": "instagram_actor_id", + "instagram_positions": ["string"], + "ios_devices": ["string"], + "is_dynamic_creative": True, + "lead_conversion_location": "website", + "lead_form_config": { + "name": "name", + "privacy_policy_url": "privacy_policy_url", + "questions": [ + { + "type": "type", + "conditional_questions_group_id": "conditional_questions_group_id", + "dependent_conditional_questions": [ + { + "type": "type", + "inline_context": "inline_context", + "key": "key", + "label": "label", + "options": [ + { + "key": "key", + "value": "value", + "logic": { + "type": "type", + "target_end_page_index": 42, + "target_question_index": 42, + }, + } + ], + } + ], + "inline_context": "inline_context", + "key": "key", + "label": "label", + "options": [ + { + "key": "key", + "value": "value", + "logic": { + "type": "type", + "target_end_page_index": 42, + "target_question_index": 42, + }, + } + ], + "question_format": "question_format", + } + ], + "background_image_source": "background_image_source", + "background_image_url": "background_image_url", + "conditional_logic_enabled": True, + "context_card_button_text": "context_card_button_text", + "context_card_content": ["string"], + "context_card_style": "context_card_style", + "context_card_title": "context_card_title", + "custom_disclaimer_body": "custom_disclaimer_body", + "custom_disclaimer_checkboxes": [ + { + "key": "key", + "text": "text", + "is_checked_by_default": True, + "is_required": True, + } + ], + "custom_disclaimer_title": "custom_disclaimer_title", + "form_type": "form_type", + "messenger_enabled": True, + "phone_verification_enabled": True, + "privacy_policy_link_text": "privacy_policy_link_text", + "question_page_custom_headline": "question_page_custom_headline", + "rich_creative_headline": "rich_creative_headline", + "rich_creative_overview": "rich_creative_overview", + "rich_creative_url": "rich_creative_url", + "thank_you_pages": [ + { + "body": "body", + "business_phone": "business_phone", + "button_text": "button_text", + "button_type": "button_type", + "conditional_question_group_id": "conditional_question_group_id", + "enable_messenger": True, + "gated_file_url": "gated_file_url", + "link": "link", + "name": "name", + "title": "title", + } + ], + }, + "lead_gen_form_id": "lead_gen_form_id", + "lifetime_budget": 42, + "lifetime_min_spend_target": "lifetime_min_spend_target", + "lifetime_spend_cap": "lifetime_spend_cap", + "location_types": ["string"], + "messenger_positions": ["string"], + "optimization_goal": "NONE", + "page_id": "page_id", + "pixel_id": "pixel_id", + "promoted_object": { + "custom_conversion_id": "custom_conversion_id", + "custom_event_str": "custom_event_str", + "custom_event_type": "custom_event_type", + "page_id": "page_id", + "pixel_id": "pixel_id", + "whatsapp_phone_number": "whatsapp_phone_number", + }, + "publisher_platforms": ["string"], + "source_adset_id": "source_adset_id", + "start_time": "start_time", + "status": "ACTIVE", + "targeting_automation": {"advantage_audience": 42}, + "threads_positions": ["string"], + "updated_time": "updated_time", + "user_device": ["string"], + "user_os": ["string"], + "whatsapp_phone_number": "whatsapp_phone_number", + "whatsapp_positions": ["string"], + }, + "tiktok": { + "actions": [ + { + "action_category_ids": ["string"], + "action_period": 42, + "action_scene": "VIDEO_RELATED", + "video_user_actions": ["WATCHED_TO_END"], + } + ], + "age_groups": ["AGE_13_17"], + "app_id": "app_xxxxxxxxxxxxxx", + "attribution_event_count": "UNSET", + "audience_ids": ["string"], + "audience_rule": {"foo": "bar"}, + "audience_type": "NORMAL", + "bid_price": 6.9, + "bid_type": "BID_TYPE_NO_BID", + "billing_event": "CPC", + "brand_safety_type": "NO_BRAND_SAFETY", + "budget_mode": "BUDGET_MODE_DAY", + "carrier_ids": ["string"], + "category_exclusion_ids": ["string"], + "click_attribution_window": "OFF", + "comment_disabled": True, + "contextual_tag_ids": ["string"], + "conversion_bid_price": 6.9, + "creative_material_mode": "creative_material_mode", + "dayparting": "dayparting", + "deep_funnel_event_source": "deep_funnel_event_source", + "deep_funnel_event_source_id": "deep_funnel_event_source_id", + "deep_funnel_optimization_status": "ON", + "device_model_ids": ["string"], + "device_price_ranges": ["string"], + "engaged_view_attribution_window": "OFF", + "excluded_audience_ids": ["string"], + "excluded_location_ids": ["string"], + "frequency": 42, + "frequency_schedule": 42, + "gender": "GENDER_UNLIMITED", + "identity_authorized_bc_id": "identity_authorized_bc_id", + "identity_id": "identity_id", + "identity_type": "identity_type", + "instant_form_config": { + "privacy_policy_url": "privacy_policy_url", + "questions": [ + { + "field_type": "field_type", + "label": "label", + } + ], + "button_text": "button_text", + "greeting": "greeting", + "name": "name", + }, + "instant_form_id": "instant_form_id", + "interest_category_ids": ["string"], + "interest_keyword_ids": ["string"], + "inventory_filter_enabled": True, + "ios14_targeting": "UNSET", + "isp_ids": ["string"], + "languages": ["string"], + "location_ids": ["string"], + "min_android_version": "min_android_version", + "min_ios_version": "min_ios_version", + "network_types": ["string"], + "operating_systems": ["ANDROID"], + "operation_status": "ENABLE", + "optimization_event": "optimization_event", + "optimization_goal": "CLICK", + "pacing": "PACING_MODE_SMOOTH", + "pangle_audience_package_exclude_ids": ["string"], + "pangle_audience_package_include_ids": ["string"], + "pangle_block_app_ids": ["string"], + "pixel_id": "pixel_id", + "placement_type": "PLACEMENT_TYPE_AUTOMATIC", + "placements": ["string"], + "product_set_id": "product_set_id", + "product_source": "CATALOG", + "promotion_type": "promotion_type", + "schedule_end_time": "schedule_end_time", + "schedule_start_time": "schedule_start_time", + "schedule_type": "SCHEDULE_START_END", + "secondary_optimization_event": "secondary_optimization_event", + "shopping_ads_retargeting_actions_days": 42, + "shopping_ads_retargeting_type": "OFF", + "spending_power": "ALL", + "tiktok_subplacements": ["string"], + "vertical_sensitivity_id": "vertical_sensitivity_id", + "video_download_disabled": True, + "video_user_actions": ["string"], + "view_attribution_window": "OFF", + }, + }, + status="active", + ) + assert_matches_type(AdGroupCreateResponse, ad_group, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_raw_response_create(self, client: Whop) -> None: + response = client.ad_groups.with_raw_response.create( + campaign_id="campaign_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + ad_group = response.parse() + assert_matches_type(AdGroupCreateResponse, ad_group, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_streaming_response_create(self, client: Whop) -> None: + with client.ad_groups.with_streaming_response.create( + campaign_id="campaign_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + ad_group = response.parse() + assert_matches_type(AdGroupCreateResponse, ad_group, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_retrieve(self, client: Whop) -> None: + ad_group = client.ad_groups.retrieve( + "adgrp_xxxxxxxxxxxx", + ) + assert_matches_type(AdGroupRetrieveResponse, ad_group, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_raw_response_retrieve(self, client: Whop) -> None: + response = client.ad_groups.with_raw_response.retrieve( + "adgrp_xxxxxxxxxxxx", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + ad_group = response.parse() + assert_matches_type(AdGroupRetrieveResponse, ad_group, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_streaming_response_retrieve(self, client: Whop) -> None: + with client.ad_groups.with_streaming_response.retrieve( + "adgrp_xxxxxxxxxxxx", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + ad_group = response.parse() + assert_matches_type(AdGroupRetrieveResponse, ad_group, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_path_params_retrieve(self, client: Whop) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + client.ad_groups.with_raw_response.retrieve( + "", + ) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_update(self, client: Whop) -> None: + ad_group = client.ad_groups.update( + id="adgrp_xxxxxxxxxxxx", + ) + assert_matches_type(AdGroupUpdateResponse, ad_group, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_update_with_all_params(self, client: Whop) -> None: + ad_group = client.ad_groups.update( + id="adgrp_xxxxxxxxxxxx", + budget=6.9, + budget_type="daily", + config={ + "bid_amount": 42, + "bid_strategy": "lowest_cost", + "billing_event": "impressions", + "end_time": "end_time", + "frequency_cap": 42, + "frequency_cap_interval_days": 42, + "optimization_goal": "conversions", + "pacing": "standard", + "start_time": "start_time", + "targeting": { + "age_max": 42, + "age_min": 42, + "countries": ["string"], + "device_platforms": ["mobile"], + "exclude_audience_ids": ["string"], + "genders": ["male"], + "include_audience_ids": ["string"], + "interest_ids": ["string"], + "languages": ["string"], + "placement_type": "automatic", + }, + }, + daily_budget=6.9, + name="name", + platform_config={ + "meta": { + "android_devices": ["string"], + "attribution_setting": "attribution_setting", + "attribution_spec": [ + { + "event_type": "event_type", + "window_days": 42, + } + ], + "audience_network_positions": ["string"], + "audience_type": "audience_type", + "bid_amount": 42, + "bid_strategy": "LOWEST_COST_WITHOUT_CAP", + "billing_event": "APP_INSTALLS", + "brand_safety_content_filter_levels": ["string"], + "budget_remaining": "budget_remaining", + "cost_per_result_goal": 6.9, + "created_time": "created_time", + "daily_budget": 42, + "daily_min_spend_target": "daily_min_spend_target", + "daily_spend_cap": "daily_spend_cap", + "destination_type": "UNDEFINED", + "dsa_beneficiary": "dsa_beneficiary", + "dsa_payor": "dsa_payor", + "end_time": "end_time", + "excluded_geo_locations": { + "cities": [ + { + "key": "key", + "country": "country", + "name": "name", + "radius": 42, + } + ], + "countries": ["string"], + "location_types": ["string"], + "regions": [ + { + "key": "key", + "country": "country", + "name": "name", + "radius": 42, + } + ], + "zips": [ + { + "key": "key", + "country": "country", + "name": "name", + "radius": 42, + } + ], + }, + "facebook_positions": ["string"], + "frequency_control_count": 42, + "frequency_control_days": 42, + "frequency_control_type": "frequency_control_type", + "geo_cities": [ + { + "key": "key", + "country": "country", + "name": "name", + "radius": 42, + } + ], + "geo_locations": { + "cities": [ + { + "key": "key", + "country": "country", + "name": "name", + "radius": 42, + } + ], + "countries": ["string"], + "location_types": ["string"], + "regions": [ + { + "key": "key", + "country": "country", + "name": "name", + "radius": 42, + } + ], + "zips": [ + { + "key": "key", + "country": "country", + "name": "name", + "radius": 42, + } + ], + }, + "geo_regions": [ + { + "key": "key", + "country": "country", + "name": "name", + "radius": 42, + } + ], + "geo_zips": ["string"], + "instagram_actor_id": "instagram_actor_id", + "instagram_positions": ["string"], + "ios_devices": ["string"], + "is_dynamic_creative": True, + "lead_conversion_location": "website", + "lead_form_config": { + "name": "name", + "privacy_policy_url": "privacy_policy_url", + "questions": [ + { + "type": "type", + "conditional_questions_group_id": "conditional_questions_group_id", + "dependent_conditional_questions": [ + { + "type": "type", + "inline_context": "inline_context", + "key": "key", + "label": "label", + "options": [ + { + "key": "key", + "value": "value", + "logic": { + "type": "type", + "target_end_page_index": 42, + "target_question_index": 42, + }, + } + ], + } + ], + "inline_context": "inline_context", + "key": "key", + "label": "label", + "options": [ + { + "key": "key", + "value": "value", + "logic": { + "type": "type", + "target_end_page_index": 42, + "target_question_index": 42, + }, + } + ], + "question_format": "question_format", + } + ], + "background_image_source": "background_image_source", + "background_image_url": "background_image_url", + "conditional_logic_enabled": True, + "context_card_button_text": "context_card_button_text", + "context_card_content": ["string"], + "context_card_style": "context_card_style", + "context_card_title": "context_card_title", + "custom_disclaimer_body": "custom_disclaimer_body", + "custom_disclaimer_checkboxes": [ + { + "key": "key", + "text": "text", + "is_checked_by_default": True, + "is_required": True, + } + ], + "custom_disclaimer_title": "custom_disclaimer_title", + "form_type": "form_type", + "messenger_enabled": True, + "phone_verification_enabled": True, + "privacy_policy_link_text": "privacy_policy_link_text", + "question_page_custom_headline": "question_page_custom_headline", + "rich_creative_headline": "rich_creative_headline", + "rich_creative_overview": "rich_creative_overview", + "rich_creative_url": "rich_creative_url", + "thank_you_pages": [ + { + "body": "body", + "business_phone": "business_phone", + "button_text": "button_text", + "button_type": "button_type", + "conditional_question_group_id": "conditional_question_group_id", + "enable_messenger": True, + "gated_file_url": "gated_file_url", + "link": "link", + "name": "name", + "title": "title", + } + ], + }, + "lead_gen_form_id": "lead_gen_form_id", + "lifetime_budget": 42, + "lifetime_min_spend_target": "lifetime_min_spend_target", + "lifetime_spend_cap": "lifetime_spend_cap", + "location_types": ["string"], + "messenger_positions": ["string"], + "optimization_goal": "NONE", + "page_id": "page_id", + "pixel_id": "pixel_id", + "promoted_object": { + "custom_conversion_id": "custom_conversion_id", + "custom_event_str": "custom_event_str", + "custom_event_type": "custom_event_type", + "page_id": "page_id", + "pixel_id": "pixel_id", + "whatsapp_phone_number": "whatsapp_phone_number", + }, + "publisher_platforms": ["string"], + "source_adset_id": "source_adset_id", + "start_time": "start_time", + "status": "ACTIVE", + "targeting_automation": {"advantage_audience": 42}, + "threads_positions": ["string"], + "updated_time": "updated_time", + "user_device": ["string"], + "user_os": ["string"], + "whatsapp_phone_number": "whatsapp_phone_number", + "whatsapp_positions": ["string"], + }, + "tiktok": { + "actions": [ + { + "action_category_ids": ["string"], + "action_period": 42, + "action_scene": "VIDEO_RELATED", + "video_user_actions": ["WATCHED_TO_END"], + } + ], + "age_groups": ["AGE_13_17"], + "app_id": "app_xxxxxxxxxxxxxx", + "attribution_event_count": "UNSET", + "audience_ids": ["string"], + "audience_rule": {"foo": "bar"}, + "audience_type": "NORMAL", + "bid_price": 6.9, + "bid_type": "BID_TYPE_NO_BID", + "billing_event": "CPC", + "brand_safety_type": "NO_BRAND_SAFETY", + "budget_mode": "BUDGET_MODE_DAY", + "carrier_ids": ["string"], + "category_exclusion_ids": ["string"], + "click_attribution_window": "OFF", + "comment_disabled": True, + "contextual_tag_ids": ["string"], + "conversion_bid_price": 6.9, + "creative_material_mode": "creative_material_mode", + "dayparting": "dayparting", + "deep_funnel_event_source": "deep_funnel_event_source", + "deep_funnel_event_source_id": "deep_funnel_event_source_id", + "deep_funnel_optimization_status": "ON", + "device_model_ids": ["string"], + "device_price_ranges": ["string"], + "engaged_view_attribution_window": "OFF", + "excluded_audience_ids": ["string"], + "excluded_location_ids": ["string"], + "frequency": 42, + "frequency_schedule": 42, + "gender": "GENDER_UNLIMITED", + "identity_authorized_bc_id": "identity_authorized_bc_id", + "identity_id": "identity_id", + "identity_type": "identity_type", + "instant_form_config": { + "privacy_policy_url": "privacy_policy_url", + "questions": [ + { + "field_type": "field_type", + "label": "label", + } + ], + "button_text": "button_text", + "greeting": "greeting", + "name": "name", + }, + "instant_form_id": "instant_form_id", + "interest_category_ids": ["string"], + "interest_keyword_ids": ["string"], + "inventory_filter_enabled": True, + "ios14_targeting": "UNSET", + "isp_ids": ["string"], + "languages": ["string"], + "location_ids": ["string"], + "min_android_version": "min_android_version", + "min_ios_version": "min_ios_version", + "network_types": ["string"], + "operating_systems": ["ANDROID"], + "operation_status": "ENABLE", + "optimization_event": "optimization_event", + "optimization_goal": "CLICK", + "pacing": "PACING_MODE_SMOOTH", + "pangle_audience_package_exclude_ids": ["string"], + "pangle_audience_package_include_ids": ["string"], + "pangle_block_app_ids": ["string"], + "pixel_id": "pixel_id", + "placement_type": "PLACEMENT_TYPE_AUTOMATIC", + "placements": ["string"], + "product_set_id": "product_set_id", + "product_source": "CATALOG", + "promotion_type": "promotion_type", + "schedule_end_time": "schedule_end_time", + "schedule_start_time": "schedule_start_time", + "schedule_type": "SCHEDULE_START_END", + "secondary_optimization_event": "secondary_optimization_event", + "shopping_ads_retargeting_actions_days": 42, + "shopping_ads_retargeting_type": "OFF", + "spending_power": "ALL", + "tiktok_subplacements": ["string"], + "vertical_sensitivity_id": "vertical_sensitivity_id", + "video_download_disabled": True, + "video_user_actions": ["string"], + "view_attribution_window": "OFF", + }, + }, + status="active", + ) + assert_matches_type(AdGroupUpdateResponse, ad_group, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_raw_response_update(self, client: Whop) -> None: + response = client.ad_groups.with_raw_response.update( + id="adgrp_xxxxxxxxxxxx", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + ad_group = response.parse() + assert_matches_type(AdGroupUpdateResponse, ad_group, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_streaming_response_update(self, client: Whop) -> None: + with client.ad_groups.with_streaming_response.update( + id="adgrp_xxxxxxxxxxxx", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + ad_group = response.parse() + assert_matches_type(AdGroupUpdateResponse, ad_group, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_path_params_update(self, client: Whop) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + client.ad_groups.with_raw_response.update( + id="", + ) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_list(self, client: Whop) -> None: + ad_group = client.ad_groups.list() + assert_matches_type(SyncCursorPage[AdGroupListResponse], ad_group, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_list_with_all_params(self, client: Whop) -> None: + ad_group = client.ad_groups.list( + after="after", + before="before", + campaign_id="campaign_id", + company_id="biz_xxxxxxxxxxxxxx", + created_after=parse_datetime("2023-12-01T05:00:00.401Z"), + created_before=parse_datetime("2023-12-01T05:00:00.401Z"), + first=42, + last=42, + query="query", + status="active", + ) + assert_matches_type(SyncCursorPage[AdGroupListResponse], ad_group, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_raw_response_list(self, client: Whop) -> None: + response = client.ad_groups.with_raw_response.list() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + ad_group = response.parse() + assert_matches_type(SyncCursorPage[AdGroupListResponse], ad_group, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_streaming_response_list(self, client: Whop) -> None: + with client.ad_groups.with_streaming_response.list() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + ad_group = response.parse() + assert_matches_type(SyncCursorPage[AdGroupListResponse], ad_group, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_delete(self, client: Whop) -> None: + ad_group = client.ad_groups.delete( + "adgrp_xxxxxxxxxxxx", + ) + assert_matches_type(AdGroupDeleteResponse, ad_group, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_raw_response_delete(self, client: Whop) -> None: + response = client.ad_groups.with_raw_response.delete( + "adgrp_xxxxxxxxxxxx", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + ad_group = response.parse() + assert_matches_type(AdGroupDeleteResponse, ad_group, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_streaming_response_delete(self, client: Whop) -> None: + with client.ad_groups.with_streaming_response.delete( + "adgrp_xxxxxxxxxxxx", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + ad_group = response.parse() + assert_matches_type(AdGroupDeleteResponse, ad_group, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_path_params_delete(self, client: Whop) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + client.ad_groups.with_raw_response.delete( + "", + ) + + +class TestAsyncAdGroups: + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_create(self, async_client: AsyncWhop) -> None: + ad_group = await async_client.ad_groups.create( + campaign_id="campaign_id", + ) + assert_matches_type(AdGroupCreateResponse, ad_group, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_create_with_all_params(self, async_client: AsyncWhop) -> None: + ad_group = await async_client.ad_groups.create( + campaign_id="campaign_id", + budget=6.9, + budget_type="daily", + config={ + "bid_amount": 42, + "bid_strategy": "lowest_cost", + "billing_event": "impressions", + "end_time": "end_time", + "frequency_cap": 42, + "frequency_cap_interval_days": 42, + "optimization_goal": "conversions", + "pacing": "standard", + "start_time": "start_time", + "targeting": { + "age_max": 42, + "age_min": 42, + "countries": ["string"], + "device_platforms": ["mobile"], + "exclude_audience_ids": ["string"], + "genders": ["male"], + "include_audience_ids": ["string"], + "interest_ids": ["string"], + "languages": ["string"], + "placement_type": "automatic", + }, + }, + daily_budget=6.9, + name="name", + platform_config={ + "meta": { + "android_devices": ["string"], + "attribution_setting": "attribution_setting", + "attribution_spec": [ + { + "event_type": "event_type", + "window_days": 42, + } + ], + "audience_network_positions": ["string"], + "audience_type": "audience_type", + "bid_amount": 42, + "bid_strategy": "LOWEST_COST_WITHOUT_CAP", + "billing_event": "APP_INSTALLS", + "brand_safety_content_filter_levels": ["string"], + "budget_remaining": "budget_remaining", + "cost_per_result_goal": 6.9, + "created_time": "created_time", + "daily_budget": 42, + "daily_min_spend_target": "daily_min_spend_target", + "daily_spend_cap": "daily_spend_cap", + "destination_type": "UNDEFINED", + "dsa_beneficiary": "dsa_beneficiary", + "dsa_payor": "dsa_payor", + "end_time": "end_time", + "excluded_geo_locations": { + "cities": [ + { + "key": "key", + "country": "country", + "name": "name", + "radius": 42, + } + ], + "countries": ["string"], + "location_types": ["string"], + "regions": [ + { + "key": "key", + "country": "country", + "name": "name", + "radius": 42, + } + ], + "zips": [ + { + "key": "key", + "country": "country", + "name": "name", + "radius": 42, + } + ], + }, + "facebook_positions": ["string"], + "frequency_control_count": 42, + "frequency_control_days": 42, + "frequency_control_type": "frequency_control_type", + "geo_cities": [ + { + "key": "key", + "country": "country", + "name": "name", + "radius": 42, + } + ], + "geo_locations": { + "cities": [ + { + "key": "key", + "country": "country", + "name": "name", + "radius": 42, + } + ], + "countries": ["string"], + "location_types": ["string"], + "regions": [ + { + "key": "key", + "country": "country", + "name": "name", + "radius": 42, + } + ], + "zips": [ + { + "key": "key", + "country": "country", + "name": "name", + "radius": 42, + } + ], + }, + "geo_regions": [ + { + "key": "key", + "country": "country", + "name": "name", + "radius": 42, + } + ], + "geo_zips": ["string"], + "instagram_actor_id": "instagram_actor_id", + "instagram_positions": ["string"], + "ios_devices": ["string"], + "is_dynamic_creative": True, + "lead_conversion_location": "website", + "lead_form_config": { + "name": "name", + "privacy_policy_url": "privacy_policy_url", + "questions": [ + { + "type": "type", + "conditional_questions_group_id": "conditional_questions_group_id", + "dependent_conditional_questions": [ + { + "type": "type", + "inline_context": "inline_context", + "key": "key", + "label": "label", + "options": [ + { + "key": "key", + "value": "value", + "logic": { + "type": "type", + "target_end_page_index": 42, + "target_question_index": 42, + }, + } + ], + } + ], + "inline_context": "inline_context", + "key": "key", + "label": "label", + "options": [ + { + "key": "key", + "value": "value", + "logic": { + "type": "type", + "target_end_page_index": 42, + "target_question_index": 42, + }, + } + ], + "question_format": "question_format", + } + ], + "background_image_source": "background_image_source", + "background_image_url": "background_image_url", + "conditional_logic_enabled": True, + "context_card_button_text": "context_card_button_text", + "context_card_content": ["string"], + "context_card_style": "context_card_style", + "context_card_title": "context_card_title", + "custom_disclaimer_body": "custom_disclaimer_body", + "custom_disclaimer_checkboxes": [ + { + "key": "key", + "text": "text", + "is_checked_by_default": True, + "is_required": True, + } + ], + "custom_disclaimer_title": "custom_disclaimer_title", + "form_type": "form_type", + "messenger_enabled": True, + "phone_verification_enabled": True, + "privacy_policy_link_text": "privacy_policy_link_text", + "question_page_custom_headline": "question_page_custom_headline", + "rich_creative_headline": "rich_creative_headline", + "rich_creative_overview": "rich_creative_overview", + "rich_creative_url": "rich_creative_url", + "thank_you_pages": [ + { + "body": "body", + "business_phone": "business_phone", + "button_text": "button_text", + "button_type": "button_type", + "conditional_question_group_id": "conditional_question_group_id", + "enable_messenger": True, + "gated_file_url": "gated_file_url", + "link": "link", + "name": "name", + "title": "title", + } + ], + }, + "lead_gen_form_id": "lead_gen_form_id", + "lifetime_budget": 42, + "lifetime_min_spend_target": "lifetime_min_spend_target", + "lifetime_spend_cap": "lifetime_spend_cap", + "location_types": ["string"], + "messenger_positions": ["string"], + "optimization_goal": "NONE", + "page_id": "page_id", + "pixel_id": "pixel_id", + "promoted_object": { + "custom_conversion_id": "custom_conversion_id", + "custom_event_str": "custom_event_str", + "custom_event_type": "custom_event_type", + "page_id": "page_id", + "pixel_id": "pixel_id", + "whatsapp_phone_number": "whatsapp_phone_number", + }, + "publisher_platforms": ["string"], + "source_adset_id": "source_adset_id", + "start_time": "start_time", + "status": "ACTIVE", + "targeting_automation": {"advantage_audience": 42}, + "threads_positions": ["string"], + "updated_time": "updated_time", + "user_device": ["string"], + "user_os": ["string"], + "whatsapp_phone_number": "whatsapp_phone_number", + "whatsapp_positions": ["string"], + }, + "tiktok": { + "actions": [ + { + "action_category_ids": ["string"], + "action_period": 42, + "action_scene": "VIDEO_RELATED", + "video_user_actions": ["WATCHED_TO_END"], + } + ], + "age_groups": ["AGE_13_17"], + "app_id": "app_xxxxxxxxxxxxxx", + "attribution_event_count": "UNSET", + "audience_ids": ["string"], + "audience_rule": {"foo": "bar"}, + "audience_type": "NORMAL", + "bid_price": 6.9, + "bid_type": "BID_TYPE_NO_BID", + "billing_event": "CPC", + "brand_safety_type": "NO_BRAND_SAFETY", + "budget_mode": "BUDGET_MODE_DAY", + "carrier_ids": ["string"], + "category_exclusion_ids": ["string"], + "click_attribution_window": "OFF", + "comment_disabled": True, + "contextual_tag_ids": ["string"], + "conversion_bid_price": 6.9, + "creative_material_mode": "creative_material_mode", + "dayparting": "dayparting", + "deep_funnel_event_source": "deep_funnel_event_source", + "deep_funnel_event_source_id": "deep_funnel_event_source_id", + "deep_funnel_optimization_status": "ON", + "device_model_ids": ["string"], + "device_price_ranges": ["string"], + "engaged_view_attribution_window": "OFF", + "excluded_audience_ids": ["string"], + "excluded_location_ids": ["string"], + "frequency": 42, + "frequency_schedule": 42, + "gender": "GENDER_UNLIMITED", + "identity_authorized_bc_id": "identity_authorized_bc_id", + "identity_id": "identity_id", + "identity_type": "identity_type", + "instant_form_config": { + "privacy_policy_url": "privacy_policy_url", + "questions": [ + { + "field_type": "field_type", + "label": "label", + } + ], + "button_text": "button_text", + "greeting": "greeting", + "name": "name", + }, + "instant_form_id": "instant_form_id", + "interest_category_ids": ["string"], + "interest_keyword_ids": ["string"], + "inventory_filter_enabled": True, + "ios14_targeting": "UNSET", + "isp_ids": ["string"], + "languages": ["string"], + "location_ids": ["string"], + "min_android_version": "min_android_version", + "min_ios_version": "min_ios_version", + "network_types": ["string"], + "operating_systems": ["ANDROID"], + "operation_status": "ENABLE", + "optimization_event": "optimization_event", + "optimization_goal": "CLICK", + "pacing": "PACING_MODE_SMOOTH", + "pangle_audience_package_exclude_ids": ["string"], + "pangle_audience_package_include_ids": ["string"], + "pangle_block_app_ids": ["string"], + "pixel_id": "pixel_id", + "placement_type": "PLACEMENT_TYPE_AUTOMATIC", + "placements": ["string"], + "product_set_id": "product_set_id", + "product_source": "CATALOG", + "promotion_type": "promotion_type", + "schedule_end_time": "schedule_end_time", + "schedule_start_time": "schedule_start_time", + "schedule_type": "SCHEDULE_START_END", + "secondary_optimization_event": "secondary_optimization_event", + "shopping_ads_retargeting_actions_days": 42, + "shopping_ads_retargeting_type": "OFF", + "spending_power": "ALL", + "tiktok_subplacements": ["string"], + "vertical_sensitivity_id": "vertical_sensitivity_id", + "video_download_disabled": True, + "video_user_actions": ["string"], + "view_attribution_window": "OFF", + }, + }, + status="active", + ) + assert_matches_type(AdGroupCreateResponse, ad_group, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_raw_response_create(self, async_client: AsyncWhop) -> None: + response = await async_client.ad_groups.with_raw_response.create( + campaign_id="campaign_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + ad_group = await response.parse() + assert_matches_type(AdGroupCreateResponse, ad_group, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_streaming_response_create(self, async_client: AsyncWhop) -> None: + async with async_client.ad_groups.with_streaming_response.create( + campaign_id="campaign_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + ad_group = await response.parse() + assert_matches_type(AdGroupCreateResponse, ad_group, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_retrieve(self, async_client: AsyncWhop) -> None: + ad_group = await async_client.ad_groups.retrieve( + "adgrp_xxxxxxxxxxxx", + ) + assert_matches_type(AdGroupRetrieveResponse, ad_group, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_raw_response_retrieve(self, async_client: AsyncWhop) -> None: + response = await async_client.ad_groups.with_raw_response.retrieve( + "adgrp_xxxxxxxxxxxx", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + ad_group = await response.parse() + assert_matches_type(AdGroupRetrieveResponse, ad_group, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_streaming_response_retrieve(self, async_client: AsyncWhop) -> None: + async with async_client.ad_groups.with_streaming_response.retrieve( + "adgrp_xxxxxxxxxxxx", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + ad_group = await response.parse() + assert_matches_type(AdGroupRetrieveResponse, ad_group, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_path_params_retrieve(self, async_client: AsyncWhop) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + await async_client.ad_groups.with_raw_response.retrieve( + "", + ) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_update(self, async_client: AsyncWhop) -> None: + ad_group = await async_client.ad_groups.update( + id="adgrp_xxxxxxxxxxxx", + ) + assert_matches_type(AdGroupUpdateResponse, ad_group, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_update_with_all_params(self, async_client: AsyncWhop) -> None: + ad_group = await async_client.ad_groups.update( + id="adgrp_xxxxxxxxxxxx", + budget=6.9, + budget_type="daily", + config={ + "bid_amount": 42, + "bid_strategy": "lowest_cost", + "billing_event": "impressions", + "end_time": "end_time", + "frequency_cap": 42, + "frequency_cap_interval_days": 42, + "optimization_goal": "conversions", + "pacing": "standard", + "start_time": "start_time", + "targeting": { + "age_max": 42, + "age_min": 42, + "countries": ["string"], + "device_platforms": ["mobile"], + "exclude_audience_ids": ["string"], + "genders": ["male"], + "include_audience_ids": ["string"], + "interest_ids": ["string"], + "languages": ["string"], + "placement_type": "automatic", + }, + }, + daily_budget=6.9, + name="name", + platform_config={ + "meta": { + "android_devices": ["string"], + "attribution_setting": "attribution_setting", + "attribution_spec": [ + { + "event_type": "event_type", + "window_days": 42, + } + ], + "audience_network_positions": ["string"], + "audience_type": "audience_type", + "bid_amount": 42, + "bid_strategy": "LOWEST_COST_WITHOUT_CAP", + "billing_event": "APP_INSTALLS", + "brand_safety_content_filter_levels": ["string"], + "budget_remaining": "budget_remaining", + "cost_per_result_goal": 6.9, + "created_time": "created_time", + "daily_budget": 42, + "daily_min_spend_target": "daily_min_spend_target", + "daily_spend_cap": "daily_spend_cap", + "destination_type": "UNDEFINED", + "dsa_beneficiary": "dsa_beneficiary", + "dsa_payor": "dsa_payor", + "end_time": "end_time", + "excluded_geo_locations": { + "cities": [ + { + "key": "key", + "country": "country", + "name": "name", + "radius": 42, + } + ], + "countries": ["string"], + "location_types": ["string"], + "regions": [ + { + "key": "key", + "country": "country", + "name": "name", + "radius": 42, + } + ], + "zips": [ + { + "key": "key", + "country": "country", + "name": "name", + "radius": 42, + } + ], + }, + "facebook_positions": ["string"], + "frequency_control_count": 42, + "frequency_control_days": 42, + "frequency_control_type": "frequency_control_type", + "geo_cities": [ + { + "key": "key", + "country": "country", + "name": "name", + "radius": 42, + } + ], + "geo_locations": { + "cities": [ + { + "key": "key", + "country": "country", + "name": "name", + "radius": 42, + } + ], + "countries": ["string"], + "location_types": ["string"], + "regions": [ + { + "key": "key", + "country": "country", + "name": "name", + "radius": 42, + } + ], + "zips": [ + { + "key": "key", + "country": "country", + "name": "name", + "radius": 42, + } + ], + }, + "geo_regions": [ + { + "key": "key", + "country": "country", + "name": "name", + "radius": 42, + } + ], + "geo_zips": ["string"], + "instagram_actor_id": "instagram_actor_id", + "instagram_positions": ["string"], + "ios_devices": ["string"], + "is_dynamic_creative": True, + "lead_conversion_location": "website", + "lead_form_config": { + "name": "name", + "privacy_policy_url": "privacy_policy_url", + "questions": [ + { + "type": "type", + "conditional_questions_group_id": "conditional_questions_group_id", + "dependent_conditional_questions": [ + { + "type": "type", + "inline_context": "inline_context", + "key": "key", + "label": "label", + "options": [ + { + "key": "key", + "value": "value", + "logic": { + "type": "type", + "target_end_page_index": 42, + "target_question_index": 42, + }, + } + ], + } + ], + "inline_context": "inline_context", + "key": "key", + "label": "label", + "options": [ + { + "key": "key", + "value": "value", + "logic": { + "type": "type", + "target_end_page_index": 42, + "target_question_index": 42, + }, + } + ], + "question_format": "question_format", + } + ], + "background_image_source": "background_image_source", + "background_image_url": "background_image_url", + "conditional_logic_enabled": True, + "context_card_button_text": "context_card_button_text", + "context_card_content": ["string"], + "context_card_style": "context_card_style", + "context_card_title": "context_card_title", + "custom_disclaimer_body": "custom_disclaimer_body", + "custom_disclaimer_checkboxes": [ + { + "key": "key", + "text": "text", + "is_checked_by_default": True, + "is_required": True, + } + ], + "custom_disclaimer_title": "custom_disclaimer_title", + "form_type": "form_type", + "messenger_enabled": True, + "phone_verification_enabled": True, + "privacy_policy_link_text": "privacy_policy_link_text", + "question_page_custom_headline": "question_page_custom_headline", + "rich_creative_headline": "rich_creative_headline", + "rich_creative_overview": "rich_creative_overview", + "rich_creative_url": "rich_creative_url", + "thank_you_pages": [ + { + "body": "body", + "business_phone": "business_phone", + "button_text": "button_text", + "button_type": "button_type", + "conditional_question_group_id": "conditional_question_group_id", + "enable_messenger": True, + "gated_file_url": "gated_file_url", + "link": "link", + "name": "name", + "title": "title", + } + ], + }, + "lead_gen_form_id": "lead_gen_form_id", + "lifetime_budget": 42, + "lifetime_min_spend_target": "lifetime_min_spend_target", + "lifetime_spend_cap": "lifetime_spend_cap", + "location_types": ["string"], + "messenger_positions": ["string"], + "optimization_goal": "NONE", + "page_id": "page_id", + "pixel_id": "pixel_id", + "promoted_object": { + "custom_conversion_id": "custom_conversion_id", + "custom_event_str": "custom_event_str", + "custom_event_type": "custom_event_type", + "page_id": "page_id", + "pixel_id": "pixel_id", + "whatsapp_phone_number": "whatsapp_phone_number", + }, + "publisher_platforms": ["string"], + "source_adset_id": "source_adset_id", + "start_time": "start_time", + "status": "ACTIVE", + "targeting_automation": {"advantage_audience": 42}, + "threads_positions": ["string"], + "updated_time": "updated_time", + "user_device": ["string"], + "user_os": ["string"], + "whatsapp_phone_number": "whatsapp_phone_number", + "whatsapp_positions": ["string"], + }, + "tiktok": { + "actions": [ + { + "action_category_ids": ["string"], + "action_period": 42, + "action_scene": "VIDEO_RELATED", + "video_user_actions": ["WATCHED_TO_END"], + } + ], + "age_groups": ["AGE_13_17"], + "app_id": "app_xxxxxxxxxxxxxx", + "attribution_event_count": "UNSET", + "audience_ids": ["string"], + "audience_rule": {"foo": "bar"}, + "audience_type": "NORMAL", + "bid_price": 6.9, + "bid_type": "BID_TYPE_NO_BID", + "billing_event": "CPC", + "brand_safety_type": "NO_BRAND_SAFETY", + "budget_mode": "BUDGET_MODE_DAY", + "carrier_ids": ["string"], + "category_exclusion_ids": ["string"], + "click_attribution_window": "OFF", + "comment_disabled": True, + "contextual_tag_ids": ["string"], + "conversion_bid_price": 6.9, + "creative_material_mode": "creative_material_mode", + "dayparting": "dayparting", + "deep_funnel_event_source": "deep_funnel_event_source", + "deep_funnel_event_source_id": "deep_funnel_event_source_id", + "deep_funnel_optimization_status": "ON", + "device_model_ids": ["string"], + "device_price_ranges": ["string"], + "engaged_view_attribution_window": "OFF", + "excluded_audience_ids": ["string"], + "excluded_location_ids": ["string"], + "frequency": 42, + "frequency_schedule": 42, + "gender": "GENDER_UNLIMITED", + "identity_authorized_bc_id": "identity_authorized_bc_id", + "identity_id": "identity_id", + "identity_type": "identity_type", + "instant_form_config": { + "privacy_policy_url": "privacy_policy_url", + "questions": [ + { + "field_type": "field_type", + "label": "label", + } + ], + "button_text": "button_text", + "greeting": "greeting", + "name": "name", + }, + "instant_form_id": "instant_form_id", + "interest_category_ids": ["string"], + "interest_keyword_ids": ["string"], + "inventory_filter_enabled": True, + "ios14_targeting": "UNSET", + "isp_ids": ["string"], + "languages": ["string"], + "location_ids": ["string"], + "min_android_version": "min_android_version", + "min_ios_version": "min_ios_version", + "network_types": ["string"], + "operating_systems": ["ANDROID"], + "operation_status": "ENABLE", + "optimization_event": "optimization_event", + "optimization_goal": "CLICK", + "pacing": "PACING_MODE_SMOOTH", + "pangle_audience_package_exclude_ids": ["string"], + "pangle_audience_package_include_ids": ["string"], + "pangle_block_app_ids": ["string"], + "pixel_id": "pixel_id", + "placement_type": "PLACEMENT_TYPE_AUTOMATIC", + "placements": ["string"], + "product_set_id": "product_set_id", + "product_source": "CATALOG", + "promotion_type": "promotion_type", + "schedule_end_time": "schedule_end_time", + "schedule_start_time": "schedule_start_time", + "schedule_type": "SCHEDULE_START_END", + "secondary_optimization_event": "secondary_optimization_event", + "shopping_ads_retargeting_actions_days": 42, + "shopping_ads_retargeting_type": "OFF", + "spending_power": "ALL", + "tiktok_subplacements": ["string"], + "vertical_sensitivity_id": "vertical_sensitivity_id", + "video_download_disabled": True, + "video_user_actions": ["string"], + "view_attribution_window": "OFF", + }, + }, + status="active", + ) + assert_matches_type(AdGroupUpdateResponse, ad_group, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_raw_response_update(self, async_client: AsyncWhop) -> None: + response = await async_client.ad_groups.with_raw_response.update( + id="adgrp_xxxxxxxxxxxx", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + ad_group = await response.parse() + assert_matches_type(AdGroupUpdateResponse, ad_group, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_streaming_response_update(self, async_client: AsyncWhop) -> None: + async with async_client.ad_groups.with_streaming_response.update( + id="adgrp_xxxxxxxxxxxx", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + ad_group = await response.parse() + assert_matches_type(AdGroupUpdateResponse, ad_group, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_path_params_update(self, async_client: AsyncWhop) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + await async_client.ad_groups.with_raw_response.update( + id="", + ) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_list(self, async_client: AsyncWhop) -> None: + ad_group = await async_client.ad_groups.list() + assert_matches_type(AsyncCursorPage[AdGroupListResponse], ad_group, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_list_with_all_params(self, async_client: AsyncWhop) -> None: + ad_group = await async_client.ad_groups.list( + after="after", + before="before", + campaign_id="campaign_id", + company_id="biz_xxxxxxxxxxxxxx", + created_after=parse_datetime("2023-12-01T05:00:00.401Z"), + created_before=parse_datetime("2023-12-01T05:00:00.401Z"), + first=42, + last=42, + query="query", + status="active", + ) + assert_matches_type(AsyncCursorPage[AdGroupListResponse], ad_group, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_raw_response_list(self, async_client: AsyncWhop) -> None: + response = await async_client.ad_groups.with_raw_response.list() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + ad_group = await response.parse() + assert_matches_type(AsyncCursorPage[AdGroupListResponse], ad_group, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_streaming_response_list(self, async_client: AsyncWhop) -> None: + async with async_client.ad_groups.with_streaming_response.list() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + ad_group = await response.parse() + assert_matches_type(AsyncCursorPage[AdGroupListResponse], ad_group, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_delete(self, async_client: AsyncWhop) -> None: + ad_group = await async_client.ad_groups.delete( + "adgrp_xxxxxxxxxxxx", + ) + assert_matches_type(AdGroupDeleteResponse, ad_group, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_raw_response_delete(self, async_client: AsyncWhop) -> None: + response = await async_client.ad_groups.with_raw_response.delete( + "adgrp_xxxxxxxxxxxx", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + ad_group = await response.parse() + assert_matches_type(AdGroupDeleteResponse, ad_group, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_streaming_response_delete(self, async_client: AsyncWhop) -> None: + async with async_client.ad_groups.with_streaming_response.delete( + "adgrp_xxxxxxxxxxxx", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + ad_group = await response.parse() + assert_matches_type(AdGroupDeleteResponse, ad_group, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_path_params_delete(self, async_client: AsyncWhop) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + await async_client.ad_groups.with_raw_response.delete( + "", + ) diff --git a/tests/api_resources/test_ads.py b/tests/api_resources/test_ads.py new file mode 100644 index 00000000..4d3e8a06 --- /dev/null +++ b/tests/api_resources/test_ads.py @@ -0,0 +1,482 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import os +from typing import Any, cast + +import pytest + +from whop_sdk import Whop, AsyncWhop +from tests.utils import assert_matches_type +from whop_sdk.types import AdListResponse, AdCreateResponse, AdRetrieveResponse +from whop_sdk._utils import parse_datetime +from whop_sdk.pagination import SyncCursorPage, AsyncCursorPage + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestAds: + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_create(self, client: Whop) -> None: + ad = client.ads.create( + ad_group_id="ad_group_id", + ) + assert_matches_type(AdCreateResponse, ad, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_create_with_all_params(self, client: Whop) -> None: + ad = client.ads.create( + ad_group_id="ad_group_id", + creative_set_id="creative_set_id", + existing_instagram_media_id="existing_instagram_media_id", + existing_post_id="existing_post_id", + platform_config={ + "meta": { + "call_to_action_type": "LEARN_MORE", + "carousel_cards": [ + { + "call_to_action_type": "call_to_action_type", + "description": "description", + "link": "link", + "name": "name", + } + ], + "description": "description", + "descriptions": ["string"], + "existing_instagram_media_id": "existing_instagram_media_id", + "existing_post_id": "existing_post_id", + "headline": "headline", + "headlines": ["string"], + "instagram_actor_id": "instagram_actor_id", + "lead_form_config": {"foo": "bar"}, + "link_url": "link_url", + "multi_advertiser_enrollment": "OPT_IN", + "name": "name", + "page_id": "page_id", + "page_welcome_message": {"foo": "bar"}, + "primary_text": "primary_text", + "primary_texts": ["string"], + "url_tags": "url_tags", + }, + "tiktok": { + "access_pass_tag": "access_pass_tag", + "ad_format": "SINGLE_IMAGE", + "ad_name": "ad_name", + "ad_text": "ad_text", + "ad_texts": ["string"], + "aigc_disclosure_type": "UNSET", + "auto_disclaimer_types": ["string"], + "automate_creative_enabled": True, + "brand_safety_postbid_partner": "UNSET", + "brand_safety_vast_url": "brand_safety_vast_url", + "call_to_action": "LEARN_MORE", + "call_to_action_enabled": True, + "call_to_action_id": "call_to_action_id", + "call_to_action_mode": "STANDARD", + "card_id": "card_id", + "carousel_image_index": 42, + "catalog_id": "catalog_id", + "click_tracking_url": "click_tracking_url", + "cpp_url": "cpp_url", + "creative_authorized": True, + "creative_auto_enhancement_strategy_list": ["string"], + "dark_post_status": "ON", + "deeplink": "deeplink", + "deeplink_format_type": "UNSET", + "deeplink_type": "deeplink_type", + "deeplink_utm_params": [{"foo": "bar"}], + "disclaimer_clickable_texts": [{"foo": "bar"}], + "disclaimer_text": "disclaimer_text", + "disclaimer_type": "NONE", + "dynamic_destination": "dynamic_destination", + "dynamic_format": "dynamic_format", + "end_card_cta": "end_card_cta", + "fallback_type": "UNSET", + "identity_authorized_bc_id": "identity_authorized_bc_id", + "identity_id": "identity_id", + "identity_type": "CUSTOMIZED_USER", + "image_ids": ["string"], + "impression_tracking_url": "impression_tracking_url", + "item_duet_status": "ENABLE", + "item_group_ids": ["string"], + "item_stitch_status": "ENABLE", + "landing_page_url": "landing_page_url", + "link_url": "link_url", + "music_id": "music_id", + "page_id": "page_id", + "product_display_field_list": ["string"], + "product_set_id": "product_set_id", + "product_specific_type": "product_specific_type", + "promotional_music_disabled": True, + "shopping_ads_fallback_type": "UNSET", + "shopping_ads_video_package_id": "shopping_ads_video_package_id", + "showcase_products": [{"foo": "bar"}], + "sku_ids": ["string"], + "tiktok_item_id": "tiktok_item_id", + "tracking_app_id": "tracking_app_id", + "tracking_message_event_set_id": "tracking_message_event_set_id", + "tracking_offline_event_set_ids": ["string"], + "tracking_pixel_id": "trpx_xxxxxxxxxxxxx", + "utm_params": [{"foo": "bar"}], + "vertical_video_strategy": "vertical_video_strategy", + "video_id": "video_id", + "video_view_tracking_url": "video_view_tracking_url", + "viewability_postbid_partner": "UNSET", + "viewability_vast_url": "viewability_vast_url", + }, + }, + status="active", + ) + assert_matches_type(AdCreateResponse, ad, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_raw_response_create(self, client: Whop) -> None: + response = client.ads.with_raw_response.create( + ad_group_id="ad_group_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + ad = response.parse() + assert_matches_type(AdCreateResponse, ad, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_streaming_response_create(self, client: Whop) -> None: + with client.ads.with_streaming_response.create( + ad_group_id="ad_group_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + ad = response.parse() + assert_matches_type(AdCreateResponse, ad, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_retrieve(self, client: Whop) -> None: + ad = client.ads.retrieve( + "xad_xxxxxxxxxxxxxx", + ) + assert_matches_type(AdRetrieveResponse, ad, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_raw_response_retrieve(self, client: Whop) -> None: + response = client.ads.with_raw_response.retrieve( + "xad_xxxxxxxxxxxxxx", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + ad = response.parse() + assert_matches_type(AdRetrieveResponse, ad, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_streaming_response_retrieve(self, client: Whop) -> None: + with client.ads.with_streaming_response.retrieve( + "xad_xxxxxxxxxxxxxx", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + ad = response.parse() + assert_matches_type(AdRetrieveResponse, ad, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_path_params_retrieve(self, client: Whop) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + client.ads.with_raw_response.retrieve( + "", + ) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_list(self, client: Whop) -> None: + ad = client.ads.list() + assert_matches_type(SyncCursorPage[AdListResponse], ad, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_list_with_all_params(self, client: Whop) -> None: + ad = client.ads.list( + ad_group_id="ad_group_id", + after="after", + before="before", + campaign_id="campaign_id", + company_id="biz_xxxxxxxxxxxxxx", + created_after=parse_datetime("2023-12-01T05:00:00.401Z"), + created_before=parse_datetime("2023-12-01T05:00:00.401Z"), + first=42, + last=42, + status="active", + ) + assert_matches_type(SyncCursorPage[AdListResponse], ad, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_raw_response_list(self, client: Whop) -> None: + response = client.ads.with_raw_response.list() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + ad = response.parse() + assert_matches_type(SyncCursorPage[AdListResponse], ad, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_streaming_response_list(self, client: Whop) -> None: + with client.ads.with_streaming_response.list() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + ad = response.parse() + assert_matches_type(SyncCursorPage[AdListResponse], ad, path=["response"]) + + assert cast(Any, response.is_closed) is True + + +class TestAsyncAds: + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_create(self, async_client: AsyncWhop) -> None: + ad = await async_client.ads.create( + ad_group_id="ad_group_id", + ) + assert_matches_type(AdCreateResponse, ad, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_create_with_all_params(self, async_client: AsyncWhop) -> None: + ad = await async_client.ads.create( + ad_group_id="ad_group_id", + creative_set_id="creative_set_id", + existing_instagram_media_id="existing_instagram_media_id", + existing_post_id="existing_post_id", + platform_config={ + "meta": { + "call_to_action_type": "LEARN_MORE", + "carousel_cards": [ + { + "call_to_action_type": "call_to_action_type", + "description": "description", + "link": "link", + "name": "name", + } + ], + "description": "description", + "descriptions": ["string"], + "existing_instagram_media_id": "existing_instagram_media_id", + "existing_post_id": "existing_post_id", + "headline": "headline", + "headlines": ["string"], + "instagram_actor_id": "instagram_actor_id", + "lead_form_config": {"foo": "bar"}, + "link_url": "link_url", + "multi_advertiser_enrollment": "OPT_IN", + "name": "name", + "page_id": "page_id", + "page_welcome_message": {"foo": "bar"}, + "primary_text": "primary_text", + "primary_texts": ["string"], + "url_tags": "url_tags", + }, + "tiktok": { + "access_pass_tag": "access_pass_tag", + "ad_format": "SINGLE_IMAGE", + "ad_name": "ad_name", + "ad_text": "ad_text", + "ad_texts": ["string"], + "aigc_disclosure_type": "UNSET", + "auto_disclaimer_types": ["string"], + "automate_creative_enabled": True, + "brand_safety_postbid_partner": "UNSET", + "brand_safety_vast_url": "brand_safety_vast_url", + "call_to_action": "LEARN_MORE", + "call_to_action_enabled": True, + "call_to_action_id": "call_to_action_id", + "call_to_action_mode": "STANDARD", + "card_id": "card_id", + "carousel_image_index": 42, + "catalog_id": "catalog_id", + "click_tracking_url": "click_tracking_url", + "cpp_url": "cpp_url", + "creative_authorized": True, + "creative_auto_enhancement_strategy_list": ["string"], + "dark_post_status": "ON", + "deeplink": "deeplink", + "deeplink_format_type": "UNSET", + "deeplink_type": "deeplink_type", + "deeplink_utm_params": [{"foo": "bar"}], + "disclaimer_clickable_texts": [{"foo": "bar"}], + "disclaimer_text": "disclaimer_text", + "disclaimer_type": "NONE", + "dynamic_destination": "dynamic_destination", + "dynamic_format": "dynamic_format", + "end_card_cta": "end_card_cta", + "fallback_type": "UNSET", + "identity_authorized_bc_id": "identity_authorized_bc_id", + "identity_id": "identity_id", + "identity_type": "CUSTOMIZED_USER", + "image_ids": ["string"], + "impression_tracking_url": "impression_tracking_url", + "item_duet_status": "ENABLE", + "item_group_ids": ["string"], + "item_stitch_status": "ENABLE", + "landing_page_url": "landing_page_url", + "link_url": "link_url", + "music_id": "music_id", + "page_id": "page_id", + "product_display_field_list": ["string"], + "product_set_id": "product_set_id", + "product_specific_type": "product_specific_type", + "promotional_music_disabled": True, + "shopping_ads_fallback_type": "UNSET", + "shopping_ads_video_package_id": "shopping_ads_video_package_id", + "showcase_products": [{"foo": "bar"}], + "sku_ids": ["string"], + "tiktok_item_id": "tiktok_item_id", + "tracking_app_id": "tracking_app_id", + "tracking_message_event_set_id": "tracking_message_event_set_id", + "tracking_offline_event_set_ids": ["string"], + "tracking_pixel_id": "trpx_xxxxxxxxxxxxx", + "utm_params": [{"foo": "bar"}], + "vertical_video_strategy": "vertical_video_strategy", + "video_id": "video_id", + "video_view_tracking_url": "video_view_tracking_url", + "viewability_postbid_partner": "UNSET", + "viewability_vast_url": "viewability_vast_url", + }, + }, + status="active", + ) + assert_matches_type(AdCreateResponse, ad, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_raw_response_create(self, async_client: AsyncWhop) -> None: + response = await async_client.ads.with_raw_response.create( + ad_group_id="ad_group_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + ad = await response.parse() + assert_matches_type(AdCreateResponse, ad, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_streaming_response_create(self, async_client: AsyncWhop) -> None: + async with async_client.ads.with_streaming_response.create( + ad_group_id="ad_group_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + ad = await response.parse() + assert_matches_type(AdCreateResponse, ad, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_retrieve(self, async_client: AsyncWhop) -> None: + ad = await async_client.ads.retrieve( + "xad_xxxxxxxxxxxxxx", + ) + assert_matches_type(AdRetrieveResponse, ad, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_raw_response_retrieve(self, async_client: AsyncWhop) -> None: + response = await async_client.ads.with_raw_response.retrieve( + "xad_xxxxxxxxxxxxxx", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + ad = await response.parse() + assert_matches_type(AdRetrieveResponse, ad, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_streaming_response_retrieve(self, async_client: AsyncWhop) -> None: + async with async_client.ads.with_streaming_response.retrieve( + "xad_xxxxxxxxxxxxxx", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + ad = await response.parse() + assert_matches_type(AdRetrieveResponse, ad, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_path_params_retrieve(self, async_client: AsyncWhop) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + await async_client.ads.with_raw_response.retrieve( + "", + ) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_list(self, async_client: AsyncWhop) -> None: + ad = await async_client.ads.list() + assert_matches_type(AsyncCursorPage[AdListResponse], ad, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_list_with_all_params(self, async_client: AsyncWhop) -> None: + ad = await async_client.ads.list( + ad_group_id="ad_group_id", + after="after", + before="before", + campaign_id="campaign_id", + company_id="biz_xxxxxxxxxxxxxx", + created_after=parse_datetime("2023-12-01T05:00:00.401Z"), + created_before=parse_datetime("2023-12-01T05:00:00.401Z"), + first=42, + last=42, + status="active", + ) + assert_matches_type(AsyncCursorPage[AdListResponse], ad, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_raw_response_list(self, async_client: AsyncWhop) -> None: + response = await async_client.ads.with_raw_response.list() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + ad = await response.parse() + assert_matches_type(AsyncCursorPage[AdListResponse], ad, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_streaming_response_list(self, async_client: AsyncWhop) -> None: + async with async_client.ads.with_streaming_response.list() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + ad = await response.parse() + assert_matches_type(AsyncCursorPage[AdListResponse], ad, path=["response"]) + + assert cast(Any, response.is_closed) is True diff --git a/tests/api_resources/test_bounties.py b/tests/api_resources/test_bounties.py new file mode 100644 index 00000000..ccec6b62 --- /dev/null +++ b/tests/api_resources/test_bounties.py @@ -0,0 +1,313 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import os +from typing import Any, cast + +import pytest + +from whop_sdk import Whop, AsyncWhop +from tests.utils import assert_matches_type +from whop_sdk.types import ( + BountyListResponse, + BountyCreateResponse, + BountyRetrieveResponse, +) +from whop_sdk.pagination import SyncCursorPage, AsyncCursorPage + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestBounties: + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_create(self, client: Whop) -> None: + bounty = client.bounties.create( + base_unit_amount=6.9, + currency="usd", + description="description", + title="title", + ) + assert_matches_type(BountyCreateResponse, bounty, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_create_with_all_params(self, client: Whop) -> None: + bounty = client.bounties.create( + base_unit_amount=6.9, + currency="usd", + description="description", + title="title", + accepted_submissions_limit=42, + allowed_country_codes=["string"], + experience_id="exp_xxxxxxxxxxxxxx", + origin_account_id="origin_account_id", + ) + assert_matches_type(BountyCreateResponse, bounty, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_raw_response_create(self, client: Whop) -> None: + response = client.bounties.with_raw_response.create( + base_unit_amount=6.9, + currency="usd", + description="description", + title="title", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + bounty = response.parse() + assert_matches_type(BountyCreateResponse, bounty, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_streaming_response_create(self, client: Whop) -> None: + with client.bounties.with_streaming_response.create( + base_unit_amount=6.9, + currency="usd", + description="description", + title="title", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + bounty = response.parse() + assert_matches_type(BountyCreateResponse, bounty, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_retrieve(self, client: Whop) -> None: + bounty = client.bounties.retrieve( + "bnty_xxxxxxxxxxxxx", + ) + assert_matches_type(BountyRetrieveResponse, bounty, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_raw_response_retrieve(self, client: Whop) -> None: + response = client.bounties.with_raw_response.retrieve( + "bnty_xxxxxxxxxxxxx", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + bounty = response.parse() + assert_matches_type(BountyRetrieveResponse, bounty, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_streaming_response_retrieve(self, client: Whop) -> None: + with client.bounties.with_streaming_response.retrieve( + "bnty_xxxxxxxxxxxxx", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + bounty = response.parse() + assert_matches_type(BountyRetrieveResponse, bounty, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_path_params_retrieve(self, client: Whop) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + client.bounties.with_raw_response.retrieve( + "", + ) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_list(self, client: Whop) -> None: + bounty = client.bounties.list() + assert_matches_type(SyncCursorPage[BountyListResponse], bounty, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_list_with_all_params(self, client: Whop) -> None: + bounty = client.bounties.list( + after="after", + before="before", + direction="asc", + experience_id="exp_xxxxxxxxxxxxxx", + first=42, + last=42, + status="published", + ) + assert_matches_type(SyncCursorPage[BountyListResponse], bounty, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_raw_response_list(self, client: Whop) -> None: + response = client.bounties.with_raw_response.list() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + bounty = response.parse() + assert_matches_type(SyncCursorPage[BountyListResponse], bounty, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_streaming_response_list(self, client: Whop) -> None: + with client.bounties.with_streaming_response.list() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + bounty = response.parse() + assert_matches_type(SyncCursorPage[BountyListResponse], bounty, path=["response"]) + + assert cast(Any, response.is_closed) is True + + +class TestAsyncBounties: + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_create(self, async_client: AsyncWhop) -> None: + bounty = await async_client.bounties.create( + base_unit_amount=6.9, + currency="usd", + description="description", + title="title", + ) + assert_matches_type(BountyCreateResponse, bounty, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_create_with_all_params(self, async_client: AsyncWhop) -> None: + bounty = await async_client.bounties.create( + base_unit_amount=6.9, + currency="usd", + description="description", + title="title", + accepted_submissions_limit=42, + allowed_country_codes=["string"], + experience_id="exp_xxxxxxxxxxxxxx", + origin_account_id="origin_account_id", + ) + assert_matches_type(BountyCreateResponse, bounty, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_raw_response_create(self, async_client: AsyncWhop) -> None: + response = await async_client.bounties.with_raw_response.create( + base_unit_amount=6.9, + currency="usd", + description="description", + title="title", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + bounty = await response.parse() + assert_matches_type(BountyCreateResponse, bounty, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_streaming_response_create(self, async_client: AsyncWhop) -> None: + async with async_client.bounties.with_streaming_response.create( + base_unit_amount=6.9, + currency="usd", + description="description", + title="title", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + bounty = await response.parse() + assert_matches_type(BountyCreateResponse, bounty, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_retrieve(self, async_client: AsyncWhop) -> None: + bounty = await async_client.bounties.retrieve( + "bnty_xxxxxxxxxxxxx", + ) + assert_matches_type(BountyRetrieveResponse, bounty, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_raw_response_retrieve(self, async_client: AsyncWhop) -> None: + response = await async_client.bounties.with_raw_response.retrieve( + "bnty_xxxxxxxxxxxxx", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + bounty = await response.parse() + assert_matches_type(BountyRetrieveResponse, bounty, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_streaming_response_retrieve(self, async_client: AsyncWhop) -> None: + async with async_client.bounties.with_streaming_response.retrieve( + "bnty_xxxxxxxxxxxxx", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + bounty = await response.parse() + assert_matches_type(BountyRetrieveResponse, bounty, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_path_params_retrieve(self, async_client: AsyncWhop) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + await async_client.bounties.with_raw_response.retrieve( + "", + ) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_list(self, async_client: AsyncWhop) -> None: + bounty = await async_client.bounties.list() + assert_matches_type(AsyncCursorPage[BountyListResponse], bounty, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_list_with_all_params(self, async_client: AsyncWhop) -> None: + bounty = await async_client.bounties.list( + after="after", + before="before", + direction="asc", + experience_id="exp_xxxxxxxxxxxxxx", + first=42, + last=42, + status="published", + ) + assert_matches_type(AsyncCursorPage[BountyListResponse], bounty, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_raw_response_list(self, async_client: AsyncWhop) -> None: + response = await async_client.bounties.with_raw_response.list() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + bounty = await response.parse() + assert_matches_type(AsyncCursorPage[BountyListResponse], bounty, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_streaming_response_list(self, async_client: AsyncWhop) -> None: + async with async_client.bounties.with_streaming_response.list() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + bounty = await response.parse() + assert_matches_type(AsyncCursorPage[BountyListResponse], bounty, path=["response"]) + + assert cast(Any, response.is_closed) is True diff --git a/tests/api_resources/test_checkout_configurations.py b/tests/api_resources/test_checkout_configurations.py index 76c97549..32f786e3 100644 --- a/tests/api_resources/test_checkout_configurations.py +++ b/tests/api_resources/test_checkout_configurations.py @@ -40,6 +40,7 @@ def test_method_create_with_all_params_overload_1(self, client: Whop) -> None: plan={ "company_id": "biz_xxxxxxxxxxxxxx", "currency": "usd", + "adaptive_pricing_enabled": True, "application_fee_amount": 6.9, "billing_period": 42, "custom_fields": [ @@ -91,6 +92,7 @@ def test_method_create_with_all_params_overload_1(self, client: Whop) -> None: affiliate_code="affiliate_code", allow_promo_codes=True, checkout_styling={ + "background_color": "background_color", "border_style": "rounded", "button_color": "button_color", "font_family": "system", @@ -156,6 +158,7 @@ def test_method_create_with_all_params_overload_2(self, client: Whop) -> None: affiliate_code="affiliate_code", allow_promo_codes=True, checkout_styling={ + "background_color": "background_color", "border_style": "rounded", "button_color": "button_color", "font_family": "system", @@ -216,6 +219,7 @@ def test_method_create_with_all_params_overload_3(self, client: Whop) -> None: mode="setup", allow_promo_codes=True, checkout_styling={ + "background_color": "background_color", "border_style": "rounded", "button_color": "button_color", "font_family": "system", @@ -384,6 +388,7 @@ async def test_method_create_with_all_params_overload_1(self, async_client: Asyn plan={ "company_id": "biz_xxxxxxxxxxxxxx", "currency": "usd", + "adaptive_pricing_enabled": True, "application_fee_amount": 6.9, "billing_period": 42, "custom_fields": [ @@ -435,6 +440,7 @@ async def test_method_create_with_all_params_overload_1(self, async_client: Asyn affiliate_code="affiliate_code", allow_promo_codes=True, checkout_styling={ + "background_color": "background_color", "border_style": "rounded", "button_color": "button_color", "font_family": "system", @@ -500,6 +506,7 @@ async def test_method_create_with_all_params_overload_2(self, async_client: Asyn affiliate_code="affiliate_code", allow_promo_codes=True, checkout_styling={ + "background_color": "background_color", "border_style": "rounded", "button_color": "button_color", "font_family": "system", @@ -560,6 +567,7 @@ async def test_method_create_with_all_params_overload_3(self, async_client: Asyn mode="setup", allow_promo_codes=True, checkout_styling={ + "background_color": "background_color", "border_style": "rounded", "button_color": "button_color", "font_family": "system", diff --git a/tests/api_resources/test_companies.py b/tests/api_resources/test_companies.py index 8b3e6f8e..4051c780 100644 --- a/tests/api_resources/test_companies.py +++ b/tests/api_resources/test_companies.py @@ -11,6 +11,7 @@ from tests.utils import assert_matches_type from whop_sdk.types import ( CompanyListResponse, + CompanyCreateAPIKeyResponse, ) from whop_sdk._utils import parse_datetime from whop_sdk.pagination import SyncCursorPage, AsyncCursorPage @@ -215,6 +216,70 @@ def test_streaming_response_list(self, client: Whop) -> None: assert cast(Any, response.is_closed) is True + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_create_api_key(self, client: Whop) -> None: + company = client.companies.create_api_key( + parent_company_id="parent_company_id", + child_company_id="child_company_id", + ) + assert_matches_type(CompanyCreateAPIKeyResponse, company, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_create_api_key_with_all_params(self, client: Whop) -> None: + company = client.companies.create_api_key( + parent_company_id="parent_company_id", + child_company_id="child_company_id", + name="name", + permissions=[ + { + "actions": ["string"], + "grant": True, + "resources": ["string"], + } + ], + role="owner", + ) + assert_matches_type(CompanyCreateAPIKeyResponse, company, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_raw_response_create_api_key(self, client: Whop) -> None: + response = client.companies.with_raw_response.create_api_key( + parent_company_id="parent_company_id", + child_company_id="child_company_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + company = response.parse() + assert_matches_type(CompanyCreateAPIKeyResponse, company, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_streaming_response_create_api_key(self, client: Whop) -> None: + with client.companies.with_streaming_response.create_api_key( + parent_company_id="parent_company_id", + child_company_id="child_company_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + company = response.parse() + assert_matches_type(CompanyCreateAPIKeyResponse, company, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_path_params_create_api_key(self, client: Whop) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `parent_company_id` but received ''"): + client.companies.with_raw_response.create_api_key( + parent_company_id="", + child_company_id="child_company_id", + ) + class TestAsyncCompanies: parametrize = pytest.mark.parametrize( @@ -413,3 +478,67 @@ async def test_streaming_response_list(self, async_client: AsyncWhop) -> None: assert_matches_type(AsyncCursorPage[CompanyListResponse], company, path=["response"]) assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_create_api_key(self, async_client: AsyncWhop) -> None: + company = await async_client.companies.create_api_key( + parent_company_id="parent_company_id", + child_company_id="child_company_id", + ) + assert_matches_type(CompanyCreateAPIKeyResponse, company, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_create_api_key_with_all_params(self, async_client: AsyncWhop) -> None: + company = await async_client.companies.create_api_key( + parent_company_id="parent_company_id", + child_company_id="child_company_id", + name="name", + permissions=[ + { + "actions": ["string"], + "grant": True, + "resources": ["string"], + } + ], + role="owner", + ) + assert_matches_type(CompanyCreateAPIKeyResponse, company, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_raw_response_create_api_key(self, async_client: AsyncWhop) -> None: + response = await async_client.companies.with_raw_response.create_api_key( + parent_company_id="parent_company_id", + child_company_id="child_company_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + company = await response.parse() + assert_matches_type(CompanyCreateAPIKeyResponse, company, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_streaming_response_create_api_key(self, async_client: AsyncWhop) -> None: + async with async_client.companies.with_streaming_response.create_api_key( + parent_company_id="parent_company_id", + child_company_id="child_company_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + company = await response.parse() + assert_matches_type(CompanyCreateAPIKeyResponse, company, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_path_params_create_api_key(self, async_client: AsyncWhop) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `parent_company_id` but received ''"): + await async_client.companies.with_raw_response.create_api_key( + parent_company_id="", + child_company_id="child_company_id", + ) diff --git a/tests/api_resources/test_conversions.py b/tests/api_resources/test_conversions.py new file mode 100644 index 00000000..3b63db11 --- /dev/null +++ b/tests/api_resources/test_conversions.py @@ -0,0 +1,215 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import os +from typing import Any, cast + +import pytest + +from whop_sdk import Whop, AsyncWhop +from tests.utils import assert_matches_type +from whop_sdk.types import ConversionCreateResponse +from whop_sdk._utils import parse_datetime + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestConversions: + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_create(self, client: Whop) -> None: + conversion = client.conversions.create( + company_id="biz_xxxxxxxxxxxxxx", + event_name="lead", + ) + assert_matches_type(ConversionCreateResponse, conversion, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_create_with_all_params(self, client: Whop) -> None: + conversion = client.conversions.create( + company_id="biz_xxxxxxxxxxxxxx", + event_name="lead", + action_source="email", + context={ + "ad_campaign_id": "ad_campaign_id", + "ad_id": "ad_id", + "ad_set_id": "ad_set_id", + "fbclid": "fbclid", + "fbp": "fbp", + "ga": "ga", + "gclid": "gclid", + "ig_sid": "ig_sid", + "ip_address": "ip_address", + "ttclid": "ttclid", + "ttp": "ttp", + "user_agent": "user_agent", + "utm_campaign": "utm_campaign", + "utm_content": "utm_content", + "utm_id": "utm_id", + "utm_medium": "utm_medium", + "utm_source": "utm_source", + "utm_term": "utm_term", + }, + currency="usd", + custom_name="custom_name", + event_id="evnt_xxxxxxxxxxxxx", + event_time=parse_datetime("2023-12-01T05:00:00.401Z"), + plan_id="plan_xxxxxxxxxxxxx", + product_id="prod_xxxxxxxxxxxxx", + referrer_url="referrer_url", + url="url", + user={ + "anonymous_id": "anonymous_id", + "birthdate": "1990-01-15", + "city": "city", + "country": "country", + "email": "email", + "external_id": "external_id", + "first_name": "first_name", + "gender": "male", + "last_name": "last_name", + "member_id": "mber_xxxxxxxxxxxxx", + "membership_id": "mem_xxxxxxxxxxxxxx", + "name": "name", + "phone": "phone", + "postal_code": "postal_code", + "state": "state", + "user_id": "user_xxxxxxxxxxxxx", + "username": "username", + }, + value=6.9, + ) + assert_matches_type(ConversionCreateResponse, conversion, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_raw_response_create(self, client: Whop) -> None: + response = client.conversions.with_raw_response.create( + company_id="biz_xxxxxxxxxxxxxx", + event_name="lead", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + conversion = response.parse() + assert_matches_type(ConversionCreateResponse, conversion, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_streaming_response_create(self, client: Whop) -> None: + with client.conversions.with_streaming_response.create( + company_id="biz_xxxxxxxxxxxxxx", + event_name="lead", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + conversion = response.parse() + assert_matches_type(ConversionCreateResponse, conversion, path=["response"]) + + assert cast(Any, response.is_closed) is True + + +class TestAsyncConversions: + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_create(self, async_client: AsyncWhop) -> None: + conversion = await async_client.conversions.create( + company_id="biz_xxxxxxxxxxxxxx", + event_name="lead", + ) + assert_matches_type(ConversionCreateResponse, conversion, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_create_with_all_params(self, async_client: AsyncWhop) -> None: + conversion = await async_client.conversions.create( + company_id="biz_xxxxxxxxxxxxxx", + event_name="lead", + action_source="email", + context={ + "ad_campaign_id": "ad_campaign_id", + "ad_id": "ad_id", + "ad_set_id": "ad_set_id", + "fbclid": "fbclid", + "fbp": "fbp", + "ga": "ga", + "gclid": "gclid", + "ig_sid": "ig_sid", + "ip_address": "ip_address", + "ttclid": "ttclid", + "ttp": "ttp", + "user_agent": "user_agent", + "utm_campaign": "utm_campaign", + "utm_content": "utm_content", + "utm_id": "utm_id", + "utm_medium": "utm_medium", + "utm_source": "utm_source", + "utm_term": "utm_term", + }, + currency="usd", + custom_name="custom_name", + event_id="evnt_xxxxxxxxxxxxx", + event_time=parse_datetime("2023-12-01T05:00:00.401Z"), + plan_id="plan_xxxxxxxxxxxxx", + product_id="prod_xxxxxxxxxxxxx", + referrer_url="referrer_url", + url="url", + user={ + "anonymous_id": "anonymous_id", + "birthdate": "1990-01-15", + "city": "city", + "country": "country", + "email": "email", + "external_id": "external_id", + "first_name": "first_name", + "gender": "male", + "last_name": "last_name", + "member_id": "mber_xxxxxxxxxxxxx", + "membership_id": "mem_xxxxxxxxxxxxxx", + "name": "name", + "phone": "phone", + "postal_code": "postal_code", + "state": "state", + "user_id": "user_xxxxxxxxxxxxx", + "username": "username", + }, + value=6.9, + ) + assert_matches_type(ConversionCreateResponse, conversion, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_raw_response_create(self, async_client: AsyncWhop) -> None: + response = await async_client.conversions.with_raw_response.create( + company_id="biz_xxxxxxxxxxxxxx", + event_name="lead", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + conversion = await response.parse() + assert_matches_type(ConversionCreateResponse, conversion, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_streaming_response_create(self, async_client: AsyncWhop) -> None: + async with async_client.conversions.with_streaming_response.create( + company_id="biz_xxxxxxxxxxxxxx", + event_name="lead", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + conversion = await response.parse() + assert_matches_type(ConversionCreateResponse, conversion, path=["response"]) + + assert cast(Any, response.is_closed) is True diff --git a/tests/api_resources/test_files.py b/tests/api_resources/test_files.py index bde3b745..5452a2b1 100644 --- a/tests/api_resources/test_files.py +++ b/tests/api_resources/test_files.py @@ -25,6 +25,15 @@ def test_method_create(self, client: Whop) -> None: ) assert_matches_type(FileCreateResponse, file, path=["response"]) + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_create_with_all_params(self, client: Whop) -> None: + file = client.files.create( + filename="filename", + visibility="public", + ) + assert_matches_type(FileCreateResponse, file, path=["response"]) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_raw_response_create(self, client: Whop) -> None: @@ -107,6 +116,15 @@ async def test_method_create(self, async_client: AsyncWhop) -> None: ) assert_matches_type(FileCreateResponse, file, path=["response"]) + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_create_with_all_params(self, async_client: AsyncWhop) -> None: + file = await async_client.files.create( + filename="filename", + visibility="public", + ) + assert_matches_type(FileCreateResponse, file, path=["response"]) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_raw_response_create(self, async_client: AsyncWhop) -> None: diff --git a/tests/api_resources/test_forum_posts.py b/tests/api_resources/test_forum_posts.py index ead26f5a..92281488 100644 --- a/tests/api_resources/test_forum_posts.py +++ b/tests/api_resources/test_forum_posts.py @@ -194,6 +194,7 @@ def test_method_list_with_all_params(self, client: Whop) -> None: after="after", before="before", first=42, + include_bounty_anchors=True, last=42, parent_id="parent_id", pinned=True, @@ -405,6 +406,7 @@ async def test_method_list_with_all_params(self, async_client: AsyncWhop) -> Non after="after", before="before", first=42, + include_bounty_anchors=True, last=42, parent_id="parent_id", pinned=True, diff --git a/tests/api_resources/test_invoices.py b/tests/api_resources/test_invoices.py index 96ec3e2f..847de39b 100644 --- a/tests/api_resources/test_invoices.py +++ b/tests/api_resources/test_invoices.py @@ -367,6 +367,7 @@ def test_method_update_with_all_params(self, client: Whop) -> None: "unlimited_stock": True, "visibility": "visible", }, + product_id="prod_xxxxxxxxxxxxx", subscription_billing_anchor_at=parse_datetime("2023-12-01T05:00:00.401Z"), ) assert_matches_type(Invoice, invoice, path=["response"]) @@ -974,6 +975,7 @@ async def test_method_update_with_all_params(self, async_client: AsyncWhop) -> N "unlimited_stock": True, "visibility": "visible", }, + product_id="prod_xxxxxxxxxxxxx", subscription_billing_anchor_at=parse_datetime("2023-12-01T05:00:00.401Z"), ) assert_matches_type(Invoice, invoice, path=["response"]) diff --git a/tests/api_resources/test_plans.py b/tests/api_resources/test_plans.py index 33d2cfcd..fbe3db1b 100644 --- a/tests/api_resources/test_plans.py +++ b/tests/api_resources/test_plans.py @@ -38,8 +38,10 @@ def test_method_create_with_all_params(self, client: Whop) -> None: plan = client.plans.create( company_id="biz_xxxxxxxxxxxxxx", product_id="prod_xxxxxxxxxxxxx", + adaptive_pricing_enabled=True, billing_period=42, checkout_styling={ + "background_color": "background_color", "border_style": "rounded", "button_color": "button_color", "font_family": "system", @@ -162,8 +164,10 @@ def test_method_update(self, client: Whop) -> None: def test_method_update_with_all_params(self, client: Whop) -> None: plan = client.plans.update( id="plan_xxxxxxxxxxxxx", + adaptive_pricing_enabled=True, billing_period=42, checkout_styling={ + "background_color": "background_color", "border_style": "rounded", "button_color": "button_color", "font_family": "system", @@ -354,8 +358,10 @@ async def test_method_create_with_all_params(self, async_client: AsyncWhop) -> N plan = await async_client.plans.create( company_id="biz_xxxxxxxxxxxxxx", product_id="prod_xxxxxxxxxxxxx", + adaptive_pricing_enabled=True, billing_period=42, checkout_styling={ + "background_color": "background_color", "border_style": "rounded", "button_color": "button_color", "font_family": "system", @@ -478,8 +484,10 @@ async def test_method_update(self, async_client: AsyncWhop) -> None: async def test_method_update_with_all_params(self, async_client: AsyncWhop) -> None: plan = await async_client.plans.update( id="plan_xxxxxxxxxxxxx", + adaptive_pricing_enabled=True, billing_period=42, checkout_styling={ + "background_color": "background_color", "border_style": "rounded", "button_color": "button_color", "font_family": "system", diff --git a/tests/api_resources/test_stats.py b/tests/api_resources/test_stats.py new file mode 100644 index 00000000..064af462 --- /dev/null +++ b/tests/api_resources/test_stats.py @@ -0,0 +1,414 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import os +from typing import Any, cast + +import pytest + +from whop_sdk import Whop, AsyncWhop +from tests.utils import assert_matches_type +from whop_sdk.types import ( + StatRunSqlResponse, + StatDescribeResponse, + StatQueryRawResponse, + StatQueryMetricResponse, +) +from whop_sdk._utils import parse_datetime + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestStats: + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_describe(self, client: Whop) -> None: + stat = client.stats.describe() + assert_matches_type(StatDescribeResponse, stat, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_describe_with_all_params(self, client: Whop) -> None: + stat = client.stats.describe( + company_id="biz_xxxxxxxxxxxxxx", + resource="resource", + user_id="user_xxxxxxxxxxxxx", + ) + assert_matches_type(StatDescribeResponse, stat, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_raw_response_describe(self, client: Whop) -> None: + response = client.stats.with_raw_response.describe() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + stat = response.parse() + assert_matches_type(StatDescribeResponse, stat, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_streaming_response_describe(self, client: Whop) -> None: + with client.stats.with_streaming_response.describe() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + stat = response.parse() + assert_matches_type(StatDescribeResponse, stat, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_query_metric(self, client: Whop) -> None: + stat = client.stats.query_metric( + resource="resource", + ) + assert_matches_type(StatQueryMetricResponse, stat, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_query_metric_with_all_params(self, client: Whop) -> None: + stat = client.stats.query_metric( + resource="resource", + breakdowns=["string"], + company_id="biz_xxxxxxxxxxxxxx", + filters={"foo": "bar"}, + from_=parse_datetime("2023-12-01T05:00:00.401Z"), + granularity="granularity", + time_zone="time_zone", + to=parse_datetime("2023-12-01T05:00:00.401Z"), + user_id="user_xxxxxxxxxxxxx", + ) + assert_matches_type(StatQueryMetricResponse, stat, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_raw_response_query_metric(self, client: Whop) -> None: + response = client.stats.with_raw_response.query_metric( + resource="resource", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + stat = response.parse() + assert_matches_type(StatQueryMetricResponse, stat, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_streaming_response_query_metric(self, client: Whop) -> None: + with client.stats.with_streaming_response.query_metric( + resource="resource", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + stat = response.parse() + assert_matches_type(StatQueryMetricResponse, stat, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_query_raw(self, client: Whop) -> None: + stat = client.stats.query_raw( + resource="resource", + ) + assert_matches_type(StatQueryRawResponse, stat, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_query_raw_with_all_params(self, client: Whop) -> None: + stat = client.stats.query_raw( + resource="resource", + company_id="biz_xxxxxxxxxxxxxx", + cursor="cursor", + from_=parse_datetime("2023-12-01T05:00:00.401Z"), + limit=42, + sort="sort", + sort_direction="asc", + to=parse_datetime("2023-12-01T05:00:00.401Z"), + user_id="user_xxxxxxxxxxxxx", + ) + assert_matches_type(StatQueryRawResponse, stat, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_raw_response_query_raw(self, client: Whop) -> None: + response = client.stats.with_raw_response.query_raw( + resource="resource", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + stat = response.parse() + assert_matches_type(StatQueryRawResponse, stat, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_streaming_response_query_raw(self, client: Whop) -> None: + with client.stats.with_streaming_response.query_raw( + resource="resource", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + stat = response.parse() + assert_matches_type(StatQueryRawResponse, stat, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_run_sql(self, client: Whop) -> None: + stat = client.stats.run_sql( + resource="resource", + sql="sql", + ) + assert_matches_type(StatRunSqlResponse, stat, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_run_sql_with_all_params(self, client: Whop) -> None: + stat = client.stats.run_sql( + resource="resource", + sql="sql", + company_id="biz_xxxxxxxxxxxxxx", + cursor="cursor", + from_=parse_datetime("2023-12-01T05:00:00.401Z"), + limit=42, + sort="sort", + sort_direction="asc", + to=parse_datetime("2023-12-01T05:00:00.401Z"), + user_id="user_xxxxxxxxxxxxx", + ) + assert_matches_type(StatRunSqlResponse, stat, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_raw_response_run_sql(self, client: Whop) -> None: + response = client.stats.with_raw_response.run_sql( + resource="resource", + sql="sql", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + stat = response.parse() + assert_matches_type(StatRunSqlResponse, stat, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_streaming_response_run_sql(self, client: Whop) -> None: + with client.stats.with_streaming_response.run_sql( + resource="resource", + sql="sql", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + stat = response.parse() + assert_matches_type(StatRunSqlResponse, stat, path=["response"]) + + assert cast(Any, response.is_closed) is True + + +class TestAsyncStats: + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_describe(self, async_client: AsyncWhop) -> None: + stat = await async_client.stats.describe() + assert_matches_type(StatDescribeResponse, stat, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_describe_with_all_params(self, async_client: AsyncWhop) -> None: + stat = await async_client.stats.describe( + company_id="biz_xxxxxxxxxxxxxx", + resource="resource", + user_id="user_xxxxxxxxxxxxx", + ) + assert_matches_type(StatDescribeResponse, stat, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_raw_response_describe(self, async_client: AsyncWhop) -> None: + response = await async_client.stats.with_raw_response.describe() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + stat = await response.parse() + assert_matches_type(StatDescribeResponse, stat, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_streaming_response_describe(self, async_client: AsyncWhop) -> None: + async with async_client.stats.with_streaming_response.describe() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + stat = await response.parse() + assert_matches_type(StatDescribeResponse, stat, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_query_metric(self, async_client: AsyncWhop) -> None: + stat = await async_client.stats.query_metric( + resource="resource", + ) + assert_matches_type(StatQueryMetricResponse, stat, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_query_metric_with_all_params(self, async_client: AsyncWhop) -> None: + stat = await async_client.stats.query_metric( + resource="resource", + breakdowns=["string"], + company_id="biz_xxxxxxxxxxxxxx", + filters={"foo": "bar"}, + from_=parse_datetime("2023-12-01T05:00:00.401Z"), + granularity="granularity", + time_zone="time_zone", + to=parse_datetime("2023-12-01T05:00:00.401Z"), + user_id="user_xxxxxxxxxxxxx", + ) + assert_matches_type(StatQueryMetricResponse, stat, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_raw_response_query_metric(self, async_client: AsyncWhop) -> None: + response = await async_client.stats.with_raw_response.query_metric( + resource="resource", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + stat = await response.parse() + assert_matches_type(StatQueryMetricResponse, stat, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_streaming_response_query_metric(self, async_client: AsyncWhop) -> None: + async with async_client.stats.with_streaming_response.query_metric( + resource="resource", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + stat = await response.parse() + assert_matches_type(StatQueryMetricResponse, stat, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_query_raw(self, async_client: AsyncWhop) -> None: + stat = await async_client.stats.query_raw( + resource="resource", + ) + assert_matches_type(StatQueryRawResponse, stat, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_query_raw_with_all_params(self, async_client: AsyncWhop) -> None: + stat = await async_client.stats.query_raw( + resource="resource", + company_id="biz_xxxxxxxxxxxxxx", + cursor="cursor", + from_=parse_datetime("2023-12-01T05:00:00.401Z"), + limit=42, + sort="sort", + sort_direction="asc", + to=parse_datetime("2023-12-01T05:00:00.401Z"), + user_id="user_xxxxxxxxxxxxx", + ) + assert_matches_type(StatQueryRawResponse, stat, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_raw_response_query_raw(self, async_client: AsyncWhop) -> None: + response = await async_client.stats.with_raw_response.query_raw( + resource="resource", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + stat = await response.parse() + assert_matches_type(StatQueryRawResponse, stat, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_streaming_response_query_raw(self, async_client: AsyncWhop) -> None: + async with async_client.stats.with_streaming_response.query_raw( + resource="resource", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + stat = await response.parse() + assert_matches_type(StatQueryRawResponse, stat, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_run_sql(self, async_client: AsyncWhop) -> None: + stat = await async_client.stats.run_sql( + resource="resource", + sql="sql", + ) + assert_matches_type(StatRunSqlResponse, stat, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_run_sql_with_all_params(self, async_client: AsyncWhop) -> None: + stat = await async_client.stats.run_sql( + resource="resource", + sql="sql", + company_id="biz_xxxxxxxxxxxxxx", + cursor="cursor", + from_=parse_datetime("2023-12-01T05:00:00.401Z"), + limit=42, + sort="sort", + sort_direction="asc", + to=parse_datetime("2023-12-01T05:00:00.401Z"), + user_id="user_xxxxxxxxxxxxx", + ) + assert_matches_type(StatRunSqlResponse, stat, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_raw_response_run_sql(self, async_client: AsyncWhop) -> None: + response = await async_client.stats.with_raw_response.run_sql( + resource="resource", + sql="sql", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + stat = await response.parse() + assert_matches_type(StatRunSqlResponse, stat, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_streaming_response_run_sql(self, async_client: AsyncWhop) -> None: + async with async_client.stats.with_streaming_response.run_sql( + resource="resource", + sql="sql", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + stat = await response.parse() + assert_matches_type(StatRunSqlResponse, stat, path=["response"]) + + assert cast(Any, response.is_closed) is True diff --git a/tests/api_resources/test_support_channels.py b/tests/api_resources/test_support_channels.py index 88f40141..272e8e5a 100644 --- a/tests/api_resources/test_support_channels.py +++ b/tests/api_resources/test_support_channels.py @@ -113,32 +113,29 @@ def test_path_params_retrieve(self, client: Whop) -> None: @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_method_list(self, client: Whop) -> None: - support_channel = client.support_channels.list( - company_id="biz_xxxxxxxxxxxxxx", - ) + support_channel = client.support_channels.list() assert_matches_type(SyncCursorPage[SupportChannelListResponse], support_channel, path=["response"]) @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_method_list_with_all_params(self, client: Whop) -> None: support_channel = client.support_channels.list( - company_id="biz_xxxxxxxxxxxxxx", after="after", before="before", + company_id="biz_xxxxxxxxxxxxxx", direction="asc", first=42, last=42, open=True, order="created_at", + view="all", ) assert_matches_type(SyncCursorPage[SupportChannelListResponse], support_channel, path=["response"]) @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_raw_response_list(self, client: Whop) -> None: - response = client.support_channels.with_raw_response.list( - company_id="biz_xxxxxxxxxxxxxx", - ) + response = client.support_channels.with_raw_response.list() assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -148,9 +145,7 @@ def test_raw_response_list(self, client: Whop) -> None: @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_streaming_response_list(self, client: Whop) -> None: - with client.support_channels.with_streaming_response.list( - company_id="biz_xxxxxxxxxxxxxx", - ) as response: + with client.support_channels.with_streaming_response.list() as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -257,32 +252,29 @@ async def test_path_params_retrieve(self, async_client: AsyncWhop) -> None: @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_method_list(self, async_client: AsyncWhop) -> None: - support_channel = await async_client.support_channels.list( - company_id="biz_xxxxxxxxxxxxxx", - ) + support_channel = await async_client.support_channels.list() assert_matches_type(AsyncCursorPage[SupportChannelListResponse], support_channel, path=["response"]) @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_method_list_with_all_params(self, async_client: AsyncWhop) -> None: support_channel = await async_client.support_channels.list( - company_id="biz_xxxxxxxxxxxxxx", after="after", before="before", + company_id="biz_xxxxxxxxxxxxxx", direction="asc", first=42, last=42, open=True, order="created_at", + view="all", ) assert_matches_type(AsyncCursorPage[SupportChannelListResponse], support_channel, path=["response"]) @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_raw_response_list(self, async_client: AsyncWhop) -> None: - response = await async_client.support_channels.with_raw_response.list( - company_id="biz_xxxxxxxxxxxxxx", - ) + response = await async_client.support_channels.with_raw_response.list() assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -292,9 +284,7 @@ async def test_raw_response_list(self, async_client: AsyncWhop) -> None: @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_streaming_response_list(self, async_client: AsyncWhop) -> None: - async with async_client.support_channels.with_streaming_response.list( - company_id="biz_xxxxxxxxxxxxxx", - ) as response: + async with async_client.support_channels.with_streaming_response.list() as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" diff --git a/tests/test_extract_files.py b/tests/test_extract_files.py index f8ca177c..fd472434 100644 --- a/tests/test_extract_files.py +++ b/tests/test_extract_files.py @@ -4,7 +4,7 @@ import pytest -from whop_sdk._types import FileTypes +from whop_sdk._types import FileTypes, ArrayFormat from whop_sdk._utils import extract_files @@ -37,10 +37,7 @@ def test_multiple_files() -> None: def test_top_level_file_array() -> None: query = {"files": [b"file one", b"file two"], "title": "hello"} - assert extract_files(query, paths=[["files", ""]]) == [ - ("files[]", b"file one"), - ("files[]", b"file two"), - ] + assert extract_files(query, paths=[["files", ""]]) == [("files[]", b"file one"), ("files[]", b"file two")] assert query == {"title": "hello"} @@ -71,3 +68,24 @@ def test_ignores_incorrect_paths( expected: list[tuple[str, FileTypes]], ) -> None: assert extract_files(query, paths=paths) == expected + + +@pytest.mark.parametrize( + "array_format,expected_top_level,expected_nested", + [ + ("brackets", [("files[]", b"a"), ("files[]", b"b")], [("items[][file]", b"a"), ("items[][file]", b"b")]), + ("repeat", [("files", b"a"), ("files", b"b")], [("items[file]", b"a"), ("items[file]", b"b")]), + ("comma", [("files", b"a"), ("files", b"b")], [("items[file]", b"a"), ("items[file]", b"b")]), + ("indices", [("files[0]", b"a"), ("files[1]", b"b")], [("items[0][file]", b"a"), ("items[1][file]", b"b")]), + ], +) +def test_array_format_controls_file_field_names( + array_format: ArrayFormat, + expected_top_level: list[tuple[str, FileTypes]], + expected_nested: list[tuple[str, FileTypes]], +) -> None: + top_level = {"files": [b"a", b"b"]} + assert extract_files(top_level, paths=[["files", ""]], array_format=array_format) == expected_top_level + + nested = {"items": [{"file": b"a"}, {"file": b"b"}]} + assert extract_files(nested, paths=[["items", "", "file"]], array_format=array_format) == expected_nested diff --git a/tests/test_files.py b/tests/test_files.py index ff5cf46d..2abb7565 100644 --- a/tests/test_files.py +++ b/tests/test_files.py @@ -131,7 +131,7 @@ def test_extract_files_does_not_mutate_original_nested_array_path(self) -> None: copied = deepcopy_with_paths(original, [["items", "", "file"]]) extracted = extract_files(copied, paths=[["items", "", "file"]]) - assert extracted == [("items[][file]", file1), ("items[][file]", file2)] + assert [entry for _, entry in extracted] == [file1, file2] assert original == { "items": [ {"file": file1, "extra": 1},