diff --git a/.release-please-manifest.json b/.release-please-manifest.json
index c7159c1a..28e831b6 100644
--- a/.release-please-manifest.json
+++ b/.release-please-manifest.json
@@ -1,3 +1,3 @@
{
- ".": "0.0.2"
+ ".": "0.0.3"
}
\ No newline at end of file
diff --git a/.stats.yml b/.stats.yml
index ff4a3e82..1125bba4 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,4 +1,4 @@
-configured_endpoints: 81
-openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/frostedinc%2Fwhopsdk-e8bcfd8a19f54624e644a1bc061df234a2e12af010a6c595d5a698cdd0e20682.yml
-openapi_spec_hash: b47bb36fbc4c9ebdf3cf6b06f33356ef
-config_hash: 131e5271eed10cf11ebc421d5a0c5f8a
+configured_endpoints: 105
+openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/frostedinc%2Fwhopsdk-ef12082e1e61bb15be49ade1364442c5c96bf93c23614e7b39142a8521fd02b7.yml
+openapi_spec_hash: c242c4c29807f5967f6ea1247e199dff
+config_hash: ba820fe26ad022ecb70dcfe483706a7c
diff --git a/CHANGELOG.md b/CHANGELOG.md
index c5c855ac..5fecd652 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,28 @@
# Changelog
+## 0.0.3 (2025-10-28)
+
+Full Changelog: [v0.0.2...v0.0.3](https://github.com/whopio/whopsdk-python/compare/v0.0.2...v0.0.3)
+
+### Features
+
+* **api:** api update ([cb26567](https://github.com/whopio/whopsdk-python/commit/cb26567d0790a6b14a82a7b031e6319bc467af03))
+* **api:** api update ([9d1c491](https://github.com/whopio/whopsdk-python/commit/9d1c491cc58672b4d7bb54d14717704b5dda8d73))
+* **api:** api update ([fb03516](https://github.com/whopio/whopsdk-python/commit/fb03516f3ca1c79848e16fbf21f780fb76db226d))
+* **api:** api update ([e4d5e22](https://github.com/whopio/whopsdk-python/commit/e4d5e222ffc757b3255624c5824abcdc8312ea37))
+* **api:** api update ([88d7744](https://github.com/whopio/whopsdk-python/commit/88d7744347e459d509142bf983770cb5086b06c9))
+* **api:** api update ([853834f](https://github.com/whopio/whopsdk-python/commit/853834f283692e15c9539869b157fb42244ed8b0))
+* **api:** api update ([b8cb3de](https://github.com/whopio/whopsdk-python/commit/b8cb3de04d4122a4054a1f56cfee7637a0a1d0af))
+* **api:** api update ([06fcd76](https://github.com/whopio/whopsdk-python/commit/06fcd76e5f7c84b1d6a2dda07464867261ea9b58))
+* **api:** api update ([8708559](https://github.com/whopio/whopsdk-python/commit/87085590a3ce6debdf321045f88b99ee3625ca48))
+* **api:** api update ([bf6a189](https://github.com/whopio/whopsdk-python/commit/bf6a18904aaf4291f76e665be08fc39aff2f3731))
+* **api:** api update ([4d649cf](https://github.com/whopio/whopsdk-python/commit/4d649cf643820cbe60a4847c5eacba760d3b90ef))
+* **api:** api update ([986b31c](https://github.com/whopio/whopsdk-python/commit/986b31c8e8ddc6f621c8e737eeb5ba24be4ea648))
+* **api:** api update ([b7f500d](https://github.com/whopio/whopsdk-python/commit/b7f500dcaa5daa83d6d236e216f130fc6c4db29e))
+* **api:** api update ([e27cde0](https://github.com/whopio/whopsdk-python/commit/e27cde011cdda82c57f242acdd20ac7955619373))
+* **api:** manual updates ([981c0df](https://github.com/whopio/whopsdk-python/commit/981c0dfb7441c2322c75e2dd6620b5064dd294ec))
+* **api:** manual updates ([c41adf9](https://github.com/whopio/whopsdk-python/commit/c41adf99358901411a5947d09a691ab0f1d83f99))
+
## 0.0.2 (2025-10-22)
Full Changelog: [v0.0.1...v0.0.2](https://github.com/whopio/whopsdk-python/compare/v0.0.1...v0.0.2)
diff --git a/api.md b/api.md
index 4195aa21..c2cd84ae 100644
--- a/api.md
+++ b/api.md
@@ -191,6 +191,7 @@ Methods:
- client.forum_posts.create(\*\*params) -> ForumPost
- client.forum_posts.retrieve(id) -> ForumPost
+- client.forum_posts.update(id, \*\*params) -> ForumPost
- client.forum_posts.list(\*\*params) -> SyncCursorPage[ForumPostListResponse]
# Transfers
@@ -304,6 +305,7 @@ Methods:
- client.messages.create(\*\*params) -> Message
- client.messages.retrieve(id) -> Message
+- client.messages.update(id, \*\*params) -> Message
- client.messages.list(\*\*params) -> SyncCursorPage[MessageListResponse]
# ChatChannels
@@ -338,7 +340,7 @@ Methods:
Types:
```python
-from whop_sdk.types import PaymentListResponse
+from whop_sdk.types import BillingReasons, CardBrands, PaymentMethodTypes, PaymentListResponse
```
Methods:
@@ -380,6 +382,7 @@ Methods:
- client.experiences.delete(id) -> ExperienceDeleteResponse
- client.experiences.attach(id, \*\*params) -> Experience
- client.experiences.detach(id, \*\*params) -> Experience
+- client.experiences.duplicate(id, \*\*params) -> Experience
# Reactions
@@ -421,3 +424,92 @@ Methods:
- client.forums.retrieve(id) -> Forum
- client.forums.update(id, \*\*params) -> Forum
- client.forums.list(\*\*params) -> SyncCursorPage[ForumListResponse]
+
+# PromoCodes
+
+Types:
+
+```python
+from whop_sdk.types import (
+ PromoCode,
+ PromoCodeStatus,
+ PromoDuration,
+ PromoCodeListResponse,
+ PromoCodeDeleteResponse,
+)
+```
+
+Methods:
+
+- client.promo_codes.create(\*\*params) -> PromoCode
+- client.promo_codes.retrieve(id) -> PromoCode
+- client.promo_codes.list(\*\*params) -> SyncCursorPage[PromoCodeListResponse]
+- client.promo_codes.delete(id) -> PromoCodeDeleteResponse
+
+# Courses
+
+Types:
+
+```python
+from whop_sdk.types import Course, Languages, CourseListResponse, CourseDeleteResponse
+```
+
+Methods:
+
+- client.courses.create(\*\*params) -> Course
+- client.courses.retrieve(id) -> Course
+- client.courses.update(id, \*\*params) -> Course
+- client.courses.list(\*\*params) -> SyncCursorPage[CourseListResponse]
+- client.courses.delete(id) -> CourseDeleteResponse
+
+# CourseChapters
+
+Types:
+
+```python
+from whop_sdk.types import CourseChapter, CourseChapterListResponse, CourseChapterDeleteResponse
+```
+
+Methods:
+
+- client.course_chapters.create(\*\*params) -> CourseChapter
+- client.course_chapters.retrieve(id) -> CourseChapter
+- client.course_chapters.update(id, \*\*params) -> CourseChapter
+- client.course_chapters.list(\*\*params) -> SyncCursorPage[CourseChapterListResponse]
+- client.course_chapters.delete(id) -> CourseChapterDeleteResponse
+
+# CourseLessons
+
+Types:
+
+```python
+from whop_sdk.types import (
+ AssessmentQuestionTypes,
+ Lesson,
+ LessonTypes,
+ LessonVisibilities,
+ CourseLessonListResponse,
+ CourseLessonDeleteResponse,
+)
+```
+
+Methods:
+
+- client.course_lessons.create(\*\*params) -> Lesson
+- client.course_lessons.retrieve(id) -> Lesson
+- client.course_lessons.update(id, \*\*params) -> Lesson
+- client.course_lessons.list(\*\*params) -> SyncCursorPage[CourseLessonListResponse]
+- client.course_lessons.delete(id) -> CourseLessonDeleteResponse
+
+# Reviews
+
+Types:
+
+```python
+from whop_sdk.types import ReviewStatus, ReviewRetrieveResponse, ReviewListResponse
+```
+
+Methods:
+
+- client.reviews.retrieve(id) -> ReviewRetrieveResponse
+- client.reviews.list(\*\*params) -> SyncCursorPage[ReviewListResponse]
diff --git a/pyproject.toml b/pyproject.toml
index 1af33a1c..954e784c 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,6 +1,6 @@
[project]
name = "whop-sdk"
-version = "0.0.2"
+version = "0.0.3"
description = "The official Python library for the Whop API"
dynamic = ["readme"]
license = "Apache-2.0"
diff --git a/src/whop_sdk/_client.py b/src/whop_sdk/_client.py
index 196ca7fb..1ef88fa1 100644
--- a/src/whop_sdk/_client.py
+++ b/src/whop_sdk/_client.py
@@ -26,8 +26,10 @@
plans,
users,
forums,
+ courses,
entries,
members,
+ reviews,
invoices,
messages,
payments,
@@ -41,7 +43,10 @@
experiences,
forum_posts,
memberships,
+ promo_codes,
chat_channels,
+ course_lessons,
+ course_chapters,
ledger_accounts,
authorized_users,
support_channels,
@@ -85,6 +90,11 @@ class Whop(SyncAPIClient):
reactions: reactions.ReactionsResource
members: members.MembersResource
forums: forums.ForumsResource
+ promo_codes: promo_codes.PromoCodesResource
+ courses: courses.CoursesResource
+ course_chapters: course_chapters.CourseChaptersResource
+ course_lessons: course_lessons.CourseLessonsResource
+ reviews: reviews.ReviewsResource
with_raw_response: WhopWithRawResponse
with_streaming_response: WhopWithStreamedResponse
@@ -186,13 +196,18 @@ def __init__(
self.reactions = reactions.ReactionsResource(self)
self.members = members.MembersResource(self)
self.forums = forums.ForumsResource(self)
+ self.promo_codes = promo_codes.PromoCodesResource(self)
+ self.courses = courses.CoursesResource(self)
+ self.course_chapters = course_chapters.CourseChaptersResource(self)
+ self.course_lessons = course_lessons.CourseLessonsResource(self)
+ self.reviews = reviews.ReviewsResource(self)
self.with_raw_response = WhopWithRawResponse(self)
self.with_streaming_response = WhopWithStreamedResponse(self)
@property
@override
def qs(self) -> Querystring:
- return Querystring(array_format="comma")
+ return Querystring(array_format="brackets")
@property
@override
@@ -325,6 +340,11 @@ class AsyncWhop(AsyncAPIClient):
reactions: reactions.AsyncReactionsResource
members: members.AsyncMembersResource
forums: forums.AsyncForumsResource
+ promo_codes: promo_codes.AsyncPromoCodesResource
+ courses: courses.AsyncCoursesResource
+ course_chapters: course_chapters.AsyncCourseChaptersResource
+ course_lessons: course_lessons.AsyncCourseLessonsResource
+ reviews: reviews.AsyncReviewsResource
with_raw_response: AsyncWhopWithRawResponse
with_streaming_response: AsyncWhopWithStreamedResponse
@@ -426,13 +446,18 @@ def __init__(
self.reactions = reactions.AsyncReactionsResource(self)
self.members = members.AsyncMembersResource(self)
self.forums = forums.AsyncForumsResource(self)
+ self.promo_codes = promo_codes.AsyncPromoCodesResource(self)
+ self.courses = courses.AsyncCoursesResource(self)
+ self.course_chapters = course_chapters.AsyncCourseChaptersResource(self)
+ self.course_lessons = course_lessons.AsyncCourseLessonsResource(self)
+ self.reviews = reviews.AsyncReviewsResource(self)
self.with_raw_response = AsyncWhopWithRawResponse(self)
self.with_streaming_response = AsyncWhopWithStreamedResponse(self)
@property
@override
def qs(self) -> Querystring:
- return Querystring(array_format="comma")
+ return Querystring(array_format="brackets")
@property
@override
@@ -569,6 +594,11 @@ def __init__(self, client: Whop) -> None:
self.reactions = reactions.ReactionsResourceWithRawResponse(client.reactions)
self.members = members.MembersResourceWithRawResponse(client.members)
self.forums = forums.ForumsResourceWithRawResponse(client.forums)
+ self.promo_codes = promo_codes.PromoCodesResourceWithRawResponse(client.promo_codes)
+ self.courses = courses.CoursesResourceWithRawResponse(client.courses)
+ self.course_chapters = course_chapters.CourseChaptersResourceWithRawResponse(client.course_chapters)
+ self.course_lessons = course_lessons.CourseLessonsResourceWithRawResponse(client.course_lessons)
+ self.reviews = reviews.ReviewsResourceWithRawResponse(client.reviews)
class AsyncWhopWithRawResponse:
@@ -603,6 +633,11 @@ def __init__(self, client: AsyncWhop) -> None:
self.reactions = reactions.AsyncReactionsResourceWithRawResponse(client.reactions)
self.members = members.AsyncMembersResourceWithRawResponse(client.members)
self.forums = forums.AsyncForumsResourceWithRawResponse(client.forums)
+ self.promo_codes = promo_codes.AsyncPromoCodesResourceWithRawResponse(client.promo_codes)
+ self.courses = courses.AsyncCoursesResourceWithRawResponse(client.courses)
+ self.course_chapters = course_chapters.AsyncCourseChaptersResourceWithRawResponse(client.course_chapters)
+ self.course_lessons = course_lessons.AsyncCourseLessonsResourceWithRawResponse(client.course_lessons)
+ self.reviews = reviews.AsyncReviewsResourceWithRawResponse(client.reviews)
class WhopWithStreamedResponse:
@@ -637,6 +672,11 @@ def __init__(self, client: Whop) -> None:
self.reactions = reactions.ReactionsResourceWithStreamingResponse(client.reactions)
self.members = members.MembersResourceWithStreamingResponse(client.members)
self.forums = forums.ForumsResourceWithStreamingResponse(client.forums)
+ self.promo_codes = promo_codes.PromoCodesResourceWithStreamingResponse(client.promo_codes)
+ self.courses = courses.CoursesResourceWithStreamingResponse(client.courses)
+ self.course_chapters = course_chapters.CourseChaptersResourceWithStreamingResponse(client.course_chapters)
+ self.course_lessons = course_lessons.CourseLessonsResourceWithStreamingResponse(client.course_lessons)
+ self.reviews = reviews.ReviewsResourceWithStreamingResponse(client.reviews)
class AsyncWhopWithStreamedResponse:
@@ -675,6 +715,11 @@ def __init__(self, client: AsyncWhop) -> None:
self.reactions = reactions.AsyncReactionsResourceWithStreamingResponse(client.reactions)
self.members = members.AsyncMembersResourceWithStreamingResponse(client.members)
self.forums = forums.AsyncForumsResourceWithStreamingResponse(client.forums)
+ self.promo_codes = promo_codes.AsyncPromoCodesResourceWithStreamingResponse(client.promo_codes)
+ self.courses = courses.AsyncCoursesResourceWithStreamingResponse(client.courses)
+ self.course_chapters = course_chapters.AsyncCourseChaptersResourceWithStreamingResponse(client.course_chapters)
+ self.course_lessons = course_lessons.AsyncCourseLessonsResourceWithStreamingResponse(client.course_lessons)
+ self.reviews = reviews.AsyncReviewsResourceWithStreamingResponse(client.reviews)
Client = Whop
diff --git a/src/whop_sdk/_version.py b/src/whop_sdk/_version.py
index d3607fd9..a12e2cff 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.2" # x-release-please-version
+__version__ = "0.0.3" # x-release-please-version
diff --git a/src/whop_sdk/resources/__init__.py b/src/whop_sdk/resources/__init__.py
index f3d55211..e6d4acc8 100644
--- a/src/whop_sdk/resources/__init__.py
+++ b/src/whop_sdk/resources/__init__.py
@@ -32,6 +32,14 @@
ForumsResourceWithStreamingResponse,
AsyncForumsResourceWithStreamingResponse,
)
+from .courses import (
+ CoursesResource,
+ AsyncCoursesResource,
+ CoursesResourceWithRawResponse,
+ AsyncCoursesResourceWithRawResponse,
+ CoursesResourceWithStreamingResponse,
+ AsyncCoursesResourceWithStreamingResponse,
+)
from .entries import (
EntriesResource,
AsyncEntriesResource,
@@ -48,6 +56,14 @@
MembersResourceWithStreamingResponse,
AsyncMembersResourceWithStreamingResponse,
)
+from .reviews import (
+ ReviewsResource,
+ AsyncReviewsResource,
+ ReviewsResourceWithRawResponse,
+ AsyncReviewsResourceWithRawResponse,
+ ReviewsResourceWithStreamingResponse,
+ AsyncReviewsResourceWithStreamingResponse,
+)
from .invoices import (
InvoicesResource,
AsyncInvoicesResource,
@@ -145,6 +161,14 @@
MembershipsResourceWithStreamingResponse,
AsyncMembershipsResourceWithStreamingResponse,
)
+from .promo_codes import (
+ PromoCodesResource,
+ AsyncPromoCodesResource,
+ PromoCodesResourceWithRawResponse,
+ AsyncPromoCodesResourceWithRawResponse,
+ PromoCodesResourceWithStreamingResponse,
+ AsyncPromoCodesResourceWithStreamingResponse,
+)
from .chat_channels import (
ChatChannelsResource,
AsyncChatChannelsResource,
@@ -153,6 +177,22 @@
ChatChannelsResourceWithStreamingResponse,
AsyncChatChannelsResourceWithStreamingResponse,
)
+from .course_lessons import (
+ CourseLessonsResource,
+ AsyncCourseLessonsResource,
+ CourseLessonsResourceWithRawResponse,
+ AsyncCourseLessonsResourceWithRawResponse,
+ CourseLessonsResourceWithStreamingResponse,
+ AsyncCourseLessonsResourceWithStreamingResponse,
+)
+from .course_chapters import (
+ CourseChaptersResource,
+ AsyncCourseChaptersResource,
+ CourseChaptersResourceWithRawResponse,
+ AsyncCourseChaptersResourceWithRawResponse,
+ CourseChaptersResourceWithStreamingResponse,
+ AsyncCourseChaptersResourceWithStreamingResponse,
+)
from .ledger_accounts import (
LedgerAccountsResource,
AsyncLedgerAccountsResource,
@@ -341,4 +381,34 @@
"AsyncForumsResourceWithRawResponse",
"ForumsResourceWithStreamingResponse",
"AsyncForumsResourceWithStreamingResponse",
+ "PromoCodesResource",
+ "AsyncPromoCodesResource",
+ "PromoCodesResourceWithRawResponse",
+ "AsyncPromoCodesResourceWithRawResponse",
+ "PromoCodesResourceWithStreamingResponse",
+ "AsyncPromoCodesResourceWithStreamingResponse",
+ "CoursesResource",
+ "AsyncCoursesResource",
+ "CoursesResourceWithRawResponse",
+ "AsyncCoursesResourceWithRawResponse",
+ "CoursesResourceWithStreamingResponse",
+ "AsyncCoursesResourceWithStreamingResponse",
+ "CourseChaptersResource",
+ "AsyncCourseChaptersResource",
+ "CourseChaptersResourceWithRawResponse",
+ "AsyncCourseChaptersResourceWithRawResponse",
+ "CourseChaptersResourceWithStreamingResponse",
+ "AsyncCourseChaptersResourceWithStreamingResponse",
+ "CourseLessonsResource",
+ "AsyncCourseLessonsResource",
+ "CourseLessonsResourceWithRawResponse",
+ "AsyncCourseLessonsResourceWithRawResponse",
+ "CourseLessonsResourceWithStreamingResponse",
+ "AsyncCourseLessonsResourceWithStreamingResponse",
+ "ReviewsResource",
+ "AsyncReviewsResource",
+ "ReviewsResourceWithRawResponse",
+ "AsyncReviewsResourceWithRawResponse",
+ "ReviewsResourceWithStreamingResponse",
+ "AsyncReviewsResourceWithStreamingResponse",
]
diff --git a/src/whop_sdk/resources/checkout_configurations.py b/src/whop_sdk/resources/checkout_configurations.py
index 756aaa60..4bb352ed 100644
--- a/src/whop_sdk/resources/checkout_configurations.py
+++ b/src/whop_sdk/resources/checkout_configurations.py
@@ -68,6 +68,8 @@ def create(
- `checkout_configuration:create`
- `plan:create`
+ - `access_pass:create`
+ - `access_pass:update`
Args:
affiliate_code: The affiliate code to use for the checkout configuration
@@ -257,6 +259,8 @@ async def create(
- `checkout_configuration:create`
- `plan:create`
+ - `access_pass:create`
+ - `access_pass:update`
Args:
affiliate_code: The affiliate code to use for the checkout configuration
diff --git a/src/whop_sdk/resources/course_chapters.py b/src/whop_sdk/resources/course_chapters.py
new file mode 100644
index 00000000..008bf0aa
--- /dev/null
+++ b/src/whop_sdk/resources/course_chapters.py
@@ -0,0 +1,602 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Optional
+
+import httpx
+
+from ..types import course_chapter_list_params, course_chapter_create_params, course_chapter_update_params
+from .._types import Body, Omit, Query, Headers, NotGiven, omit, not_given
+from .._utils import maybe_transform, async_maybe_transform
+from .._compat import cached_property
+from .._resource import SyncAPIResource, AsyncAPIResource
+from .._response import (
+ to_raw_response_wrapper,
+ to_streamed_response_wrapper,
+ async_to_raw_response_wrapper,
+ async_to_streamed_response_wrapper,
+)
+from ..pagination import SyncCursorPage, AsyncCursorPage
+from .._base_client import AsyncPaginator, make_request_options
+from ..types.course_chapter import CourseChapter
+from ..types.course_chapter_list_response import CourseChapterListResponse
+from ..types.course_chapter_delete_response import CourseChapterDeleteResponse
+
+__all__ = ["CourseChaptersResource", "AsyncCourseChaptersResource"]
+
+
+class CourseChaptersResource(SyncAPIResource):
+ @cached_property
+ def with_raw_response(self) -> CourseChaptersResourceWithRawResponse:
+ """
+ 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 CourseChaptersResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> CourseChaptersResourceWithStreamingResponse:
+ """
+ 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 CourseChaptersResourceWithStreamingResponse(self)
+
+ def create(
+ self,
+ *,
+ course_id: str,
+ 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,
+ ) -> CourseChapter:
+ """
+ Creates a new course chapter
+
+ Required permissions:
+
+ - `courses:update`
+
+ Args:
+ course_id: The ID of the course to create the chapter in
+
+ title: The title of the chapter
+
+ 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(
+ "/course_chapters",
+ body=maybe_transform(
+ {
+ "course_id": course_id,
+ "title": title,
+ },
+ course_chapter_create_params.CourseChapterCreateParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=CourseChapter,
+ )
+
+ 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,
+ ) -> CourseChapter:
+ """
+ Retrieves a course chapter by ID
+
+ Required permissions:
+
+ - `courses: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(
+ f"/course_chapters/{id}",
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=CourseChapter,
+ )
+
+ def update(
+ self,
+ id: str,
+ *,
+ title: 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,
+ ) -> CourseChapter:
+ """
+ Updates a course chapter
+
+ Required permissions:
+
+ - `courses:update`
+
+ Args:
+ title: The title of the chapter
+
+ 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(
+ f"/course_chapters/{id}",
+ body=maybe_transform({"title": title}, course_chapter_update_params.CourseChapterUpdateParams),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=CourseChapter,
+ )
+
+ def list(
+ self,
+ *,
+ course_id: str,
+ after: Optional[str] | Omit = omit,
+ before: Optional[str] | Omit = omit,
+ first: Optional[int] | Omit = omit,
+ last: Optional[int] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> SyncCursorPage[CourseChapterListResponse]:
+ """
+ Lists chapters for a course
+
+ Required permissions:
+
+ - `courses:read`
+
+ Args:
+ course_id: The ID of the course
+
+ 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.
+
+ first: Returns the first _n_ elements from the list.
+
+ last: Returns the last _n_ elements from the list.
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ return self._get_api_list(
+ "/course_chapters",
+ page=SyncCursorPage[CourseChapterListResponse],
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=maybe_transform(
+ {
+ "course_id": course_id,
+ "after": after,
+ "before": before,
+ "first": first,
+ "last": last,
+ },
+ course_chapter_list_params.CourseChapterListParams,
+ ),
+ ),
+ model=CourseChapterListResponse,
+ )
+
+ 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,
+ ) -> CourseChapterDeleteResponse:
+ """
+ Deletes a course chapter
+
+ Required permissions:
+
+ - `courses:update`
+
+ 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(
+ f"/course_chapters/{id}",
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=CourseChapterDeleteResponse,
+ )
+
+
+class AsyncCourseChaptersResource(AsyncAPIResource):
+ @cached_property
+ def with_raw_response(self) -> AsyncCourseChaptersResourceWithRawResponse:
+ """
+ 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 AsyncCourseChaptersResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> AsyncCourseChaptersResourceWithStreamingResponse:
+ """
+ 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 AsyncCourseChaptersResourceWithStreamingResponse(self)
+
+ async def create(
+ self,
+ *,
+ course_id: str,
+ 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,
+ ) -> CourseChapter:
+ """
+ Creates a new course chapter
+
+ Required permissions:
+
+ - `courses:update`
+
+ Args:
+ course_id: The ID of the course to create the chapter in
+
+ title: The title of the chapter
+
+ 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(
+ "/course_chapters",
+ body=await async_maybe_transform(
+ {
+ "course_id": course_id,
+ "title": title,
+ },
+ course_chapter_create_params.CourseChapterCreateParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=CourseChapter,
+ )
+
+ 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,
+ ) -> CourseChapter:
+ """
+ Retrieves a course chapter by ID
+
+ Required permissions:
+
+ - `courses: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(
+ f"/course_chapters/{id}",
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=CourseChapter,
+ )
+
+ async def update(
+ self,
+ id: str,
+ *,
+ title: 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,
+ ) -> CourseChapter:
+ """
+ Updates a course chapter
+
+ Required permissions:
+
+ - `courses:update`
+
+ Args:
+ title: The title of the chapter
+
+ 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(
+ f"/course_chapters/{id}",
+ body=await async_maybe_transform({"title": title}, course_chapter_update_params.CourseChapterUpdateParams),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=CourseChapter,
+ )
+
+ def list(
+ self,
+ *,
+ course_id: str,
+ after: Optional[str] | Omit = omit,
+ before: Optional[str] | Omit = omit,
+ first: Optional[int] | Omit = omit,
+ last: Optional[int] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> AsyncPaginator[CourseChapterListResponse, AsyncCursorPage[CourseChapterListResponse]]:
+ """
+ Lists chapters for a course
+
+ Required permissions:
+
+ - `courses:read`
+
+ Args:
+ course_id: The ID of the course
+
+ 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.
+
+ first: Returns the first _n_ elements from the list.
+
+ last: Returns the last _n_ elements from the list.
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ return self._get_api_list(
+ "/course_chapters",
+ page=AsyncCursorPage[CourseChapterListResponse],
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=maybe_transform(
+ {
+ "course_id": course_id,
+ "after": after,
+ "before": before,
+ "first": first,
+ "last": last,
+ },
+ course_chapter_list_params.CourseChapterListParams,
+ ),
+ ),
+ model=CourseChapterListResponse,
+ )
+
+ 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,
+ ) -> CourseChapterDeleteResponse:
+ """
+ Deletes a course chapter
+
+ Required permissions:
+
+ - `courses:update`
+
+ 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(
+ f"/course_chapters/{id}",
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=CourseChapterDeleteResponse,
+ )
+
+
+class CourseChaptersResourceWithRawResponse:
+ def __init__(self, course_chapters: CourseChaptersResource) -> None:
+ self._course_chapters = course_chapters
+
+ self.create = to_raw_response_wrapper(
+ course_chapters.create,
+ )
+ self.retrieve = to_raw_response_wrapper(
+ course_chapters.retrieve,
+ )
+ self.update = to_raw_response_wrapper(
+ course_chapters.update,
+ )
+ self.list = to_raw_response_wrapper(
+ course_chapters.list,
+ )
+ self.delete = to_raw_response_wrapper(
+ course_chapters.delete,
+ )
+
+
+class AsyncCourseChaptersResourceWithRawResponse:
+ def __init__(self, course_chapters: AsyncCourseChaptersResource) -> None:
+ self._course_chapters = course_chapters
+
+ self.create = async_to_raw_response_wrapper(
+ course_chapters.create,
+ )
+ self.retrieve = async_to_raw_response_wrapper(
+ course_chapters.retrieve,
+ )
+ self.update = async_to_raw_response_wrapper(
+ course_chapters.update,
+ )
+ self.list = async_to_raw_response_wrapper(
+ course_chapters.list,
+ )
+ self.delete = async_to_raw_response_wrapper(
+ course_chapters.delete,
+ )
+
+
+class CourseChaptersResourceWithStreamingResponse:
+ def __init__(self, course_chapters: CourseChaptersResource) -> None:
+ self._course_chapters = course_chapters
+
+ self.create = to_streamed_response_wrapper(
+ course_chapters.create,
+ )
+ self.retrieve = to_streamed_response_wrapper(
+ course_chapters.retrieve,
+ )
+ self.update = to_streamed_response_wrapper(
+ course_chapters.update,
+ )
+ self.list = to_streamed_response_wrapper(
+ course_chapters.list,
+ )
+ self.delete = to_streamed_response_wrapper(
+ course_chapters.delete,
+ )
+
+
+class AsyncCourseChaptersResourceWithStreamingResponse:
+ def __init__(self, course_chapters: AsyncCourseChaptersResource) -> None:
+ self._course_chapters = course_chapters
+
+ self.create = async_to_streamed_response_wrapper(
+ course_chapters.create,
+ )
+ self.retrieve = async_to_streamed_response_wrapper(
+ course_chapters.retrieve,
+ )
+ self.update = async_to_streamed_response_wrapper(
+ course_chapters.update,
+ )
+ self.list = async_to_streamed_response_wrapper(
+ course_chapters.list,
+ )
+ self.delete = async_to_streamed_response_wrapper(
+ course_chapters.delete,
+ )
diff --git a/src/whop_sdk/resources/course_lessons.py b/src/whop_sdk/resources/course_lessons.py
new file mode 100644
index 00000000..2afcc1a6
--- /dev/null
+++ b/src/whop_sdk/resources/course_lessons.py
@@ -0,0 +1,722 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Iterable, Optional
+
+import httpx
+
+from ..types import (
+ LessonTypes,
+ LessonVisibilities,
+ course_lesson_list_params,
+ course_lesson_create_params,
+ course_lesson_update_params,
+)
+from .._types import Body, Omit, Query, Headers, NotGiven, omit, not_given
+from .._utils import maybe_transform, async_maybe_transform
+from .._compat import cached_property
+from .._resource import SyncAPIResource, AsyncAPIResource
+from .._response import (
+ to_raw_response_wrapper,
+ to_streamed_response_wrapper,
+ async_to_raw_response_wrapper,
+ async_to_streamed_response_wrapper,
+)
+from ..pagination import SyncCursorPage, AsyncCursorPage
+from .._base_client import AsyncPaginator, make_request_options
+from ..types.lesson import Lesson
+from ..types.lesson_types import LessonTypes
+from ..types.lesson_visibilities import LessonVisibilities
+from ..types.course_lesson_list_response import CourseLessonListResponse
+from ..types.course_lesson_delete_response import CourseLessonDeleteResponse
+
+__all__ = ["CourseLessonsResource", "AsyncCourseLessonsResource"]
+
+
+class CourseLessonsResource(SyncAPIResource):
+ @cached_property
+ def with_raw_response(self) -> CourseLessonsResourceWithRawResponse:
+ """
+ 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 CourseLessonsResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> CourseLessonsResourceWithStreamingResponse:
+ """
+ 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 CourseLessonsResourceWithStreamingResponse(self)
+
+ def create(
+ self,
+ *,
+ chapter_id: str,
+ lesson_type: LessonTypes,
+ content: Optional[str] | Omit = omit,
+ days_from_course_start_until_unlock: Optional[int] | 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,
+ ) -> Lesson:
+ """
+ Creates a new course lesson
+
+ Required permissions:
+
+ - `courses:update`
+
+ Args:
+ chapter_id: The ID of the chapter to create the lesson in
+
+ lesson_type: The type of the lesson
+
+ content: The content of the lesson
+
+ days_from_course_start_until_unlock: Days from course start until unlock
+
+ title: The title of the lesson
+
+ 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(
+ "/course_lessons",
+ body=maybe_transform(
+ {
+ "chapter_id": chapter_id,
+ "lesson_type": lesson_type,
+ "content": content,
+ "days_from_course_start_until_unlock": days_from_course_start_until_unlock,
+ "title": title,
+ },
+ course_lesson_create_params.CourseLessonCreateParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=Lesson,
+ )
+
+ 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,
+ ) -> Lesson:
+ """
+ Retrieves a course lesson by ID
+
+ Required permissions:
+
+ - `courses: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(
+ f"/course_lessons/{id}",
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=Lesson,
+ )
+
+ def update(
+ self,
+ id: str,
+ *,
+ assessment_questions: Optional[Iterable[course_lesson_update_params.AssessmentQuestion]] | Omit = omit,
+ attachments: Optional[Iterable[course_lesson_update_params.Attachment]] | Omit = omit,
+ content: Optional[str] | Omit = omit,
+ days_from_course_start_until_unlock: Optional[int] | Omit = omit,
+ lesson_type: Optional[LessonTypes] | Omit = omit,
+ main_pdf: Optional[course_lesson_update_params.MainPdf] | Omit = omit,
+ mux_asset_id: Optional[str] | Omit = omit,
+ title: Optional[str] | Omit = omit,
+ visibility: Optional[LessonVisibilities] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> Lesson:
+ """
+ Updates a course lesson
+
+ Required permissions:
+
+ - `courses:update`
+
+ Args:
+ assessment_questions: Assessment questions for quiz/knowledge check lessons. Replaces all existing
+ questions.
+
+ attachments: General attachments for the lesson (PDFs, files, etc). Replaces all existing
+ attachments.
+
+ content: The content of the lesson
+
+ days_from_course_start_until_unlock: Days from course start until unlock
+
+ lesson_type: The available types for a lesson
+
+ main_pdf: The main PDF file for this lesson
+
+ mux_asset_id: The ID of the Mux asset to attach to this lesson for video lessons
+
+ title: The title of the lesson
+
+ visibility: The available visibilities for a lesson. Determines how / whether a lesson is
+ visible to users.
+
+ 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(
+ f"/course_lessons/{id}",
+ body=maybe_transform(
+ {
+ "assessment_questions": assessment_questions,
+ "attachments": attachments,
+ "content": content,
+ "days_from_course_start_until_unlock": days_from_course_start_until_unlock,
+ "lesson_type": lesson_type,
+ "main_pdf": main_pdf,
+ "mux_asset_id": mux_asset_id,
+ "title": title,
+ "visibility": visibility,
+ },
+ course_lesson_update_params.CourseLessonUpdateParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=Lesson,
+ )
+
+ def list(
+ self,
+ *,
+ after: Optional[str] | Omit = omit,
+ before: Optional[str] | Omit = omit,
+ chapter_id: Optional[str] | Omit = omit,
+ course_id: Optional[str] | Omit = omit,
+ first: Optional[int] | Omit = omit,
+ last: Optional[int] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> SyncCursorPage[CourseLessonListResponse]:
+ """
+ Lists lessons for a course or chapter
+
+ Required permissions:
+
+ - `courses: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.
+
+ chapter_id: The ID of the chapter (returns lessons only for this chapter)
+
+ course_id: The ID of the course (returns all lessons across all chapters)
+
+ first: Returns the first _n_ elements from the list.
+
+ last: Returns the last _n_ elements from the list.
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ return self._get_api_list(
+ "/course_lessons",
+ page=SyncCursorPage[CourseLessonListResponse],
+ 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,
+ "chapter_id": chapter_id,
+ "course_id": course_id,
+ "first": first,
+ "last": last,
+ },
+ course_lesson_list_params.CourseLessonListParams,
+ ),
+ ),
+ model=CourseLessonListResponse,
+ )
+
+ 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,
+ ) -> CourseLessonDeleteResponse:
+ """
+ Deletes a course lesson
+
+ Required permissions:
+
+ - `courses:update`
+
+ 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(
+ f"/course_lessons/{id}",
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=CourseLessonDeleteResponse,
+ )
+
+
+class AsyncCourseLessonsResource(AsyncAPIResource):
+ @cached_property
+ def with_raw_response(self) -> AsyncCourseLessonsResourceWithRawResponse:
+ """
+ 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 AsyncCourseLessonsResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> AsyncCourseLessonsResourceWithStreamingResponse:
+ """
+ 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 AsyncCourseLessonsResourceWithStreamingResponse(self)
+
+ async def create(
+ self,
+ *,
+ chapter_id: str,
+ lesson_type: LessonTypes,
+ content: Optional[str] | Omit = omit,
+ days_from_course_start_until_unlock: Optional[int] | 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,
+ ) -> Lesson:
+ """
+ Creates a new course lesson
+
+ Required permissions:
+
+ - `courses:update`
+
+ Args:
+ chapter_id: The ID of the chapter to create the lesson in
+
+ lesson_type: The type of the lesson
+
+ content: The content of the lesson
+
+ days_from_course_start_until_unlock: Days from course start until unlock
+
+ title: The title of the lesson
+
+ 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(
+ "/course_lessons",
+ body=await async_maybe_transform(
+ {
+ "chapter_id": chapter_id,
+ "lesson_type": lesson_type,
+ "content": content,
+ "days_from_course_start_until_unlock": days_from_course_start_until_unlock,
+ "title": title,
+ },
+ course_lesson_create_params.CourseLessonCreateParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=Lesson,
+ )
+
+ 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,
+ ) -> Lesson:
+ """
+ Retrieves a course lesson by ID
+
+ Required permissions:
+
+ - `courses: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(
+ f"/course_lessons/{id}",
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=Lesson,
+ )
+
+ async def update(
+ self,
+ id: str,
+ *,
+ assessment_questions: Optional[Iterable[course_lesson_update_params.AssessmentQuestion]] | Omit = omit,
+ attachments: Optional[Iterable[course_lesson_update_params.Attachment]] | Omit = omit,
+ content: Optional[str] | Omit = omit,
+ days_from_course_start_until_unlock: Optional[int] | Omit = omit,
+ lesson_type: Optional[LessonTypes] | Omit = omit,
+ main_pdf: Optional[course_lesson_update_params.MainPdf] | Omit = omit,
+ mux_asset_id: Optional[str] | Omit = omit,
+ title: Optional[str] | Omit = omit,
+ visibility: Optional[LessonVisibilities] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> Lesson:
+ """
+ Updates a course lesson
+
+ Required permissions:
+
+ - `courses:update`
+
+ Args:
+ assessment_questions: Assessment questions for quiz/knowledge check lessons. Replaces all existing
+ questions.
+
+ attachments: General attachments for the lesson (PDFs, files, etc). Replaces all existing
+ attachments.
+
+ content: The content of the lesson
+
+ days_from_course_start_until_unlock: Days from course start until unlock
+
+ lesson_type: The available types for a lesson
+
+ main_pdf: The main PDF file for this lesson
+
+ mux_asset_id: The ID of the Mux asset to attach to this lesson for video lessons
+
+ title: The title of the lesson
+
+ visibility: The available visibilities for a lesson. Determines how / whether a lesson is
+ visible to users.
+
+ 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(
+ f"/course_lessons/{id}",
+ body=await async_maybe_transform(
+ {
+ "assessment_questions": assessment_questions,
+ "attachments": attachments,
+ "content": content,
+ "days_from_course_start_until_unlock": days_from_course_start_until_unlock,
+ "lesson_type": lesson_type,
+ "main_pdf": main_pdf,
+ "mux_asset_id": mux_asset_id,
+ "title": title,
+ "visibility": visibility,
+ },
+ course_lesson_update_params.CourseLessonUpdateParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=Lesson,
+ )
+
+ def list(
+ self,
+ *,
+ after: Optional[str] | Omit = omit,
+ before: Optional[str] | Omit = omit,
+ chapter_id: Optional[str] | Omit = omit,
+ course_id: Optional[str] | Omit = omit,
+ first: Optional[int] | Omit = omit,
+ last: Optional[int] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> AsyncPaginator[CourseLessonListResponse, AsyncCursorPage[CourseLessonListResponse]]:
+ """
+ Lists lessons for a course or chapter
+
+ Required permissions:
+
+ - `courses: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.
+
+ chapter_id: The ID of the chapter (returns lessons only for this chapter)
+
+ course_id: The ID of the course (returns all lessons across all chapters)
+
+ first: Returns the first _n_ elements from the list.
+
+ last: Returns the last _n_ elements from the list.
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ return self._get_api_list(
+ "/course_lessons",
+ page=AsyncCursorPage[CourseLessonListResponse],
+ 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,
+ "chapter_id": chapter_id,
+ "course_id": course_id,
+ "first": first,
+ "last": last,
+ },
+ course_lesson_list_params.CourseLessonListParams,
+ ),
+ ),
+ model=CourseLessonListResponse,
+ )
+
+ 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,
+ ) -> CourseLessonDeleteResponse:
+ """
+ Deletes a course lesson
+
+ Required permissions:
+
+ - `courses:update`
+
+ 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(
+ f"/course_lessons/{id}",
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=CourseLessonDeleteResponse,
+ )
+
+
+class CourseLessonsResourceWithRawResponse:
+ def __init__(self, course_lessons: CourseLessonsResource) -> None:
+ self._course_lessons = course_lessons
+
+ self.create = to_raw_response_wrapper(
+ course_lessons.create,
+ )
+ self.retrieve = to_raw_response_wrapper(
+ course_lessons.retrieve,
+ )
+ self.update = to_raw_response_wrapper(
+ course_lessons.update,
+ )
+ self.list = to_raw_response_wrapper(
+ course_lessons.list,
+ )
+ self.delete = to_raw_response_wrapper(
+ course_lessons.delete,
+ )
+
+
+class AsyncCourseLessonsResourceWithRawResponse:
+ def __init__(self, course_lessons: AsyncCourseLessonsResource) -> None:
+ self._course_lessons = course_lessons
+
+ self.create = async_to_raw_response_wrapper(
+ course_lessons.create,
+ )
+ self.retrieve = async_to_raw_response_wrapper(
+ course_lessons.retrieve,
+ )
+ self.update = async_to_raw_response_wrapper(
+ course_lessons.update,
+ )
+ self.list = async_to_raw_response_wrapper(
+ course_lessons.list,
+ )
+ self.delete = async_to_raw_response_wrapper(
+ course_lessons.delete,
+ )
+
+
+class CourseLessonsResourceWithStreamingResponse:
+ def __init__(self, course_lessons: CourseLessonsResource) -> None:
+ self._course_lessons = course_lessons
+
+ self.create = to_streamed_response_wrapper(
+ course_lessons.create,
+ )
+ self.retrieve = to_streamed_response_wrapper(
+ course_lessons.retrieve,
+ )
+ self.update = to_streamed_response_wrapper(
+ course_lessons.update,
+ )
+ self.list = to_streamed_response_wrapper(
+ course_lessons.list,
+ )
+ self.delete = to_streamed_response_wrapper(
+ course_lessons.delete,
+ )
+
+
+class AsyncCourseLessonsResourceWithStreamingResponse:
+ def __init__(self, course_lessons: AsyncCourseLessonsResource) -> None:
+ self._course_lessons = course_lessons
+
+ self.create = async_to_streamed_response_wrapper(
+ course_lessons.create,
+ )
+ self.retrieve = async_to_streamed_response_wrapper(
+ course_lessons.retrieve,
+ )
+ self.update = async_to_streamed_response_wrapper(
+ course_lessons.update,
+ )
+ self.list = async_to_streamed_response_wrapper(
+ course_lessons.list,
+ )
+ self.delete = async_to_streamed_response_wrapper(
+ course_lessons.delete,
+ )
diff --git a/src/whop_sdk/resources/courses.py b/src/whop_sdk/resources/courses.py
new file mode 100644
index 00000000..c760845f
--- /dev/null
+++ b/src/whop_sdk/resources/courses.py
@@ -0,0 +1,713 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Iterable, Optional
+
+import httpx
+
+from ..types import Languages, course_list_params, course_create_params, course_update_params
+from .._types import Body, Omit, Query, Headers, NotGiven, omit, not_given
+from .._utils import maybe_transform, async_maybe_transform
+from .._compat import cached_property
+from .._resource import SyncAPIResource, AsyncAPIResource
+from .._response import (
+ to_raw_response_wrapper,
+ to_streamed_response_wrapper,
+ async_to_raw_response_wrapper,
+ async_to_streamed_response_wrapper,
+)
+from ..pagination import SyncCursorPage, AsyncCursorPage
+from .._base_client import AsyncPaginator, make_request_options
+from ..types.course import Course
+from ..types.languages import Languages
+from ..types.course_list_response import CourseListResponse
+from ..types.course_delete_response import CourseDeleteResponse
+
+__all__ = ["CoursesResource", "AsyncCoursesResource"]
+
+
+class CoursesResource(SyncAPIResource):
+ @cached_property
+ def with_raw_response(self) -> CoursesResourceWithRawResponse:
+ """
+ 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 CoursesResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> CoursesResourceWithStreamingResponse:
+ """
+ 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 CoursesResourceWithStreamingResponse(self)
+
+ def create(
+ self,
+ *,
+ experience_id: str,
+ title: str,
+ cover_image: Optional[str] | Omit = omit,
+ tagline: Optional[str] | Omit = omit,
+ thumbnail: Optional[course_create_params.Thumbnail] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> Course:
+ """
+ Creates a new course module in an experience
+
+ Required permissions:
+
+ - `courses:update`
+
+ Args:
+ experience_id: The ID of the experience to create the course in
+
+ title: The title of the course
+
+ cover_image: The cover image URL of the course
+
+ tagline: The tagline of the course
+
+ thumbnail: The thumbnail for the course in png, jpeg, or gif format
+
+ 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(
+ "/courses",
+ body=maybe_transform(
+ {
+ "experience_id": experience_id,
+ "title": title,
+ "cover_image": cover_image,
+ "tagline": tagline,
+ "thumbnail": thumbnail,
+ },
+ course_create_params.CourseCreateParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=Course,
+ )
+
+ 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,
+ ) -> Course:
+ """
+ Retrieves a course by ID
+
+ Required permissions:
+
+ - `courses: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(
+ f"/courses/{id}",
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=Course,
+ )
+
+ def update(
+ self,
+ id: str,
+ *,
+ certificate_after_completion_enabled: Optional[bool] | Omit = omit,
+ chapters: Optional[Iterable[course_update_params.Chapter]] | Omit = omit,
+ cover_image: Optional[str] | Omit = omit,
+ description: Optional[str] | Omit = omit,
+ language: Optional[Languages] | Omit = omit,
+ require_completing_lessons_in_order: Optional[bool] | Omit = omit,
+ tagline: Optional[str] | Omit = omit,
+ thumbnail: Optional[course_update_params.Thumbnail] | 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,
+ ) -> Course:
+ """
+ Updates a course
+
+ Required permissions:
+
+ - `courses:update`
+
+ Args:
+ certificate_after_completion_enabled: Whether the course will award its students a PDF certificate after completing
+ all lessons
+
+ chapters: The chapters and lessons to update
+
+ cover_image: The cover image URL of the course
+
+ description: A short description of the course
+
+ language: The available languages for a course
+
+ require_completing_lessons_in_order: Whether the course requires students to complete the previous lesson before
+ moving on to the next one
+
+ tagline: A short tagline for the course
+
+ thumbnail: The thumbnail for the course in png, jpeg, or gif format
+
+ title: The title of the course
+
+ 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(
+ f"/courses/{id}",
+ body=maybe_transform(
+ {
+ "certificate_after_completion_enabled": certificate_after_completion_enabled,
+ "chapters": chapters,
+ "cover_image": cover_image,
+ "description": description,
+ "language": language,
+ "require_completing_lessons_in_order": require_completing_lessons_in_order,
+ "tagline": tagline,
+ "thumbnail": thumbnail,
+ "title": title,
+ },
+ course_update_params.CourseUpdateParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=Course,
+ )
+
+ def list(
+ self,
+ *,
+ after: Optional[str] | Omit = omit,
+ before: Optional[str] | Omit = omit,
+ company_id: Optional[str] | Omit = omit,
+ experience_id: Optional[str] | Omit = omit,
+ first: Optional[int] | Omit = omit,
+ last: Optional[int] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> SyncCursorPage[CourseListResponse]:
+ """
+ Lists courses for an experience or company
+
+ Required permissions:
+
+ - `courses: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.
+
+ company_id: The ID of the company
+
+ experience_id: The ID of the experience
+
+ first: Returns the first _n_ elements from the list.
+
+ last: Returns the last _n_ elements from the list.
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ return self._get_api_list(
+ "/courses",
+ page=SyncCursorPage[CourseListResponse],
+ 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,
+ "company_id": company_id,
+ "experience_id": experience_id,
+ "first": first,
+ "last": last,
+ },
+ course_list_params.CourseListParams,
+ ),
+ ),
+ model=CourseListResponse,
+ )
+
+ 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,
+ ) -> CourseDeleteResponse:
+ """
+ Deletes a course
+
+ Required permissions:
+
+ - `courses:update`
+
+ 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(
+ f"/courses/{id}",
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=CourseDeleteResponse,
+ )
+
+
+class AsyncCoursesResource(AsyncAPIResource):
+ @cached_property
+ def with_raw_response(self) -> AsyncCoursesResourceWithRawResponse:
+ """
+ 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 AsyncCoursesResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> AsyncCoursesResourceWithStreamingResponse:
+ """
+ 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 AsyncCoursesResourceWithStreamingResponse(self)
+
+ async def create(
+ self,
+ *,
+ experience_id: str,
+ title: str,
+ cover_image: Optional[str] | Omit = omit,
+ tagline: Optional[str] | Omit = omit,
+ thumbnail: Optional[course_create_params.Thumbnail] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> Course:
+ """
+ Creates a new course module in an experience
+
+ Required permissions:
+
+ - `courses:update`
+
+ Args:
+ experience_id: The ID of the experience to create the course in
+
+ title: The title of the course
+
+ cover_image: The cover image URL of the course
+
+ tagline: The tagline of the course
+
+ thumbnail: The thumbnail for the course in png, jpeg, or gif format
+
+ 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(
+ "/courses",
+ body=await async_maybe_transform(
+ {
+ "experience_id": experience_id,
+ "title": title,
+ "cover_image": cover_image,
+ "tagline": tagline,
+ "thumbnail": thumbnail,
+ },
+ course_create_params.CourseCreateParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=Course,
+ )
+
+ 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,
+ ) -> Course:
+ """
+ Retrieves a course by ID
+
+ Required permissions:
+
+ - `courses: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(
+ f"/courses/{id}",
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=Course,
+ )
+
+ async def update(
+ self,
+ id: str,
+ *,
+ certificate_after_completion_enabled: Optional[bool] | Omit = omit,
+ chapters: Optional[Iterable[course_update_params.Chapter]] | Omit = omit,
+ cover_image: Optional[str] | Omit = omit,
+ description: Optional[str] | Omit = omit,
+ language: Optional[Languages] | Omit = omit,
+ require_completing_lessons_in_order: Optional[bool] | Omit = omit,
+ tagline: Optional[str] | Omit = omit,
+ thumbnail: Optional[course_update_params.Thumbnail] | 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,
+ ) -> Course:
+ """
+ Updates a course
+
+ Required permissions:
+
+ - `courses:update`
+
+ Args:
+ certificate_after_completion_enabled: Whether the course will award its students a PDF certificate after completing
+ all lessons
+
+ chapters: The chapters and lessons to update
+
+ cover_image: The cover image URL of the course
+
+ description: A short description of the course
+
+ language: The available languages for a course
+
+ require_completing_lessons_in_order: Whether the course requires students to complete the previous lesson before
+ moving on to the next one
+
+ tagline: A short tagline for the course
+
+ thumbnail: The thumbnail for the course in png, jpeg, or gif format
+
+ title: The title of the course
+
+ 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(
+ f"/courses/{id}",
+ body=await async_maybe_transform(
+ {
+ "certificate_after_completion_enabled": certificate_after_completion_enabled,
+ "chapters": chapters,
+ "cover_image": cover_image,
+ "description": description,
+ "language": language,
+ "require_completing_lessons_in_order": require_completing_lessons_in_order,
+ "tagline": tagline,
+ "thumbnail": thumbnail,
+ "title": title,
+ },
+ course_update_params.CourseUpdateParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=Course,
+ )
+
+ def list(
+ self,
+ *,
+ after: Optional[str] | Omit = omit,
+ before: Optional[str] | Omit = omit,
+ company_id: Optional[str] | Omit = omit,
+ experience_id: Optional[str] | Omit = omit,
+ first: Optional[int] | Omit = omit,
+ last: Optional[int] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> AsyncPaginator[CourseListResponse, AsyncCursorPage[CourseListResponse]]:
+ """
+ Lists courses for an experience or company
+
+ Required permissions:
+
+ - `courses: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.
+
+ company_id: The ID of the company
+
+ experience_id: The ID of the experience
+
+ first: Returns the first _n_ elements from the list.
+
+ last: Returns the last _n_ elements from the list.
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ return self._get_api_list(
+ "/courses",
+ page=AsyncCursorPage[CourseListResponse],
+ 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,
+ "company_id": company_id,
+ "experience_id": experience_id,
+ "first": first,
+ "last": last,
+ },
+ course_list_params.CourseListParams,
+ ),
+ ),
+ model=CourseListResponse,
+ )
+
+ 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,
+ ) -> CourseDeleteResponse:
+ """
+ Deletes a course
+
+ Required permissions:
+
+ - `courses:update`
+
+ 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(
+ f"/courses/{id}",
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=CourseDeleteResponse,
+ )
+
+
+class CoursesResourceWithRawResponse:
+ def __init__(self, courses: CoursesResource) -> None:
+ self._courses = courses
+
+ self.create = to_raw_response_wrapper(
+ courses.create,
+ )
+ self.retrieve = to_raw_response_wrapper(
+ courses.retrieve,
+ )
+ self.update = to_raw_response_wrapper(
+ courses.update,
+ )
+ self.list = to_raw_response_wrapper(
+ courses.list,
+ )
+ self.delete = to_raw_response_wrapper(
+ courses.delete,
+ )
+
+
+class AsyncCoursesResourceWithRawResponse:
+ def __init__(self, courses: AsyncCoursesResource) -> None:
+ self._courses = courses
+
+ self.create = async_to_raw_response_wrapper(
+ courses.create,
+ )
+ self.retrieve = async_to_raw_response_wrapper(
+ courses.retrieve,
+ )
+ self.update = async_to_raw_response_wrapper(
+ courses.update,
+ )
+ self.list = async_to_raw_response_wrapper(
+ courses.list,
+ )
+ self.delete = async_to_raw_response_wrapper(
+ courses.delete,
+ )
+
+
+class CoursesResourceWithStreamingResponse:
+ def __init__(self, courses: CoursesResource) -> None:
+ self._courses = courses
+
+ self.create = to_streamed_response_wrapper(
+ courses.create,
+ )
+ self.retrieve = to_streamed_response_wrapper(
+ courses.retrieve,
+ )
+ self.update = to_streamed_response_wrapper(
+ courses.update,
+ )
+ self.list = to_streamed_response_wrapper(
+ courses.list,
+ )
+ self.delete = to_streamed_response_wrapper(
+ courses.delete,
+ )
+
+
+class AsyncCoursesResourceWithStreamingResponse:
+ def __init__(self, courses: AsyncCoursesResource) -> None:
+ self._courses = courses
+
+ self.create = async_to_streamed_response_wrapper(
+ courses.create,
+ )
+ self.retrieve = async_to_streamed_response_wrapper(
+ courses.retrieve,
+ )
+ self.update = async_to_streamed_response_wrapper(
+ courses.update,
+ )
+ self.list = async_to_streamed_response_wrapper(
+ courses.list,
+ )
+ self.delete = async_to_streamed_response_wrapper(
+ courses.delete,
+ )
diff --git a/src/whop_sdk/resources/experiences.py b/src/whop_sdk/resources/experiences.py
index 41e06c65..a0ae554c 100644
--- a/src/whop_sdk/resources/experiences.py
+++ b/src/whop_sdk/resources/experiences.py
@@ -13,6 +13,7 @@
experience_create_params,
experience_detach_params,
experience_update_params,
+ experience_duplicate_params,
)
from .._types import Body, Omit, Query, Headers, NotGiven, omit, not_given
from .._utils import maybe_transform, async_maybe_transform
@@ -389,6 +390,53 @@ def detach(
cast_to=Experience,
)
+ def duplicate(
+ self,
+ id: str,
+ *,
+ name: 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,
+ ) -> Experience:
+ """Duplicates an existing experience.
+
+ The name will be copied, unless provided. The
+ new experience will be attached to the same products as the original experience.
+ If duplicating a Forum or Chat experience, the new experience will have the same
+ settings as the original experience, e.g. who can post, who can comment, etc. No
+ content, e.g. posts, messages, lessons from within the original experience will
+ be copied.
+
+ Required permissions:
+
+ - `experience:create`
+
+ Args:
+ name: The name of the new experience
+
+ 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(
+ f"/experiences/{id}/duplicate",
+ body=maybe_transform({"name": name}, experience_duplicate_params.ExperienceDuplicateParams),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=Experience,
+ )
+
class AsyncExperiencesResource(AsyncAPIResource):
@cached_property
@@ -750,6 +798,53 @@ async def detach(
cast_to=Experience,
)
+ async def duplicate(
+ self,
+ id: str,
+ *,
+ name: 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,
+ ) -> Experience:
+ """Duplicates an existing experience.
+
+ The name will be copied, unless provided. The
+ new experience will be attached to the same products as the original experience.
+ If duplicating a Forum or Chat experience, the new experience will have the same
+ settings as the original experience, e.g. who can post, who can comment, etc. No
+ content, e.g. posts, messages, lessons from within the original experience will
+ be copied.
+
+ Required permissions:
+
+ - `experience:create`
+
+ Args:
+ name: The name of the new experience
+
+ 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(
+ f"/experiences/{id}/duplicate",
+ body=await async_maybe_transform({"name": name}, experience_duplicate_params.ExperienceDuplicateParams),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=Experience,
+ )
+
class ExperiencesResourceWithRawResponse:
def __init__(self, experiences: ExperiencesResource) -> None:
@@ -776,6 +871,9 @@ def __init__(self, experiences: ExperiencesResource) -> None:
self.detach = to_raw_response_wrapper(
experiences.detach,
)
+ self.duplicate = to_raw_response_wrapper(
+ experiences.duplicate,
+ )
class AsyncExperiencesResourceWithRawResponse:
@@ -803,6 +901,9 @@ def __init__(self, experiences: AsyncExperiencesResource) -> None:
self.detach = async_to_raw_response_wrapper(
experiences.detach,
)
+ self.duplicate = async_to_raw_response_wrapper(
+ experiences.duplicate,
+ )
class ExperiencesResourceWithStreamingResponse:
@@ -830,6 +931,9 @@ def __init__(self, experiences: ExperiencesResource) -> None:
self.detach = to_streamed_response_wrapper(
experiences.detach,
)
+ self.duplicate = to_streamed_response_wrapper(
+ experiences.duplicate,
+ )
class AsyncExperiencesResourceWithStreamingResponse:
@@ -857,3 +961,6 @@ def __init__(self, experiences: AsyncExperiencesResource) -> None:
self.detach = async_to_streamed_response_wrapper(
experiences.detach,
)
+ self.duplicate = async_to_streamed_response_wrapper(
+ experiences.duplicate,
+ )
diff --git a/src/whop_sdk/resources/forum_posts.py b/src/whop_sdk/resources/forum_posts.py
index 8b744103..87ab5118 100644
--- a/src/whop_sdk/resources/forum_posts.py
+++ b/src/whop_sdk/resources/forum_posts.py
@@ -6,7 +6,7 @@
import httpx
-from ..types import forum_post_list_params, forum_post_create_params
+from ..types import forum_post_list_params, forum_post_create_params, forum_post_update_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
@@ -167,6 +167,61 @@ def retrieve(
cast_to=ForumPost,
)
+ def update(
+ self,
+ id: str,
+ *,
+ attachments: Optional[Iterable[forum_post_update_params.Attachment]] | Omit = omit,
+ content: Optional[str] | Omit = omit,
+ is_pinned: Optional[bool] | 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,
+ ) -> ForumPost:
+ """
+ Update an existing forum post
+
+ Args:
+ attachments: The attachments for this post
+
+ content: This is the main body of the post in Markdown format. Hidden if paywalled and
+ user hasn't purchased access to it.
+
+ is_pinned: Whether the post is pinned. You can only pin a top level posts (not comments).
+
+ title: The title of the post. Only visible if paywalled.
+
+ 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(
+ f"/forum_posts/{id}",
+ body=maybe_transform(
+ {
+ "attachments": attachments,
+ "content": content,
+ "is_pinned": is_pinned,
+ "title": title,
+ },
+ forum_post_update_params.ForumPostUpdateParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=ForumPost,
+ )
+
def list(
self,
*,
@@ -380,6 +435,61 @@ async def retrieve(
cast_to=ForumPost,
)
+ async def update(
+ self,
+ id: str,
+ *,
+ attachments: Optional[Iterable[forum_post_update_params.Attachment]] | Omit = omit,
+ content: Optional[str] | Omit = omit,
+ is_pinned: Optional[bool] | 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,
+ ) -> ForumPost:
+ """
+ Update an existing forum post
+
+ Args:
+ attachments: The attachments for this post
+
+ content: This is the main body of the post in Markdown format. Hidden if paywalled and
+ user hasn't purchased access to it.
+
+ is_pinned: Whether the post is pinned. You can only pin a top level posts (not comments).
+
+ title: The title of the post. Only visible if paywalled.
+
+ 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(
+ f"/forum_posts/{id}",
+ body=await async_maybe_transform(
+ {
+ "attachments": attachments,
+ "content": content,
+ "is_pinned": is_pinned,
+ "title": title,
+ },
+ forum_post_update_params.ForumPostUpdateParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=ForumPost,
+ )
+
def list(
self,
*,
@@ -462,6 +572,9 @@ def __init__(self, forum_posts: ForumPostsResource) -> None:
self.retrieve = to_raw_response_wrapper(
forum_posts.retrieve,
)
+ self.update = to_raw_response_wrapper(
+ forum_posts.update,
+ )
self.list = to_raw_response_wrapper(
forum_posts.list,
)
@@ -477,6 +590,9 @@ def __init__(self, forum_posts: AsyncForumPostsResource) -> None:
self.retrieve = async_to_raw_response_wrapper(
forum_posts.retrieve,
)
+ self.update = async_to_raw_response_wrapper(
+ forum_posts.update,
+ )
self.list = async_to_raw_response_wrapper(
forum_posts.list,
)
@@ -492,6 +608,9 @@ def __init__(self, forum_posts: ForumPostsResource) -> None:
self.retrieve = to_streamed_response_wrapper(
forum_posts.retrieve,
)
+ self.update = to_streamed_response_wrapper(
+ forum_posts.update,
+ )
self.list = to_streamed_response_wrapper(
forum_posts.list,
)
@@ -507,6 +626,9 @@ def __init__(self, forum_posts: AsyncForumPostsResource) -> None:
self.retrieve = async_to_streamed_response_wrapper(
forum_posts.retrieve,
)
+ self.update = async_to_streamed_response_wrapper(
+ forum_posts.update,
+ )
self.list = async_to_streamed_response_wrapper(
forum_posts.list,
)
diff --git a/src/whop_sdk/resources/members.py b/src/whop_sdk/resources/members.py
index 5cf6f0cf..682337b8 100644
--- a/src/whop_sdk/resources/members.py
+++ b/src/whop_sdk/resources/members.py
@@ -110,6 +110,7 @@ def list(
promo_code_ids: Optional[SequenceNotStr[str]] | Omit = omit,
query: Optional[str] | Omit = omit,
statuses: Optional[List[MemberStatuses]] | Omit = omit,
+ user_ids: 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,
@@ -160,6 +161,8 @@ def list(
statuses: The statuses to filter the members by
+ user_ids: The user IDs to filter the members by
+
extra_headers: Send extra headers
extra_query: Add additional query parameters to the request
@@ -194,6 +197,7 @@ def list(
"promo_code_ids": promo_code_ids,
"query": query,
"statuses": statuses,
+ "user_ids": user_ids,
},
member_list_params.MemberListParams,
),
@@ -281,6 +285,7 @@ def list(
promo_code_ids: Optional[SequenceNotStr[str]] | Omit = omit,
query: Optional[str] | Omit = omit,
statuses: Optional[List[MemberStatuses]] | Omit = omit,
+ user_ids: 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,
@@ -331,6 +336,8 @@ def list(
statuses: The statuses to filter the members by
+ user_ids: The user IDs to filter the members by
+
extra_headers: Send extra headers
extra_query: Add additional query parameters to the request
@@ -365,6 +372,7 @@ def list(
"promo_code_ids": promo_code_ids,
"query": query,
"statuses": statuses,
+ "user_ids": user_ids,
},
member_list_params.MemberListParams,
),
diff --git a/src/whop_sdk/resources/memberships.py b/src/whop_sdk/resources/memberships.py
index 907f9efa..dd593d22 100644
--- a/src/whop_sdk/resources/memberships.py
+++ b/src/whop_sdk/resources/memberships.py
@@ -136,7 +136,6 @@ def update(
def list(
self,
*,
- company_id: str,
access_pass_ids: Optional[SequenceNotStr[str]] | Omit = omit,
after: Optional[str] | Omit = omit,
before: Optional[str] | Omit = omit,
@@ -154,6 +153,7 @@ def list(
]
]
| Omit = omit,
+ company_id: Optional[str] | Omit = omit,
created_after: Union[str, datetime, None] | Omit = omit,
created_before: Union[str, datetime, None] | Omit = omit,
direction: Optional[Direction] | Omit = omit,
@@ -164,6 +164,7 @@ def list(
plan_ids: Optional[SequenceNotStr[str]] | Omit = omit,
promo_code_ids: Optional[SequenceNotStr[str]] | Omit = omit,
statuses: Optional[List[MembershipStatus]] | Omit = omit,
+ user_ids: 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,
@@ -179,8 +180,6 @@ def list(
- `member:basic:read`
Args:
- company_id: The ID of the company to list memberships for
-
access_pass_ids: The access pass IDs to filter the memberships by
after: Returns the elements in the list that come after the specified cursor.
@@ -189,6 +188,8 @@ def list(
cancel_options: The cancel options to filter the memberships by
+ company_id: The ID of the company to list memberships for
+
created_after: The minimum creation date to filter by
created_before: The maximum creation date to filter by
@@ -207,6 +208,8 @@ def list(
statuses: The membership status to filter the memberships by
+ user_ids: Only return memberships from these whop user ids
+
extra_headers: Send extra headers
extra_query: Add additional query parameters to the request
@@ -225,11 +228,11 @@ def list(
timeout=timeout,
query=maybe_transform(
{
- "company_id": company_id,
"access_pass_ids": access_pass_ids,
"after": after,
"before": before,
"cancel_options": cancel_options,
+ "company_id": company_id,
"created_after": created_after,
"created_before": created_before,
"direction": direction,
@@ -239,6 +242,7 @@ def list(
"plan_ids": plan_ids,
"promo_code_ids": promo_code_ids,
"statuses": statuses,
+ "user_ids": user_ids,
},
membership_list_params.MembershipListParams,
),
@@ -475,7 +479,6 @@ async def update(
def list(
self,
*,
- company_id: str,
access_pass_ids: Optional[SequenceNotStr[str]] | Omit = omit,
after: Optional[str] | Omit = omit,
before: Optional[str] | Omit = omit,
@@ -493,6 +496,7 @@ def list(
]
]
| Omit = omit,
+ company_id: Optional[str] | Omit = omit,
created_after: Union[str, datetime, None] | Omit = omit,
created_before: Union[str, datetime, None] | Omit = omit,
direction: Optional[Direction] | Omit = omit,
@@ -503,6 +507,7 @@ def list(
plan_ids: Optional[SequenceNotStr[str]] | Omit = omit,
promo_code_ids: Optional[SequenceNotStr[str]] | Omit = omit,
statuses: Optional[List[MembershipStatus]] | Omit = omit,
+ user_ids: 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,
@@ -518,8 +523,6 @@ def list(
- `member:basic:read`
Args:
- company_id: The ID of the company to list memberships for
-
access_pass_ids: The access pass IDs to filter the memberships by
after: Returns the elements in the list that come after the specified cursor.
@@ -528,6 +531,8 @@ def list(
cancel_options: The cancel options to filter the memberships by
+ company_id: The ID of the company to list memberships for
+
created_after: The minimum creation date to filter by
created_before: The maximum creation date to filter by
@@ -546,6 +551,8 @@ def list(
statuses: The membership status to filter the memberships by
+ user_ids: Only return memberships from these whop user ids
+
extra_headers: Send extra headers
extra_query: Add additional query parameters to the request
@@ -564,11 +571,11 @@ def list(
timeout=timeout,
query=maybe_transform(
{
- "company_id": company_id,
"access_pass_ids": access_pass_ids,
"after": after,
"before": before,
"cancel_options": cancel_options,
+ "company_id": company_id,
"created_after": created_after,
"created_before": created_before,
"direction": direction,
@@ -578,6 +585,7 @@ def list(
"plan_ids": plan_ids,
"promo_code_ids": promo_code_ids,
"statuses": statuses,
+ "user_ids": user_ids,
},
membership_list_params.MembershipListParams,
),
diff --git a/src/whop_sdk/resources/messages.py b/src/whop_sdk/resources/messages.py
index a5f5aed5..589bfc7b 100644
--- a/src/whop_sdk/resources/messages.py
+++ b/src/whop_sdk/resources/messages.py
@@ -6,7 +6,7 @@
import httpx
-from ..types import message_list_params, message_create_params
+from ..types import message_list_params, message_create_params, message_update_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
@@ -138,6 +138,56 @@ def retrieve(
cast_to=Message,
)
+ def update(
+ self,
+ id: str,
+ *,
+ attachments: Optional[Iterable[message_update_params.Attachment]] | Omit = omit,
+ content: Optional[str] | Omit = omit,
+ is_pinned: Optional[bool] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> Message:
+ """
+ Updates an existing message
+
+ Args:
+ attachments: The attachments for this message
+
+ content: The content of the message in Markdown format
+
+ is_pinned: Whether this message is pinned
+
+ 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(
+ f"/messages/{id}",
+ body=maybe_transform(
+ {
+ "attachments": attachments,
+ "content": content,
+ "is_pinned": is_pinned,
+ },
+ message_update_params.MessageUpdateParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=Message,
+ )
+
def list(
self,
*,
@@ -318,6 +368,56 @@ async def retrieve(
cast_to=Message,
)
+ async def update(
+ self,
+ id: str,
+ *,
+ attachments: Optional[Iterable[message_update_params.Attachment]] | Omit = omit,
+ content: Optional[str] | Omit = omit,
+ is_pinned: Optional[bool] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> Message:
+ """
+ Updates an existing message
+
+ Args:
+ attachments: The attachments for this message
+
+ content: The content of the message in Markdown format
+
+ is_pinned: Whether this message is pinned
+
+ 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(
+ f"/messages/{id}",
+ body=await async_maybe_transform(
+ {
+ "attachments": attachments,
+ "content": content,
+ "is_pinned": is_pinned,
+ },
+ message_update_params.MessageUpdateParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=Message,
+ )
+
def list(
self,
*,
@@ -396,6 +496,9 @@ def __init__(self, messages: MessagesResource) -> None:
self.retrieve = to_raw_response_wrapper(
messages.retrieve,
)
+ self.update = to_raw_response_wrapper(
+ messages.update,
+ )
self.list = to_raw_response_wrapper(
messages.list,
)
@@ -411,6 +514,9 @@ def __init__(self, messages: AsyncMessagesResource) -> None:
self.retrieve = async_to_raw_response_wrapper(
messages.retrieve,
)
+ self.update = async_to_raw_response_wrapper(
+ messages.update,
+ )
self.list = async_to_raw_response_wrapper(
messages.list,
)
@@ -426,6 +532,9 @@ def __init__(self, messages: MessagesResource) -> None:
self.retrieve = to_streamed_response_wrapper(
messages.retrieve,
)
+ self.update = to_streamed_response_wrapper(
+ messages.update,
+ )
self.list = to_streamed_response_wrapper(
messages.list,
)
@@ -441,6 +550,9 @@ def __init__(self, messages: AsyncMessagesResource) -> None:
self.retrieve = async_to_streamed_response_wrapper(
messages.retrieve,
)
+ self.update = async_to_streamed_response_wrapper(
+ messages.update,
+ )
self.list = async_to_streamed_response_wrapper(
messages.list,
)
diff --git a/src/whop_sdk/resources/payments.py b/src/whop_sdk/resources/payments.py
index a1b5ddde..e486d203 100644
--- a/src/whop_sdk/resources/payments.py
+++ b/src/whop_sdk/resources/payments.py
@@ -22,6 +22,7 @@
from ..pagination import SyncCursorPage, AsyncCursorPage
from .._base_client import AsyncPaginator, make_request_options
from ..types.shared.payment import Payment
+from ..types.billing_reasons import BillingReasons
from ..types.shared.currency import Currency
from ..types.shared.direction import Direction
from ..types.payment_list_response import PaymentListResponse
@@ -100,19 +101,7 @@ def list(
company_id: str,
after: Optional[str] | Omit = omit,
before: Optional[str] | Omit = omit,
- billing_reasons: Optional[
- List[
- Literal[
- "subscription_create",
- "subscription_cycle",
- "subscription_update",
- "one_time",
- "manual",
- "subscription",
- ]
- ]
- ]
- | Omit = omit,
+ billing_reasons: Optional[List[BillingReasons]] | Omit = omit,
created_after: Union[str, datetime, None] | Omit = omit,
created_before: Union[str, datetime, None] | Omit = omit,
currencies: Optional[List[Currency]] | Omit = omit,
@@ -422,19 +411,7 @@ def list(
company_id: str,
after: Optional[str] | Omit = omit,
before: Optional[str] | Omit = omit,
- billing_reasons: Optional[
- List[
- Literal[
- "subscription_create",
- "subscription_cycle",
- "subscription_update",
- "one_time",
- "manual",
- "subscription",
- ]
- ]
- ]
- | Omit = omit,
+ billing_reasons: Optional[List[BillingReasons]] | Omit = omit,
created_after: Union[str, datetime, None] | Omit = omit,
created_before: Union[str, datetime, None] | Omit = omit,
currencies: Optional[List[Currency]] | Omit = omit,
diff --git a/src/whop_sdk/resources/plans.py b/src/whop_sdk/resources/plans.py
index 873be659..1c1ce602 100644
--- a/src/whop_sdk/resources/plans.py
+++ b/src/whop_sdk/resources/plans.py
@@ -71,8 +71,12 @@ def create(
plan_type: Optional[PlanType] | Omit = omit,
release_method: Optional[ReleaseMethod] | Omit = omit,
renewal_price: Optional[float] | Omit = omit,
+ stock: Optional[int] | Omit = omit,
+ strike_through_initial_price: Optional[float] | Omit = omit,
+ strike_through_renewal_price: Optional[float] | Omit = omit,
title: Optional[str] | Omit = omit,
trial_period_days: Optional[int] | Omit = omit,
+ unlimited_stock: Optional[bool] | Omit = omit,
visibility: Optional[Visibility] | Omit = omit,
# Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
# The extra values given here take precedence over values defined on the client or passed to this method.
@@ -95,7 +99,7 @@ def create(
product_id: The product the plan is related to.
- billing_period: The interval at which the plan charges (renewal plans).
+ billing_period: The interval in days at which the plan charges (renewal plans).
currency: The available currencies on the platform
@@ -123,10 +127,20 @@ def create(
renewal_price: The amount the customer is charged every billing period. Use only if a recurring
payment. Provided as a number in dollars. Eg: 10.43 for $10.43
+ stock: The number of units available for purchase.
+
+ strike_through_initial_price: The price to display with a strikethrough for the initial price. Provided as a
+ number in dollars. Eg: 19.99 for $19.99
+
+ strike_through_renewal_price: The price to display with a strikethrough for the renewal price. Provided as a
+ number in dollars. Eg: 19.99 for $19.99
+
title: The title of the plan. This will be visible on the product page to customers.
trial_period_days: The number of free trial days added before a renewal plan.
+ unlimited_stock: Limits/doesn't limit the number of units available for purchase.
+
visibility: Visibility of a resource
extra_headers: Send extra headers
@@ -155,8 +169,12 @@ def create(
"plan_type": plan_type,
"release_method": release_method,
"renewal_price": renewal_price,
+ "stock": stock,
+ "strike_through_initial_price": strike_through_initial_price,
+ "strike_through_renewal_price": strike_through_renewal_price,
"title": title,
"trial_period_days": trial_period_days,
+ "unlimited_stock": unlimited_stock,
"visibility": visibility,
},
plan_create_params.PlanCreateParams,
@@ -219,8 +237,12 @@ def update(
offer_cancel_discount: Optional[bool] | Omit = omit,
override_tax_type: Optional[TaxType] | Omit = omit,
renewal_price: Optional[float] | Omit = omit,
+ stock: Optional[int] | Omit = omit,
+ strike_through_initial_price: Optional[float] | Omit = omit,
+ strike_through_renewal_price: Optional[float] | Omit = omit,
title: Optional[str] | Omit = omit,
trial_period_days: Optional[int] | Omit = omit,
+ unlimited_stock: Optional[bool] | Omit = omit,
visibility: Optional[Visibility] | Omit = omit,
# Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
# The extra values given here take precedence over values defined on the client or passed to this method.
@@ -262,10 +284,20 @@ def update(
renewal_price: The amount the customer is charged every billing period.
+ stock: The number of units available for purchase.
+
+ strike_through_initial_price: The price to display with a strikethrough for the initial price. Provided as a
+ number in dollars. Eg: 19.99 for $19.99
+
+ strike_through_renewal_price: The price to display with a strikethrough for the renewal price. Provided as a
+ number in dollars. Eg: 19.99 for $19.99
+
title: The title of the plan. This will be visible on the product page to customers.
trial_period_days: The number of free trial days added before a renewal plan.
+ unlimited_stock: Limits/doesn't limit the number of units available for purchase.
+
visibility: Visibility of a resource
extra_headers: Send extra headers
@@ -293,8 +325,12 @@ def update(
"offer_cancel_discount": offer_cancel_discount,
"override_tax_type": override_tax_type,
"renewal_price": renewal_price,
+ "stock": stock,
+ "strike_through_initial_price": strike_through_initial_price,
+ "strike_through_renewal_price": strike_through_renewal_price,
"title": title,
"trial_period_days": trial_period_days,
+ "unlimited_stock": unlimited_stock,
"visibility": visibility,
},
plan_update_params.PlanUpdateParams,
@@ -468,8 +504,12 @@ async def create(
plan_type: Optional[PlanType] | Omit = omit,
release_method: Optional[ReleaseMethod] | Omit = omit,
renewal_price: Optional[float] | Omit = omit,
+ stock: Optional[int] | Omit = omit,
+ strike_through_initial_price: Optional[float] | Omit = omit,
+ strike_through_renewal_price: Optional[float] | Omit = omit,
title: Optional[str] | Omit = omit,
trial_period_days: Optional[int] | Omit = omit,
+ unlimited_stock: Optional[bool] | Omit = omit,
visibility: Optional[Visibility] | Omit = omit,
# Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
# The extra values given here take precedence over values defined on the client or passed to this method.
@@ -492,7 +532,7 @@ async def create(
product_id: The product the plan is related to.
- billing_period: The interval at which the plan charges (renewal plans).
+ billing_period: The interval in days at which the plan charges (renewal plans).
currency: The available currencies on the platform
@@ -520,10 +560,20 @@ async def create(
renewal_price: The amount the customer is charged every billing period. Use only if a recurring
payment. Provided as a number in dollars. Eg: 10.43 for $10.43
+ stock: The number of units available for purchase.
+
+ strike_through_initial_price: The price to display with a strikethrough for the initial price. Provided as a
+ number in dollars. Eg: 19.99 for $19.99
+
+ strike_through_renewal_price: The price to display with a strikethrough for the renewal price. Provided as a
+ number in dollars. Eg: 19.99 for $19.99
+
title: The title of the plan. This will be visible on the product page to customers.
trial_period_days: The number of free trial days added before a renewal plan.
+ unlimited_stock: Limits/doesn't limit the number of units available for purchase.
+
visibility: Visibility of a resource
extra_headers: Send extra headers
@@ -552,8 +602,12 @@ async def create(
"plan_type": plan_type,
"release_method": release_method,
"renewal_price": renewal_price,
+ "stock": stock,
+ "strike_through_initial_price": strike_through_initial_price,
+ "strike_through_renewal_price": strike_through_renewal_price,
"title": title,
"trial_period_days": trial_period_days,
+ "unlimited_stock": unlimited_stock,
"visibility": visibility,
},
plan_create_params.PlanCreateParams,
@@ -616,8 +670,12 @@ async def update(
offer_cancel_discount: Optional[bool] | Omit = omit,
override_tax_type: Optional[TaxType] | Omit = omit,
renewal_price: Optional[float] | Omit = omit,
+ stock: Optional[int] | Omit = omit,
+ strike_through_initial_price: Optional[float] | Omit = omit,
+ strike_through_renewal_price: Optional[float] | Omit = omit,
title: Optional[str] | Omit = omit,
trial_period_days: Optional[int] | Omit = omit,
+ unlimited_stock: Optional[bool] | Omit = omit,
visibility: Optional[Visibility] | Omit = omit,
# Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
# The extra values given here take precedence over values defined on the client or passed to this method.
@@ -659,10 +717,20 @@ async def update(
renewal_price: The amount the customer is charged every billing period.
+ stock: The number of units available for purchase.
+
+ strike_through_initial_price: The price to display with a strikethrough for the initial price. Provided as a
+ number in dollars. Eg: 19.99 for $19.99
+
+ strike_through_renewal_price: The price to display with a strikethrough for the renewal price. Provided as a
+ number in dollars. Eg: 19.99 for $19.99
+
title: The title of the plan. This will be visible on the product page to customers.
trial_period_days: The number of free trial days added before a renewal plan.
+ unlimited_stock: Limits/doesn't limit the number of units available for purchase.
+
visibility: Visibility of a resource
extra_headers: Send extra headers
@@ -690,8 +758,12 @@ async def update(
"offer_cancel_discount": offer_cancel_discount,
"override_tax_type": override_tax_type,
"renewal_price": renewal_price,
+ "stock": stock,
+ "strike_through_initial_price": strike_through_initial_price,
+ "strike_through_renewal_price": strike_through_renewal_price,
"title": title,
"trial_period_days": trial_period_days,
+ "unlimited_stock": unlimited_stock,
"visibility": visibility,
},
plan_update_params.PlanUpdateParams,
diff --git a/src/whop_sdk/resources/products.py b/src/whop_sdk/resources/products.py
index 0c9cba7c..429bf9d8 100644
--- a/src/whop_sdk/resources/products.py
+++ b/src/whop_sdk/resources/products.py
@@ -60,8 +60,6 @@ def create(
*,
company_id: str,
title: str,
- access_pass_type: Optional[AccessPassType] | Omit = omit,
- banner_image: Optional[product_create_params.BannerImage] | Omit = omit,
business_type: Optional[BusinessTypes] | Omit = omit,
collect_shipping_address: Optional[bool] | Omit = omit,
custom_cta: Optional[CustomCta] | Omit = omit,
@@ -101,10 +99,6 @@ def create(
title: The title of the product.
- access_pass_type: The different types an access pass can be.
-
- banner_image: A banner image for the product in png, jpeg format
-
business_type: The different business types a company can be.
collect_shipping_address: Whether or not to collect shipping information at checkout from the customer.
@@ -159,8 +153,6 @@ def create(
{
"company_id": company_id,
"title": title,
- "access_pass_type": access_pass_type,
- "banner_image": banner_image,
"business_type": business_type,
"collect_shipping_address": collect_shipping_address,
"custom_cta": custom_cta,
@@ -246,6 +238,7 @@ def update(
product_tax_code_id: Optional[str] | Omit = omit,
redirect_purchase_url: Optional[str] | Omit = omit,
route: Optional[str] | Omit = omit,
+ store_page_config: Optional[product_update_params.StorePageConfig] | Omit = omit,
title: Optional[str] | Omit = omit,
visibility: Optional[Visibility] | Omit = omit,
# Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
@@ -298,6 +291,8 @@ def update(
route: The route of the product.
+ store_page_config: Configuration for a product on the company's store page.
+
title: The title of the product.
visibility: Visibility of a resource
@@ -332,6 +327,7 @@ def update(
"product_tax_code_id": product_tax_code_id,
"redirect_purchase_url": redirect_purchase_url,
"route": route,
+ "store_page_config": store_page_config,
"title": title,
"visibility": visibility,
},
@@ -485,8 +481,6 @@ async def create(
*,
company_id: str,
title: str,
- access_pass_type: Optional[AccessPassType] | Omit = omit,
- banner_image: Optional[product_create_params.BannerImage] | Omit = omit,
business_type: Optional[BusinessTypes] | Omit = omit,
collect_shipping_address: Optional[bool] | Omit = omit,
custom_cta: Optional[CustomCta] | Omit = omit,
@@ -526,10 +520,6 @@ async def create(
title: The title of the product.
- access_pass_type: The different types an access pass can be.
-
- banner_image: A banner image for the product in png, jpeg format
-
business_type: The different business types a company can be.
collect_shipping_address: Whether or not to collect shipping information at checkout from the customer.
@@ -584,8 +574,6 @@ async def create(
{
"company_id": company_id,
"title": title,
- "access_pass_type": access_pass_type,
- "banner_image": banner_image,
"business_type": business_type,
"collect_shipping_address": collect_shipping_address,
"custom_cta": custom_cta,
@@ -671,6 +659,7 @@ async def update(
product_tax_code_id: Optional[str] | Omit = omit,
redirect_purchase_url: Optional[str] | Omit = omit,
route: Optional[str] | Omit = omit,
+ store_page_config: Optional[product_update_params.StorePageConfig] | Omit = omit,
title: Optional[str] | Omit = omit,
visibility: Optional[Visibility] | Omit = omit,
# Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
@@ -723,6 +712,8 @@ async def update(
route: The route of the product.
+ store_page_config: Configuration for a product on the company's store page.
+
title: The title of the product.
visibility: Visibility of a resource
@@ -757,6 +748,7 @@ async def update(
"product_tax_code_id": product_tax_code_id,
"redirect_purchase_url": redirect_purchase_url,
"route": route,
+ "store_page_config": store_page_config,
"title": title,
"visibility": visibility,
},
diff --git a/src/whop_sdk/resources/promo_codes.py b/src/whop_sdk/resources/promo_codes.py
new file mode 100644
index 00000000..ad73e9d5
--- /dev/null
+++ b/src/whop_sdk/resources/promo_codes.py
@@ -0,0 +1,652 @@
+# 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
+
+import httpx
+
+from ..types import PromoCodeStatus, promo_code_list_params, promo_code_create_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 ..pagination import SyncCursorPage, AsyncCursorPage
+from .._base_client import AsyncPaginator, make_request_options
+from ..types.promo_code import PromoCode
+from ..types.shared.currency import Currency
+from ..types.promo_code_status import PromoCodeStatus
+from ..types.shared.promo_type import PromoType
+from ..types.promo_code_list_response import PromoCodeListResponse
+from ..types.promo_code_delete_response import PromoCodeDeleteResponse
+
+__all__ = ["PromoCodesResource", "AsyncPromoCodesResource"]
+
+
+class PromoCodesResource(SyncAPIResource):
+ @cached_property
+ def with_raw_response(self) -> PromoCodesResourceWithRawResponse:
+ """
+ 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 PromoCodesResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> PromoCodesResourceWithStreamingResponse:
+ """
+ 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 PromoCodesResourceWithStreamingResponse(self)
+
+ def create(
+ self,
+ *,
+ amount_off: float,
+ base_currency: Currency,
+ code: str,
+ company_id: str,
+ new_users_only: bool,
+ promo_duration_months: int,
+ promo_type: PromoType,
+ churned_users_only: Optional[bool] | Omit = omit,
+ existing_memberships_only: Optional[bool] | Omit = omit,
+ expires_at: Union[str, datetime, None] | Omit = omit,
+ one_per_customer: Optional[bool] | Omit = omit,
+ plan_ids: Optional[SequenceNotStr[str]] | Omit = omit,
+ product_id: Optional[str] | Omit = omit,
+ stock: Optional[int] | Omit = omit,
+ unlimited_stock: Optional[bool] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> PromoCode:
+ """
+ Create a new promo code for a product or plan
+
+ Required permissions:
+
+ - `promo_code:create`
+ - `access_pass:basic:read`
+
+ Args:
+ amount_off: The amount off (% or flat amount) for the promo.
+
+ base_currency: The monetary currency of the promo code.
+
+ code: The specific code used to apply the promo at checkout.
+
+ company_id: The id of the company to create the promo code for.
+
+ new_users_only: Restricts promo use to only users who have never purchased from the company
+ before.
+
+ promo_duration_months: The number of months this promo code is applied and valid for.
+
+ promo_type: The type (% or flat amount) of the promo.
+
+ churned_users_only: Restricts promo use to only users who have churned from the company before.
+
+ existing_memberships_only: Whether this promo code is for existing memberships only (cancelations)
+
+ expires_at: The date/time of when the promo expires.
+
+ one_per_customer: Restricts promo use to only be applied once per customer.
+
+ plan_ids: The IDs of the plans that the promo code applies to. If product_id is provided,
+ it will only apply to plans attached to that product
+
+ product_id: The product to lock the promo code to, if any. If provided will filter out any
+ plan ids not attached to this product
+
+ stock: The quantity limit on the number of uses.
+
+ unlimited_stock: Whether or not the promo code should have unlimited stock.
+
+ 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(
+ "/promo_codes",
+ body=maybe_transform(
+ {
+ "amount_off": amount_off,
+ "base_currency": base_currency,
+ "code": code,
+ "company_id": company_id,
+ "new_users_only": new_users_only,
+ "promo_duration_months": promo_duration_months,
+ "promo_type": promo_type,
+ "churned_users_only": churned_users_only,
+ "existing_memberships_only": existing_memberships_only,
+ "expires_at": expires_at,
+ "one_per_customer": one_per_customer,
+ "plan_ids": plan_ids,
+ "product_id": product_id,
+ "stock": stock,
+ "unlimited_stock": unlimited_stock,
+ },
+ promo_code_create_params.PromoCodeCreateParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=PromoCode,
+ )
+
+ 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,
+ ) -> PromoCode:
+ """
+ Retrieves a promo code by ID
+
+ Required permissions:
+
+ - `promo_code:basic:read`
+ - `access_pass: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(
+ f"/promo_codes/{id}",
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=PromoCode,
+ )
+
+ def list(
+ self,
+ *,
+ company_id: str,
+ after: Optional[str] | Omit = omit,
+ before: Optional[str] | Omit = omit,
+ first: Optional[int] | Omit = omit,
+ last: Optional[int] | Omit = omit,
+ plan_ids: Optional[SequenceNotStr[str]] | Omit = omit,
+ product_ids: Optional[SequenceNotStr[str]] | Omit = omit,
+ status: Optional[PromoCodeStatus] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this 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[PromoCodeListResponse]:
+ """
+ Lists promo codes for a company
+
+ Required permissions:
+
+ - `promo_code:basic:read`
+ - `access_pass:basic:read`
+
+ Args:
+ company_id: The ID of the company to list promo codes 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.
+
+ first: Returns the first _n_ elements from the list.
+
+ last: Returns the last _n_ elements from the list.
+
+ plan_ids: Filter promo codes by plan ID(s)
+
+ product_ids: Filter promo codes by product ID(s)
+
+ status: Statuses for promo codes
+
+ 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(
+ "/promo_codes",
+ page=SyncCursorPage[PromoCodeListResponse],
+ 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,
+ "first": first,
+ "last": last,
+ "plan_ids": plan_ids,
+ "product_ids": product_ids,
+ "status": status,
+ },
+ promo_code_list_params.PromoCodeListParams,
+ ),
+ ),
+ model=PromoCodeListResponse,
+ )
+
+ 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,
+ ) -> PromoCodeDeleteResponse:
+ """
+ Archive a promo code, preventing further use
+
+ Required permissions:
+
+ - `promo_code: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(
+ f"/promo_codes/{id}",
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=PromoCodeDeleteResponse,
+ )
+
+
+class AsyncPromoCodesResource(AsyncAPIResource):
+ @cached_property
+ def with_raw_response(self) -> AsyncPromoCodesResourceWithRawResponse:
+ """
+ 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 AsyncPromoCodesResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> AsyncPromoCodesResourceWithStreamingResponse:
+ """
+ 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 AsyncPromoCodesResourceWithStreamingResponse(self)
+
+ async def create(
+ self,
+ *,
+ amount_off: float,
+ base_currency: Currency,
+ code: str,
+ company_id: str,
+ new_users_only: bool,
+ promo_duration_months: int,
+ promo_type: PromoType,
+ churned_users_only: Optional[bool] | Omit = omit,
+ existing_memberships_only: Optional[bool] | Omit = omit,
+ expires_at: Union[str, datetime, None] | Omit = omit,
+ one_per_customer: Optional[bool] | Omit = omit,
+ plan_ids: Optional[SequenceNotStr[str]] | Omit = omit,
+ product_id: Optional[str] | Omit = omit,
+ stock: Optional[int] | Omit = omit,
+ unlimited_stock: Optional[bool] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> PromoCode:
+ """
+ Create a new promo code for a product or plan
+
+ Required permissions:
+
+ - `promo_code:create`
+ - `access_pass:basic:read`
+
+ Args:
+ amount_off: The amount off (% or flat amount) for the promo.
+
+ base_currency: The monetary currency of the promo code.
+
+ code: The specific code used to apply the promo at checkout.
+
+ company_id: The id of the company to create the promo code for.
+
+ new_users_only: Restricts promo use to only users who have never purchased from the company
+ before.
+
+ promo_duration_months: The number of months this promo code is applied and valid for.
+
+ promo_type: The type (% or flat amount) of the promo.
+
+ churned_users_only: Restricts promo use to only users who have churned from the company before.
+
+ existing_memberships_only: Whether this promo code is for existing memberships only (cancelations)
+
+ expires_at: The date/time of when the promo expires.
+
+ one_per_customer: Restricts promo use to only be applied once per customer.
+
+ plan_ids: The IDs of the plans that the promo code applies to. If product_id is provided,
+ it will only apply to plans attached to that product
+
+ product_id: The product to lock the promo code to, if any. If provided will filter out any
+ plan ids not attached to this product
+
+ stock: The quantity limit on the number of uses.
+
+ unlimited_stock: Whether or not the promo code should have unlimited stock.
+
+ 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(
+ "/promo_codes",
+ body=await async_maybe_transform(
+ {
+ "amount_off": amount_off,
+ "base_currency": base_currency,
+ "code": code,
+ "company_id": company_id,
+ "new_users_only": new_users_only,
+ "promo_duration_months": promo_duration_months,
+ "promo_type": promo_type,
+ "churned_users_only": churned_users_only,
+ "existing_memberships_only": existing_memberships_only,
+ "expires_at": expires_at,
+ "one_per_customer": one_per_customer,
+ "plan_ids": plan_ids,
+ "product_id": product_id,
+ "stock": stock,
+ "unlimited_stock": unlimited_stock,
+ },
+ promo_code_create_params.PromoCodeCreateParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=PromoCode,
+ )
+
+ 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,
+ ) -> PromoCode:
+ """
+ Retrieves a promo code by ID
+
+ Required permissions:
+
+ - `promo_code:basic:read`
+ - `access_pass: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(
+ f"/promo_codes/{id}",
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=PromoCode,
+ )
+
+ def list(
+ self,
+ *,
+ company_id: str,
+ after: Optional[str] | Omit = omit,
+ before: Optional[str] | Omit = omit,
+ first: Optional[int] | Omit = omit,
+ last: Optional[int] | Omit = omit,
+ plan_ids: Optional[SequenceNotStr[str]] | Omit = omit,
+ product_ids: Optional[SequenceNotStr[str]] | Omit = omit,
+ status: Optional[PromoCodeStatus] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this 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[PromoCodeListResponse, AsyncCursorPage[PromoCodeListResponse]]:
+ """
+ Lists promo codes for a company
+
+ Required permissions:
+
+ - `promo_code:basic:read`
+ - `access_pass:basic:read`
+
+ Args:
+ company_id: The ID of the company to list promo codes 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.
+
+ first: Returns the first _n_ elements from the list.
+
+ last: Returns the last _n_ elements from the list.
+
+ plan_ids: Filter promo codes by plan ID(s)
+
+ product_ids: Filter promo codes by product ID(s)
+
+ status: Statuses for promo codes
+
+ 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(
+ "/promo_codes",
+ page=AsyncCursorPage[PromoCodeListResponse],
+ 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,
+ "first": first,
+ "last": last,
+ "plan_ids": plan_ids,
+ "product_ids": product_ids,
+ "status": status,
+ },
+ promo_code_list_params.PromoCodeListParams,
+ ),
+ ),
+ model=PromoCodeListResponse,
+ )
+
+ 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,
+ ) -> PromoCodeDeleteResponse:
+ """
+ Archive a promo code, preventing further use
+
+ Required permissions:
+
+ - `promo_code: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(
+ f"/promo_codes/{id}",
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=PromoCodeDeleteResponse,
+ )
+
+
+class PromoCodesResourceWithRawResponse:
+ def __init__(self, promo_codes: PromoCodesResource) -> None:
+ self._promo_codes = promo_codes
+
+ self.create = to_raw_response_wrapper(
+ promo_codes.create,
+ )
+ self.retrieve = to_raw_response_wrapper(
+ promo_codes.retrieve,
+ )
+ self.list = to_raw_response_wrapper(
+ promo_codes.list,
+ )
+ self.delete = to_raw_response_wrapper(
+ promo_codes.delete,
+ )
+
+
+class AsyncPromoCodesResourceWithRawResponse:
+ def __init__(self, promo_codes: AsyncPromoCodesResource) -> None:
+ self._promo_codes = promo_codes
+
+ self.create = async_to_raw_response_wrapper(
+ promo_codes.create,
+ )
+ self.retrieve = async_to_raw_response_wrapper(
+ promo_codes.retrieve,
+ )
+ self.list = async_to_raw_response_wrapper(
+ promo_codes.list,
+ )
+ self.delete = async_to_raw_response_wrapper(
+ promo_codes.delete,
+ )
+
+
+class PromoCodesResourceWithStreamingResponse:
+ def __init__(self, promo_codes: PromoCodesResource) -> None:
+ self._promo_codes = promo_codes
+
+ self.create = to_streamed_response_wrapper(
+ promo_codes.create,
+ )
+ self.retrieve = to_streamed_response_wrapper(
+ promo_codes.retrieve,
+ )
+ self.list = to_streamed_response_wrapper(
+ promo_codes.list,
+ )
+ self.delete = to_streamed_response_wrapper(
+ promo_codes.delete,
+ )
+
+
+class AsyncPromoCodesResourceWithStreamingResponse:
+ def __init__(self, promo_codes: AsyncPromoCodesResource) -> None:
+ self._promo_codes = promo_codes
+
+ self.create = async_to_streamed_response_wrapper(
+ promo_codes.create,
+ )
+ self.retrieve = async_to_streamed_response_wrapper(
+ promo_codes.retrieve,
+ )
+ self.list = async_to_streamed_response_wrapper(
+ promo_codes.list,
+ )
+ self.delete = async_to_streamed_response_wrapper(
+ promo_codes.delete,
+ )
diff --git a/src/whop_sdk/resources/reviews.py b/src/whop_sdk/resources/reviews.py
new file mode 100644
index 00000000..4a6538e4
--- /dev/null
+++ b/src/whop_sdk/resources/reviews.py
@@ -0,0 +1,315 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Optional
+
+import httpx
+
+from ..types import review_list_params
+from .._types import Body, Omit, Query, Headers, NotGiven, omit, not_given
+from .._utils import maybe_transform
+from .._compat import cached_property
+from .._resource import SyncAPIResource, AsyncAPIResource
+from .._response import (
+ to_raw_response_wrapper,
+ to_streamed_response_wrapper,
+ async_to_raw_response_wrapper,
+ async_to_streamed_response_wrapper,
+)
+from ..pagination import SyncCursorPage, AsyncCursorPage
+from .._base_client import AsyncPaginator, make_request_options
+from ..types.review_list_response import ReviewListResponse
+from ..types.review_retrieve_response import ReviewRetrieveResponse
+
+__all__ = ["ReviewsResource", "AsyncReviewsResource"]
+
+
+class ReviewsResource(SyncAPIResource):
+ @cached_property
+ def with_raw_response(self) -> ReviewsResourceWithRawResponse:
+ """
+ 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 ReviewsResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> ReviewsResourceWithStreamingResponse:
+ """
+ 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 ReviewsResourceWithStreamingResponse(self)
+
+ def retrieve(
+ self,
+ id: str,
+ *,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> ReviewRetrieveResponse:
+ """
+ Retrieve a review by its ID
+
+ 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(
+ f"/reviews/{id}",
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=ReviewRetrieveResponse,
+ )
+
+ def list(
+ self,
+ *,
+ product_id: str,
+ after: Optional[str] | Omit = omit,
+ before: Optional[str] | Omit = omit,
+ first: Optional[int] | Omit = omit,
+ last: Optional[int] | Omit = omit,
+ max_stars: Optional[int] | Omit = omit,
+ min_stars: Optional[int] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> SyncCursorPage[ReviewListResponse]:
+ """
+ List all reviews
+
+ Args:
+ product_id: The ID of the product
+
+ 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.
+
+ first: Returns the first _n_ elements from the list.
+
+ last: Returns the last _n_ elements from the list.
+
+ max_stars: The maximum star rating of the review (inclusive)
+
+ min_stars: The minimum star rating of the review (inclusive)
+
+ 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(
+ "/reviews",
+ page=SyncCursorPage[ReviewListResponse],
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=maybe_transform(
+ {
+ "product_id": product_id,
+ "after": after,
+ "before": before,
+ "first": first,
+ "last": last,
+ "max_stars": max_stars,
+ "min_stars": min_stars,
+ },
+ review_list_params.ReviewListParams,
+ ),
+ ),
+ model=ReviewListResponse,
+ )
+
+
+class AsyncReviewsResource(AsyncAPIResource):
+ @cached_property
+ def with_raw_response(self) -> AsyncReviewsResourceWithRawResponse:
+ """
+ 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 AsyncReviewsResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> AsyncReviewsResourceWithStreamingResponse:
+ """
+ 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 AsyncReviewsResourceWithStreamingResponse(self)
+
+ async def retrieve(
+ self,
+ id: str,
+ *,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> ReviewRetrieveResponse:
+ """
+ Retrieve a review by its ID
+
+ 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(
+ f"/reviews/{id}",
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=ReviewRetrieveResponse,
+ )
+
+ def list(
+ self,
+ *,
+ product_id: str,
+ after: Optional[str] | Omit = omit,
+ before: Optional[str] | Omit = omit,
+ first: Optional[int] | Omit = omit,
+ last: Optional[int] | Omit = omit,
+ max_stars: Optional[int] | Omit = omit,
+ min_stars: Optional[int] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> AsyncPaginator[ReviewListResponse, AsyncCursorPage[ReviewListResponse]]:
+ """
+ List all reviews
+
+ Args:
+ product_id: The ID of the product
+
+ 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.
+
+ first: Returns the first _n_ elements from the list.
+
+ last: Returns the last _n_ elements from the list.
+
+ max_stars: The maximum star rating of the review (inclusive)
+
+ min_stars: The minimum star rating of the review (inclusive)
+
+ 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(
+ "/reviews",
+ page=AsyncCursorPage[ReviewListResponse],
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=maybe_transform(
+ {
+ "product_id": product_id,
+ "after": after,
+ "before": before,
+ "first": first,
+ "last": last,
+ "max_stars": max_stars,
+ "min_stars": min_stars,
+ },
+ review_list_params.ReviewListParams,
+ ),
+ ),
+ model=ReviewListResponse,
+ )
+
+
+class ReviewsResourceWithRawResponse:
+ def __init__(self, reviews: ReviewsResource) -> None:
+ self._reviews = reviews
+
+ self.retrieve = to_raw_response_wrapper(
+ reviews.retrieve,
+ )
+ self.list = to_raw_response_wrapper(
+ reviews.list,
+ )
+
+
+class AsyncReviewsResourceWithRawResponse:
+ def __init__(self, reviews: AsyncReviewsResource) -> None:
+ self._reviews = reviews
+
+ self.retrieve = async_to_raw_response_wrapper(
+ reviews.retrieve,
+ )
+ self.list = async_to_raw_response_wrapper(
+ reviews.list,
+ )
+
+
+class ReviewsResourceWithStreamingResponse:
+ def __init__(self, reviews: ReviewsResource) -> None:
+ self._reviews = reviews
+
+ self.retrieve = to_streamed_response_wrapper(
+ reviews.retrieve,
+ )
+ self.list = to_streamed_response_wrapper(
+ reviews.list,
+ )
+
+
+class AsyncReviewsResourceWithStreamingResponse:
+ def __init__(self, reviews: AsyncReviewsResource) -> None:
+ self._reviews = reviews
+
+ self.retrieve = async_to_streamed_response_wrapper(
+ reviews.retrieve,
+ )
+ self.list = async_to_streamed_response_wrapper(
+ reviews.list,
+ )
diff --git a/src/whop_sdk/types/__init__.py b/src/whop_sdk/types/__init__.py
index 37c1e57a..33bebb0e 100644
--- a/src/whop_sdk/types/__init__.py
+++ b/src/whop_sdk/types/__init__.py
@@ -2,6 +2,8 @@
from __future__ import annotations
+from .course import Course as Course
+from .lesson import Lesson as Lesson
from .shared import (
App as App,
Plan as Plan,
@@ -64,27 +66,44 @@
EmailNotificationPreferences as EmailNotificationPreferences,
CourseLessonInteractionListItem as CourseLessonInteractionListItem,
)
+from .languages import Languages as Languages
+from .promo_code import PromoCode as PromoCode
+from .card_brands import CardBrands as CardBrands
+from .lesson_types import LessonTypes as LessonTypes
+from .review_status import ReviewStatus as ReviewStatus
+from .course_chapter import CourseChapter as CourseChapter
+from .promo_duration import PromoDuration as PromoDuration
from .app_list_params import AppListParams as AppListParams
+from .billing_reasons import BillingReasons as BillingReasons
from .plan_list_params import PlanListParams as PlanListParams
from .app_create_params import AppCreateParams as AppCreateParams
from .app_list_response import AppListResponse as AppListResponse
from .app_update_params import AppUpdateParams as AppUpdateParams
from .entry_list_params import EntryListParams as EntryListParams
from .forum_list_params import ForumListParams as ForumListParams
+from .promo_code_status import PromoCodeStatus as PromoCodeStatus
+from .course_list_params import CourseListParams as CourseListParams
from .member_list_params import MemberListParams as MemberListParams
from .plan_create_params import PlanCreateParams as PlanCreateParams
from .plan_list_response import PlanListResponse as PlanListResponse
from .plan_update_params import PlanUpdateParams as PlanUpdateParams
+from .review_list_params import ReviewListParams as ReviewListParams
from .entry_list_response import EntryListResponse as EntryListResponse
from .forum_list_response import ForumListResponse as ForumListResponse
from .forum_update_params import ForumUpdateParams as ForumUpdateParams
from .invoice_list_params import InvoiceListParams as InvoiceListParams
+from .lesson_visibilities import LessonVisibilities as LessonVisibilities
from .message_list_params import MessageListParams as MessageListParams
from .payment_list_params import PaymentListParams as PaymentListParams
from .product_list_params import ProductListParams as ProductListParams
+from .course_create_params import CourseCreateParams as CourseCreateParams
+from .course_list_response import CourseListResponse as CourseListResponse
+from .course_update_params import CourseUpdateParams as CourseUpdateParams
from .member_list_response import MemberListResponse as MemberListResponse
+from .payment_method_types import PaymentMethodTypes as PaymentMethodTypes
from .plan_delete_response import PlanDeleteResponse as PlanDeleteResponse
from .reaction_list_params import ReactionListParams as ReactionListParams
+from .review_list_response import ReviewListResponse as ReviewListResponse
from .shipment_list_params import ShipmentListParams as ShipmentListParams
from .transfer_list_params import TransferListParams as TransferListParams
from .unwrap_webhook_event import UnwrapWebhookEvent as UnwrapWebhookEvent
@@ -93,14 +112,17 @@
from .invoice_void_response import InvoiceVoidResponse as InvoiceVoidResponse
from .message_create_params import MessageCreateParams as MessageCreateParams
from .message_list_response import MessageListResponse as MessageListResponse
+from .message_update_params import MessageUpdateParams as MessageUpdateParams
from .payment_list_response import PaymentListResponse as PaymentListResponse
from .payment_refund_params import PaymentRefundParams as PaymentRefundParams
from .product_create_params import ProductCreateParams as ProductCreateParams
from .product_update_params import ProductUpdateParams as ProductUpdateParams
+from .course_delete_response import CourseDeleteResponse as CourseDeleteResponse
from .entry_approve_response import EntryApproveResponse as EntryApproveResponse
from .experience_list_params import ExperienceListParams as ExperienceListParams
from .forum_post_list_params import ForumPostListParams as ForumPostListParams
from .membership_list_params import MembershipListParams as MembershipListParams
+from .promo_code_list_params import PromoCodeListParams as PromoCodeListParams
from .reaction_create_params import ReactionCreateParams as ReactionCreateParams
from .reaction_list_response import ReactionListResponse as ReactionListResponse
from .shipment_create_params import ShipmentCreateParams as ShipmentCreateParams
@@ -121,28 +143,45 @@
from .experience_update_params import ExperienceUpdateParams as ExperienceUpdateParams
from .forum_post_create_params import ForumPostCreateParams as ForumPostCreateParams
from .forum_post_list_response import ForumPostListResponse as ForumPostListResponse
+from .forum_post_update_params import ForumPostUpdateParams as ForumPostUpdateParams
from .member_retrieve_response import MemberRetrieveResponse as MemberRetrieveResponse
from .membership_cancel_params import MembershipCancelParams as MembershipCancelParams
from .membership_list_response import MembershipListResponse as MembershipListResponse
from .membership_update_params import MembershipUpdateParams as MembershipUpdateParams
+from .promo_code_create_params import PromoCodeCreateParams as PromoCodeCreateParams
+from .promo_code_list_response import PromoCodeListResponse as PromoCodeListResponse
+from .review_retrieve_response import ReviewRetrieveResponse as ReviewRetrieveResponse
+from .assessment_question_types import AssessmentQuestionTypes as AssessmentQuestionTypes
+from .course_lesson_list_params import CourseLessonListParams as CourseLessonListParams
from .chat_channel_list_response import ChatChannelListResponse as ChatChannelListResponse
from .chat_channel_update_params import ChatChannelUpdateParams as ChatChannelUpdateParams
+from .course_chapter_list_params import CourseChapterListParams as CourseChapterListParams
from .entry_denied_webhook_event import EntryDeniedWebhookEvent as EntryDeniedWebhookEvent
from .experience_delete_response import ExperienceDeleteResponse as ExperienceDeleteResponse
from .invoice_paid_webhook_event import InvoicePaidWebhookEvent as InvoicePaidWebhookEvent
+from .promo_code_delete_response import PromoCodeDeleteResponse as PromoCodeDeleteResponse
from .user_check_access_response import UserCheckAccessResponse as UserCheckAccessResponse
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
+from .course_lesson_update_params import CourseLessonUpdateParams as CourseLessonUpdateParams
from .entry_created_webhook_event import EntryCreatedWebhookEvent as EntryCreatedWebhookEvent
from .entry_deleted_webhook_event import EntryDeletedWebhookEvent as EntryDeletedWebhookEvent
+from .experience_duplicate_params import ExperienceDuplicateParams as ExperienceDuplicateParams
from .support_channel_list_params import SupportChannelListParams as SupportChannelListParams
+from .course_chapter_create_params import CourseChapterCreateParams as CourseChapterCreateParams
+from .course_chapter_list_response import CourseChapterListResponse as CourseChapterListResponse
+from .course_chapter_update_params import CourseChapterUpdateParams as CourseChapterUpdateParams
from .entry_approved_webhook_event import EntryApprovedWebhookEvent as EntryApprovedWebhookEvent
from .invoice_voided_webhook_event import InvoiceVoidedWebhookEvent as InvoiceVoidedWebhookEvent
from .payment_failed_webhook_event import PaymentFailedWebhookEvent as PaymentFailedWebhookEvent
from .authorized_user_list_response import AuthorizedUserListResponse as AuthorizedUserListResponse
+from .course_lesson_delete_response import CourseLessonDeleteResponse as CourseLessonDeleteResponse
from .invoice_created_webhook_event import InvoiceCreatedWebhookEvent as InvoiceCreatedWebhookEvent
from .payment_pending_webhook_event import PaymentPendingWebhookEvent as PaymentPendingWebhookEvent
from .support_channel_create_params import SupportChannelCreateParams as SupportChannelCreateParams
from .support_channel_list_response import SupportChannelListResponse as SupportChannelListResponse
+from .course_chapter_delete_response import CourseChapterDeleteResponse as CourseChapterDeleteResponse
from .invoice_past_due_webhook_event import InvoicePastDueWebhookEvent as InvoicePastDueWebhookEvent
from .payment_succeeded_webhook_event import PaymentSucceededWebhookEvent as PaymentSucceededWebhookEvent
from .ledger_account_retrieve_response import LedgerAccountRetrieveResponse as LedgerAccountRetrieveResponse
diff --git a/src/whop_sdk/types/assessment_question_types.py b/src/whop_sdk/types/assessment_question_types.py
new file mode 100644
index 00000000..35cf1f44
--- /dev/null
+++ b/src/whop_sdk/types/assessment_question_types.py
@@ -0,0 +1,7 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing_extensions import Literal, TypeAlias
+
+__all__ = ["AssessmentQuestionTypes"]
+
+AssessmentQuestionTypes: TypeAlias = Literal["short_answer", "true_false", "multiple_choice", "multiple_select"]
diff --git a/src/whop_sdk/types/billing_reasons.py b/src/whop_sdk/types/billing_reasons.py
new file mode 100644
index 00000000..a35732a1
--- /dev/null
+++ b/src/whop_sdk/types/billing_reasons.py
@@ -0,0 +1,9 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing_extensions import Literal, TypeAlias
+
+__all__ = ["BillingReasons"]
+
+BillingReasons: TypeAlias = Literal[
+ "subscription_create", "subscription_cycle", "subscription_update", "one_time", "manual", "subscription"
+]
diff --git a/src/whop_sdk/types/card_brands.py b/src/whop_sdk/types/card_brands.py
new file mode 100644
index 00000000..f500a411
--- /dev/null
+++ b/src/whop_sdk/types/card_brands.py
@@ -0,0 +1,24 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing_extensions import Literal, TypeAlias
+
+__all__ = ["CardBrands"]
+
+CardBrands: TypeAlias = Literal[
+ "mastercard",
+ "visa",
+ "amex",
+ "discover",
+ "unionpay",
+ "jcb",
+ "diners",
+ "link",
+ "troy",
+ "visadankort",
+ "visabancontact",
+ "china_union_pay",
+ "rupay",
+ "jcbrupay",
+ "elo",
+ "unknown",
+]
diff --git a/src/whop_sdk/types/checkout_configuration_create_params.py b/src/whop_sdk/types/checkout_configuration_create_params.py
index 4d40fff3..aa88f58f 100644
--- a/src/whop_sdk/types/checkout_configuration_create_params.py
+++ b/src/whop_sdk/types/checkout_configuration_create_params.py
@@ -9,9 +9,12 @@
from .shared.tax_type import TaxType
from .shared.plan_type import PlanType
from .shared.visibility import Visibility
+from .shared.business_types import BusinessTypes
+from .shared.industry_types import IndustryTypes
from .shared.release_method import ReleaseMethod
+from .shared.global_affiliate_status import GlobalAffiliateStatus
-__all__ = ["CheckoutConfigurationCreateParams", "Plan", "PlanCustomField", "PlanImage"]
+__all__ = ["CheckoutConfigurationCreateParams", "Plan", "PlanCustomField", "PlanImage", "PlanProduct"]
class CheckoutConfigurationCreateParams(TypedDict, total=False):
@@ -67,6 +70,59 @@ class PlanImage(TypedDict, total=False):
"""
+class PlanProduct(TypedDict, total=False):
+ external_identifier: Required[str]
+ """A unique ID used to find or create a product.
+
+ When provided during creation, we will look for an existing product with this
+ external identifier — if found, it will be updated; otherwise, a new product
+ will be created.
+ """
+
+ title: Required[str]
+ """The title of the product."""
+
+ business_type: Optional[BusinessTypes]
+ """The different business types a company can be."""
+
+ collect_shipping_address: Optional[bool]
+ """Whether or not to collect shipping information at checkout from the customer."""
+
+ custom_statement_descriptor: Optional[str]
+ """The custom statement descriptor for the product i.e.
+
+ WHOP\\**SPORTS, must be between 5 and 22 characters, contain at least one letter,
+ and not contain any of the following characters: <, >, \\,, ', "
+ """
+
+ description: Optional[str]
+ """A written description of the product."""
+
+ global_affiliate_percentage: Optional[float]
+ """The percentage of the revenue that goes to the global affiliate program."""
+
+ global_affiliate_status: Optional[GlobalAffiliateStatus]
+ """The different statuses of the global affiliate program for an access pass."""
+
+ headline: Optional[str]
+ """The headline of the product."""
+
+ industry_type: Optional[IndustryTypes]
+ """The different industry types a company can be in."""
+
+ product_tax_code_id: Optional[str]
+ """The ID of the product tax code to apply to this product."""
+
+ redirect_purchase_url: Optional[str]
+ """The URL to redirect the customer to after a purchase."""
+
+ route: Optional[str]
+ """The route of the product."""
+
+ visibility: Optional[Visibility]
+ """Visibility of a resource"""
+
+
class Plan(TypedDict, total=False):
company_id: Required[str]
"""The company the plan should be created for."""
@@ -110,8 +166,15 @@ class Plan(TypedDict, total=False):
plan_type: Optional[PlanType]
"""The type of plan that can be attached to an access pass"""
+ product: Optional[PlanProduct]
+ """Pass this object to create a new product for this plan.
+
+ We will use the product external identifier to find or create an existing
+ product.
+ """
+
product_id: Optional[str]
- """The product the plan is related to."""
+ """The product the plan is related to. Either this or product is required."""
release_method: Optional[ReleaseMethod]
"""The methods of how a plan can be released."""
diff --git a/src/whop_sdk/types/checkout_configuration_list_response.py b/src/whop_sdk/types/checkout_configuration_list_response.py
index 07c1f4b2..76980021 100644
--- a/src/whop_sdk/types/checkout_configuration_list_response.py
+++ b/src/whop_sdk/types/checkout_configuration_list_response.py
@@ -60,7 +60,10 @@ class CheckoutConfigurationListResponse(BaseModel):
"""The plan to use for the checkout configuration"""
purchase_url: str
- """The URL to redirect the user to after the checkout configuration is created"""
+ """A URL you can send to customers to complete a checkout.
+
+ It looks like `/checkout/plan_xxxx?session={id}`
+ """
redirect_url: Optional[str] = None
"""The URL to redirect the user to after the checkout configuration is created"""
diff --git a/src/whop_sdk/types/course.py b/src/whop_sdk/types/course.py
new file mode 100644
index 00000000..f39808de
--- /dev/null
+++ b/src/whop_sdk/types/course.py
@@ -0,0 +1,109 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import List, Optional
+from datetime import datetime
+
+from .._models import BaseModel
+from .languages import Languages
+from .lesson_types import LessonTypes
+
+__all__ = ["Course", "Chapter", "ChapterLesson", "Thumbnail"]
+
+
+class ChapterLesson(BaseModel):
+ id: str
+ """The ID of the lesson"""
+
+ lesson_type: LessonTypes
+ """The type of the lesson (text, video, pdf, multi, quiz, knowledge_check)"""
+
+ order: int
+ """The order of the lesson within its chapter"""
+
+ title: str
+ """The title of the lesson"""
+
+
+class Chapter(BaseModel):
+ id: str
+ """The ID of the chapter. Looks like chap_XXX"""
+
+ lessons: List[ChapterLesson]
+ """The lessons in this chapter"""
+
+ order: int
+ """The order of the chapter within its course"""
+
+ title: str
+ """The title of the chapter"""
+
+
+class Thumbnail(BaseModel):
+ id: str
+ """The ID of the attachment"""
+
+ content_type: Optional[str] = None
+ """The attachment's content type (e.g., image/jpg, video/mp4)"""
+
+ filename: Optional[str] = None
+ """The name of the file"""
+
+ optimized_url: Optional[str] = None
+ """This is the URL you use to render optimized attachments on the client.
+
+ This should be used for apps.
+ """
+
+ source_url: Optional[str] = None
+ """The original URL of the attachment, such as a direct link to S3.
+
+ This should never be displayed on the client and always passed to an Imgproxy
+ transformer.
+ """
+
+
+class Course(BaseModel):
+ id: str
+ """The ID of the course. Looks like cors_XXX"""
+
+ certificate_after_completion_enabled: Optional[bool] = None
+ """
+ Whether the course will award its students a PDF certificate after completing
+ all lessons
+ """
+
+ chapters: List[Chapter]
+ """The chapters in this course"""
+
+ created_at: datetime
+ """The timestamp of when the course was created"""
+
+ description: Optional[str] = None
+ """A short description of the course"""
+
+ language: Languages
+ """
+ The language spoken in the video content of the course, used to generate closed
+ captions in the right language
+ """
+
+ require_completing_lessons_in_order: bool
+ """
+ Whether the course requires students to complete the previous lesson before
+ moving on to the next one
+ """
+
+ tagline: Optional[str] = None
+ """A short tagline for the course.
+
+ It is displayed under the course title in the UI
+ """
+
+ thumbnail: Optional[Thumbnail] = None
+ """The thumbnail for the course"""
+
+ title: Optional[str] = None
+ """The title of the course"""
+
+ updated_at: datetime
+ """The timestamp of when the course was last updated"""
diff --git a/src/whop_sdk/types/course_chapter.py b/src/whop_sdk/types/course_chapter.py
new file mode 100644
index 00000000..48a6de54
--- /dev/null
+++ b/src/whop_sdk/types/course_chapter.py
@@ -0,0 +1,32 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import List
+
+from .._models import BaseModel
+
+__all__ = ["CourseChapter", "Lesson"]
+
+
+class Lesson(BaseModel):
+ id: str
+ """The ID of the lesson"""
+
+ order: int
+ """The order of the lesson within its chapter"""
+
+ title: str
+ """The title of the lesson"""
+
+
+class CourseChapter(BaseModel):
+ id: str
+ """The ID of the chapter. Looks like chap_XXX"""
+
+ lessons: List[Lesson]
+ """The lessons in this chapter"""
+
+ order: int
+ """The order of the chapter within its course"""
+
+ title: str
+ """The title of the chapter"""
diff --git a/src/whop_sdk/types/course_chapter_create_params.py b/src/whop_sdk/types/course_chapter_create_params.py
new file mode 100644
index 00000000..1d4d7dfb
--- /dev/null
+++ b/src/whop_sdk/types/course_chapter_create_params.py
@@ -0,0 +1,16 @@
+# 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
+
+__all__ = ["CourseChapterCreateParams"]
+
+
+class CourseChapterCreateParams(TypedDict, total=False):
+ course_id: Required[str]
+ """The ID of the course to create the chapter in"""
+
+ title: Optional[str]
+ """The title of the chapter"""
diff --git a/src/whop_sdk/types/course_chapter_delete_response.py b/src/whop_sdk/types/course_chapter_delete_response.py
new file mode 100644
index 00000000..3c6ef823
--- /dev/null
+++ b/src/whop_sdk/types/course_chapter_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__ = ["CourseChapterDeleteResponse"]
+
+CourseChapterDeleteResponse: TypeAlias = bool
diff --git a/src/whop_sdk/types/course_chapter_list_params.py b/src/whop_sdk/types/course_chapter_list_params.py
new file mode 100644
index 00000000..211423f3
--- /dev/null
+++ b/src/whop_sdk/types/course_chapter_list_params.py
@@ -0,0 +1,25 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Optional
+from typing_extensions import Required, TypedDict
+
+__all__ = ["CourseChapterListParams"]
+
+
+class CourseChapterListParams(TypedDict, total=False):
+ course_id: Required[str]
+ """The ID of the course"""
+
+ after: Optional[str]
+ """Returns the elements in the list that come after the specified cursor."""
+
+ before: Optional[str]
+ """Returns the elements in the list that come before the specified cursor."""
+
+ first: Optional[int]
+ """Returns the first _n_ elements from the list."""
+
+ last: Optional[int]
+ """Returns the last _n_ elements from the list."""
diff --git a/src/whop_sdk/types/course_chapter_list_response.py b/src/whop_sdk/types/course_chapter_list_response.py
new file mode 100644
index 00000000..e6bb4610
--- /dev/null
+++ b/src/whop_sdk/types/course_chapter_list_response.py
@@ -0,0 +1,16 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from .._models import BaseModel
+
+__all__ = ["CourseChapterListResponse"]
+
+
+class CourseChapterListResponse(BaseModel):
+ id: str
+ """The ID of the chapter. Looks like chap_XXX"""
+
+ order: int
+ """The order of the chapter within its course"""
+
+ title: str
+ """The title of the chapter"""
diff --git a/src/whop_sdk/types/course_chapter_update_params.py b/src/whop_sdk/types/course_chapter_update_params.py
new file mode 100644
index 00000000..706b8f4f
--- /dev/null
+++ b/src/whop_sdk/types/course_chapter_update_params.py
@@ -0,0 +1,12 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Required, TypedDict
+
+__all__ = ["CourseChapterUpdateParams"]
+
+
+class CourseChapterUpdateParams(TypedDict, total=False):
+ title: Required[str]
+ """The title of the chapter"""
diff --git a/src/whop_sdk/types/course_create_params.py b/src/whop_sdk/types/course_create_params.py
new file mode 100644
index 00000000..dc86f657
--- /dev/null
+++ b/src/whop_sdk/types/course_create_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 Optional
+from typing_extensions import Required, TypedDict
+
+__all__ = ["CourseCreateParams", "Thumbnail"]
+
+
+class CourseCreateParams(TypedDict, total=False):
+ experience_id: Required[str]
+ """The ID of the experience to create the course in"""
+
+ title: Required[str]
+ """The title of the course"""
+
+ cover_image: Optional[str]
+ """The cover image URL of the course"""
+
+ tagline: Optional[str]
+ """The tagline of the course"""
+
+ thumbnail: Optional[Thumbnail]
+ """The thumbnail for the course in png, jpeg, or gif format"""
+
+
+class Thumbnail(TypedDict, total=False):
+ id: Optional[str]
+ """The ID of an existing attachment object.
+
+ Use this when updating a resource and keeping a subset of the attachments. Don't
+ use this unless you know what you're doing.
+ """
+
+ direct_upload_id: Optional[str]
+ """This ID should be used the first time you upload an attachment.
+
+ It is the ID of the direct upload that was created when uploading the file to S3
+ via the mediaDirectUpload mutation.
+ """
diff --git a/src/whop_sdk/types/course_delete_response.py b/src/whop_sdk/types/course_delete_response.py
new file mode 100644
index 00000000..72c3dfaf
--- /dev/null
+++ b/src/whop_sdk/types/course_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__ = ["CourseDeleteResponse"]
+
+CourseDeleteResponse: TypeAlias = bool
diff --git a/src/whop_sdk/types/course_lesson_create_params.py b/src/whop_sdk/types/course_lesson_create_params.py
new file mode 100644
index 00000000..29f6fdd2
--- /dev/null
+++ b/src/whop_sdk/types/course_lesson_create_params.py
@@ -0,0 +1,27 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Optional
+from typing_extensions import Required, TypedDict
+
+from .lesson_types import LessonTypes
+
+__all__ = ["CourseLessonCreateParams"]
+
+
+class CourseLessonCreateParams(TypedDict, total=False):
+ chapter_id: Required[str]
+ """The ID of the chapter to create the lesson in"""
+
+ lesson_type: Required[LessonTypes]
+ """The type of the lesson"""
+
+ content: Optional[str]
+ """The content of the lesson"""
+
+ days_from_course_start_until_unlock: Optional[int]
+ """Days from course start until unlock"""
+
+ title: Optional[str]
+ """The title of the lesson"""
diff --git a/src/whop_sdk/types/course_lesson_delete_response.py b/src/whop_sdk/types/course_lesson_delete_response.py
new file mode 100644
index 00000000..8686f01f
--- /dev/null
+++ b/src/whop_sdk/types/course_lesson_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__ = ["CourseLessonDeleteResponse"]
+
+CourseLessonDeleteResponse: TypeAlias = bool
diff --git a/src/whop_sdk/types/course_lesson_list_params.py b/src/whop_sdk/types/course_lesson_list_params.py
new file mode 100644
index 00000000..5f59c6e5
--- /dev/null
+++ b/src/whop_sdk/types/course_lesson_list_params.py
@@ -0,0 +1,28 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Optional
+from typing_extensions import TypedDict
+
+__all__ = ["CourseLessonListParams"]
+
+
+class CourseLessonListParams(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."""
+
+ chapter_id: Optional[str]
+ """The ID of the chapter (returns lessons only for this chapter)"""
+
+ course_id: Optional[str]
+ """The ID of the course (returns all lessons across all chapters)"""
+
+ first: Optional[int]
+ """Returns the first _n_ elements from the list."""
+
+ last: Optional[int]
+ """Returns the last _n_ elements from the list."""
diff --git a/src/whop_sdk/types/course_lesson_list_response.py b/src/whop_sdk/types/course_lesson_list_response.py
new file mode 100644
index 00000000..e7d65b22
--- /dev/null
+++ b/src/whop_sdk/types/course_lesson_list_response.py
@@ -0,0 +1,35 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Optional
+
+from .._models import BaseModel
+from .lesson_types import LessonTypes
+from .lesson_visibilities import LessonVisibilities
+
+__all__ = ["CourseLessonListResponse"]
+
+
+class CourseLessonListResponse(BaseModel):
+ id: str
+ """The ID of the lesson"""
+
+ content: Optional[str] = None
+ """The content of the lesson"""
+
+ days_from_course_start_until_unlock: Optional[int] = None
+ """Number of days from course start until the lesson is unlocked"""
+
+ lesson_type: LessonTypes
+ """The type of the lesson (text, video, pdf, multi, quiz, knowledge_check)"""
+
+ order: int
+ """The order of the lesson within its chapter"""
+
+ title: str
+ """The title of the lesson"""
+
+ visibility: LessonVisibilities
+ """The visibility of the lesson.
+
+ Determines how / whether this lesson is visible to users.
+ """
diff --git a/src/whop_sdk/types/course_lesson_update_params.py b/src/whop_sdk/types/course_lesson_update_params.py
new file mode 100644
index 00000000..aca167c0
--- /dev/null
+++ b/src/whop_sdk/types/course_lesson_update_params.py
@@ -0,0 +1,144 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Iterable, Optional
+from typing_extensions import Required, TypedDict
+
+from .lesson_types import LessonTypes
+from .lesson_visibilities import LessonVisibilities
+from .assessment_question_types import AssessmentQuestionTypes
+
+__all__ = [
+ "CourseLessonUpdateParams",
+ "AssessmentQuestion",
+ "AssessmentQuestionImage",
+ "AssessmentQuestionOption",
+ "Attachment",
+ "MainPdf",
+]
+
+
+class CourseLessonUpdateParams(TypedDict, total=False):
+ assessment_questions: Optional[Iterable[AssessmentQuestion]]
+ """Assessment questions for quiz/knowledge check lessons.
+
+ Replaces all existing questions.
+ """
+
+ attachments: Optional[Iterable[Attachment]]
+ """General attachments for the lesson (PDFs, files, etc).
+
+ Replaces all existing attachments.
+ """
+
+ content: Optional[str]
+ """The content of the lesson"""
+
+ days_from_course_start_until_unlock: Optional[int]
+ """Days from course start until unlock"""
+
+ lesson_type: Optional[LessonTypes]
+ """The available types for a lesson"""
+
+ main_pdf: Optional[MainPdf]
+ """The main PDF file for this lesson"""
+
+ mux_asset_id: Optional[str]
+ """The ID of the Mux asset to attach to this lesson for video lessons"""
+
+ title: Optional[str]
+ """The title of the lesson"""
+
+ visibility: Optional[LessonVisibilities]
+ """The available visibilities for a lesson.
+
+ Determines how / whether a lesson is visible to users.
+ """
+
+
+class AssessmentQuestionImage(TypedDict, total=False):
+ id: Optional[str]
+ """The ID of an existing attachment object.
+
+ Use this when updating a resource and keeping a subset of the attachments. Don't
+ use this unless you know what you're doing.
+ """
+
+ direct_upload_id: Optional[str]
+ """This ID should be used the first time you upload an attachment.
+
+ It is the ID of the direct upload that was created when uploading the file to S3
+ via the mediaDirectUpload mutation.
+ """
+
+
+class AssessmentQuestionOption(TypedDict, total=False):
+ is_correct: Required[bool]
+ """Whether this option is a correct answer"""
+
+ option_text: Required[str]
+ """The text of the answer option"""
+
+ id: Optional[str]
+ """The ID of an existing option.
+
+ If provided, the option will be updated. If not provided, a new option will be
+ created.
+ """
+
+
+class AssessmentQuestion(TypedDict, total=False):
+ correct_answer: Required[str]
+ """The correct answer for the question. Used for short answer questions"""
+
+ question_text: Required[str]
+ """The text of the question"""
+
+ question_type: Required[AssessmentQuestionTypes]
+ """The type of the question"""
+
+ id: Optional[str]
+ """The ID of an existing question.
+
+ If provided, the question will be updated. If not provided, a new question will
+ be created.
+ """
+
+ image: Optional[AssessmentQuestionImage]
+ """Optional image attachment for the question"""
+
+ options: Optional[Iterable[AssessmentQuestionOption]]
+ """The answer options for multiple choice/select questions"""
+
+
+class Attachment(TypedDict, total=False):
+ id: Optional[str]
+ """The ID of an existing attachment object.
+
+ Use this when updating a resource and keeping a subset of the attachments. Don't
+ use this unless you know what you're doing.
+ """
+
+ direct_upload_id: Optional[str]
+ """This ID should be used the first time you upload an attachment.
+
+ It is the ID of the direct upload that was created when uploading the file to S3
+ via the mediaDirectUpload mutation.
+ """
+
+
+class MainPdf(TypedDict, total=False):
+ id: Optional[str]
+ """The ID of an existing attachment object.
+
+ Use this when updating a resource and keeping a subset of the attachments. Don't
+ use this unless you know what you're doing.
+ """
+
+ direct_upload_id: Optional[str]
+ """This ID should be used the first time you upload an attachment.
+
+ It is the ID of the direct upload that was created when uploading the file to S3
+ via the mediaDirectUpload mutation.
+ """
diff --git a/src/whop_sdk/types/course_list_params.py b/src/whop_sdk/types/course_list_params.py
new file mode 100644
index 00000000..56734c39
--- /dev/null
+++ b/src/whop_sdk/types/course_list_params.py
@@ -0,0 +1,28 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Optional
+from typing_extensions import TypedDict
+
+__all__ = ["CourseListParams"]
+
+
+class CourseListParams(TypedDict, total=False):
+ after: Optional[str]
+ """Returns the elements in the list that come after the specified cursor."""
+
+ before: Optional[str]
+ """Returns the elements in the list that come before the specified cursor."""
+
+ company_id: Optional[str]
+ """The ID of the company"""
+
+ experience_id: Optional[str]
+ """The ID of the experience"""
+
+ first: Optional[int]
+ """Returns the first _n_ elements from the list."""
+
+ last: Optional[int]
+ """Returns the last _n_ elements from the list."""
diff --git a/src/whop_sdk/types/course_list_response.py b/src/whop_sdk/types/course_list_response.py
new file mode 100644
index 00000000..ea312354
--- /dev/null
+++ b/src/whop_sdk/types/course_list_response.py
@@ -0,0 +1,77 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Optional
+from datetime import datetime
+
+from .._models import BaseModel
+from .languages import Languages
+
+__all__ = ["CourseListResponse", "Thumbnail"]
+
+
+class Thumbnail(BaseModel):
+ id: str
+ """The ID of the attachment"""
+
+ content_type: Optional[str] = None
+ """The attachment's content type (e.g., image/jpg, video/mp4)"""
+
+ filename: Optional[str] = None
+ """The name of the file"""
+
+ optimized_url: Optional[str] = None
+ """This is the URL you use to render optimized attachments on the client.
+
+ This should be used for apps.
+ """
+
+ source_url: Optional[str] = None
+ """The original URL of the attachment, such as a direct link to S3.
+
+ This should never be displayed on the client and always passed to an Imgproxy
+ transformer.
+ """
+
+
+class CourseListResponse(BaseModel):
+ id: str
+ """The ID of the course. Looks like cors_XXX"""
+
+ certificate_after_completion_enabled: Optional[bool] = None
+ """
+ Whether the course will award its students a PDF certificate after completing
+ all lessons
+ """
+
+ created_at: datetime
+ """The timestamp of when the course was created"""
+
+ description: Optional[str] = None
+ """A short description of the course"""
+
+ language: Languages
+ """
+ The language spoken in the video content of the course, used to generate closed
+ captions in the right language
+ """
+
+ require_completing_lessons_in_order: bool
+ """
+ Whether the course requires students to complete the previous lesson before
+ moving on to the next one
+ """
+
+ tagline: Optional[str] = None
+ """A short tagline for the course.
+
+ It is displayed under the course title in the UI
+ """
+
+ thumbnail: Optional[Thumbnail] = None
+ """The thumbnail for the course"""
+
+ title: Optional[str] = None
+ """The title of the course"""
+
+ updated_at: datetime
+ """The timestamp of when the course was last updated"""
diff --git a/src/whop_sdk/types/course_update_params.py b/src/whop_sdk/types/course_update_params.py
new file mode 100644
index 00000000..35353cc3
--- /dev/null
+++ b/src/whop_sdk/types/course_update_params.py
@@ -0,0 +1,89 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Iterable, Optional
+from typing_extensions import Required, TypedDict
+
+from .languages import Languages
+
+__all__ = ["CourseUpdateParams", "Chapter", "ChapterLesson", "Thumbnail"]
+
+
+class CourseUpdateParams(TypedDict, total=False):
+ certificate_after_completion_enabled: Optional[bool]
+ """
+ Whether the course will award its students a PDF certificate after completing
+ all lessons
+ """
+
+ chapters: Optional[Iterable[Chapter]]
+ """The chapters and lessons to update"""
+
+ cover_image: Optional[str]
+ """The cover image URL of the course"""
+
+ description: Optional[str]
+ """A short description of the course"""
+
+ language: Optional[Languages]
+ """The available languages for a course"""
+
+ require_completing_lessons_in_order: Optional[bool]
+ """
+ Whether the course requires students to complete the previous lesson before
+ moving on to the next one
+ """
+
+ tagline: Optional[str]
+ """A short tagline for the course"""
+
+ thumbnail: Optional[Thumbnail]
+ """The thumbnail for the course in png, jpeg, or gif format"""
+
+ title: Optional[str]
+ """The title of the course"""
+
+
+class ChapterLesson(TypedDict, total=False):
+ id: Required[str]
+ """The ID of the lesson to update"""
+
+ chapter_id: Required[str]
+ """The ID of the chapter this lesson belongs to (for moving between chapters)"""
+
+ order: Required[int]
+ """The order of the lesson within its chapter"""
+
+ title: Required[str]
+ """The title of the lesson"""
+
+
+class Chapter(TypedDict, total=False):
+ id: Required[str]
+ """The ID of the chapter to update"""
+
+ order: Required[int]
+ """The order of the chapter within its course"""
+
+ title: Required[str]
+ """The title of the chapter"""
+
+ lessons: Optional[Iterable[ChapterLesson]]
+ """The lessons to update within this chapter"""
+
+
+class Thumbnail(TypedDict, total=False):
+ id: Optional[str]
+ """The ID of an existing attachment object.
+
+ Use this when updating a resource and keeping a subset of the attachments. Don't
+ use this unless you know what you're doing.
+ """
+
+ direct_upload_id: Optional[str]
+ """This ID should be used the first time you upload an attachment.
+
+ It is the ID of the direct upload that was created when uploading the file to S3
+ via the mediaDirectUpload mutation.
+ """
diff --git a/src/whop_sdk/types/experience_duplicate_params.py b/src/whop_sdk/types/experience_duplicate_params.py
new file mode 100644
index 00000000..e1dbf224
--- /dev/null
+++ b/src/whop_sdk/types/experience_duplicate_params.py
@@ -0,0 +1,13 @@
+# 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__ = ["ExperienceDuplicateParams"]
+
+
+class ExperienceDuplicateParams(TypedDict, total=False):
+ name: Optional[str]
+ """The name of the new experience"""
diff --git a/src/whop_sdk/types/forum_post_list_response.py b/src/whop_sdk/types/forum_post_list_response.py
index 4ccfb1d2..7749d8dc 100644
--- a/src/whop_sdk/types/forum_post_list_response.py
+++ b/src/whop_sdk/types/forum_post_list_response.py
@@ -1,6 +1,7 @@
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
from typing import Optional
+from datetime import datetime
from .._models import BaseModel
@@ -28,6 +29,9 @@ class ForumPostListResponse(BaseModel):
content: Optional[str] = None
"""The content of the forum post in Markdown format"""
+ created_at: datetime
+ """The timestamp when the post was created"""
+
is_edited: bool
"""Whether the forum post has been edited"""
@@ -46,6 +50,9 @@ class ForumPostListResponse(BaseModel):
title: Optional[str] = None
"""The title of the forum post"""
+ updated_at: datetime
+ """The timestamp when the post was last updated"""
+
user: User
"""The user who created this forum post"""
diff --git a/src/whop_sdk/types/forum_post_update_params.py b/src/whop_sdk/types/forum_post_update_params.py
new file mode 100644
index 00000000..dfa2435b
--- /dev/null
+++ b/src/whop_sdk/types/forum_post_update_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 Iterable, Optional
+from typing_extensions import TypedDict
+
+__all__ = ["ForumPostUpdateParams", "Attachment"]
+
+
+class ForumPostUpdateParams(TypedDict, total=False):
+ attachments: Optional[Iterable[Attachment]]
+ """The attachments for this post"""
+
+ content: Optional[str]
+ """This is the main body of the post in Markdown format.
+
+ Hidden if paywalled and user hasn't purchased access to it.
+ """
+
+ is_pinned: Optional[bool]
+ """Whether the post is pinned. You can only pin a top level posts (not comments)."""
+
+ title: Optional[str]
+ """The title of the post. Only visible if paywalled."""
+
+
+class Attachment(TypedDict, total=False):
+ id: Optional[str]
+ """The ID of an existing attachment object.
+
+ Use this when updating a resource and keeping a subset of the attachments. Don't
+ use this unless you know what you're doing.
+ """
+
+ direct_upload_id: Optional[str]
+ """This ID should be used the first time you upload an attachment.
+
+ It is the ID of the direct upload that was created when uploading the file to S3
+ via the mediaDirectUpload mutation.
+ """
diff --git a/src/whop_sdk/types/languages.py b/src/whop_sdk/types/languages.py
new file mode 100644
index 00000000..d72d461e
--- /dev/null
+++ b/src/whop_sdk/types/languages.py
@@ -0,0 +1,30 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing_extensions import Literal, TypeAlias
+
+__all__ = ["Languages"]
+
+Languages: TypeAlias = Literal[
+ "en",
+ "es",
+ "it",
+ "pt",
+ "de",
+ "fr",
+ "pl",
+ "ru",
+ "nl",
+ "ca",
+ "tr",
+ "sv",
+ "uk",
+ "no",
+ "fi",
+ "sk",
+ "el",
+ "cs",
+ "hr",
+ "da",
+ "ro",
+ "bg",
+]
diff --git a/src/whop_sdk/types/ledger_account_retrieve_response.py b/src/whop_sdk/types/ledger_account_retrieve_response.py
index faa910e2..042d20f3 100644
--- a/src/whop_sdk/types/ledger_account_retrieve_response.py
+++ b/src/whop_sdk/types/ledger_account_retrieve_response.py
@@ -62,7 +62,21 @@ class LedgerAccountRetrieveResponse(BaseModel):
balances: List[Balance]
"""The balances associated with the account."""
- ledger_account_audit_status: Optional[Literal["reserves_imposed", "requested_more_information"]] = None
+ ledger_account_audit_status: Optional[
+ Literal[
+ "pending",
+ "approved",
+ "reserves_imposed",
+ "suspended",
+ "ignored",
+ "rejected",
+ "requested_more_information",
+ "information_submitted",
+ "requested_tos_violation_correction",
+ "clawback_attempted",
+ "awaiting_sales_review",
+ ]
+ ] = None
"""The different statuses a LedgerAccountAudit can be"""
ledger_type: Literal["primary", "pool"]
diff --git a/src/whop_sdk/types/lesson.py b/src/whop_sdk/types/lesson.py
new file mode 100644
index 00000000..77aec38e
--- /dev/null
+++ b/src/whop_sdk/types/lesson.py
@@ -0,0 +1,159 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import List, Optional
+from datetime import datetime
+
+from .._models import BaseModel
+from .lesson_types import LessonTypes
+from .lesson_visibilities import LessonVisibilities
+from .assessment_question_types import AssessmentQuestionTypes
+
+__all__ = [
+ "Lesson",
+ "AssessmentQuestion",
+ "AssessmentQuestionImage",
+ "AssessmentQuestionOption",
+ "Attachment",
+ "MainPdf",
+ "VideoAsset",
+]
+
+
+class AssessmentQuestionImage(BaseModel):
+ id: str
+ """The ID of the attachment"""
+
+ content_type: Optional[str] = None
+ """The attachment's content type (e.g., image/jpg, video/mp4)"""
+
+ filename: Optional[str] = None
+ """The name of the file"""
+
+ url: Optional[str] = None
+ """This is the URL you use to render optimized attachments on the client.
+
+ This should be used for apps.
+ """
+
+
+class AssessmentQuestionOption(BaseModel):
+ id: str
+ """The ID of the assessment question option"""
+
+ is_correct: bool
+ """Whether this option is a correct answer"""
+
+ option_text: str
+ """The text of the answer option"""
+
+ order: int
+ """The order of this option within the question"""
+
+
+class AssessmentQuestion(BaseModel):
+ id: str
+ """The ID of the assessment question"""
+
+ correct_answer: str
+ """The correct answer for the question. Used for short answer questions"""
+
+ created_at: datetime
+ """When the question was created"""
+
+ image: Optional[AssessmentQuestionImage] = None
+ """Optional image attachment for the question"""
+
+ options: List[AssessmentQuestionOption]
+ """The answer options for multiple choice/select questions"""
+
+ order: int
+ """The order of the question within its lesson"""
+
+ question_text: str
+ """The text of the question"""
+
+ question_type: AssessmentQuestionTypes
+ """The type of the question"""
+
+
+class Attachment(BaseModel):
+ id: str
+ """The ID of the attachment"""
+
+ content_type: Optional[str] = None
+ """The attachment's content type (e.g., image/jpg, video/mp4)"""
+
+ filename: Optional[str] = None
+ """The name of the file"""
+
+ url: Optional[str] = None
+ """This is the URL you use to render optimized attachments on the client.
+
+ This should be used for apps.
+ """
+
+
+class MainPdf(BaseModel):
+ id: str
+ """The ID of the attachment"""
+
+ content_type: Optional[str] = None
+ """The attachment's content type (e.g., image/jpg, video/mp4)"""
+
+ filename: Optional[str] = None
+ """The name of the file"""
+
+ url: Optional[str] = None
+ """This is the URL you use to render optimized attachments on the client.
+
+ This should be used for apps.
+ """
+
+
+class VideoAsset(BaseModel):
+ id: str
+ """The ID of the Mux asset"""
+
+ asset_id: Optional[str] = None
+ """The Mux-provided ID of the asset"""
+
+ playback_id: Optional[str] = None
+ """The public playback ID of the Mux asset"""
+
+
+class Lesson(BaseModel):
+ id: str
+ """The ID of the lesson"""
+
+ assessment_questions: List[AssessmentQuestion]
+ """Assessment questions for quiz/knowledge check lessons"""
+
+ attachments: List[Attachment]
+ """The attached files in this lesson as a flat array"""
+
+ content: Optional[str] = None
+ """The content of the lesson"""
+
+ days_from_course_start_until_unlock: Optional[int] = None
+ """Number of days from course start until the lesson is unlocked"""
+
+ lesson_type: LessonTypes
+ """The type of the lesson (text, video, pdf, multi, quiz, knowledge_check)"""
+
+ main_pdf: Optional[MainPdf] = None
+ """The main PDF file for this lesson"""
+
+ order: int
+ """The order of the lesson within its chapter"""
+
+ title: str
+ """The title of the lesson"""
+
+ video_asset: Optional[VideoAsset] = None
+ """The associated Mux asset for video lessons"""
+
+ visibility: LessonVisibilities
+ """The visibility of the lesson.
+
+ Determines how / whether this lesson is visible to users.
+ """
diff --git a/src/whop_sdk/types/lesson_types.py b/src/whop_sdk/types/lesson_types.py
new file mode 100644
index 00000000..c0c4503e
--- /dev/null
+++ b/src/whop_sdk/types/lesson_types.py
@@ -0,0 +1,7 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing_extensions import Literal, TypeAlias
+
+__all__ = ["LessonTypes"]
+
+LessonTypes: TypeAlias = Literal["text", "video", "pdf", "multi", "quiz", "knowledge_check"]
diff --git a/src/whop_sdk/types/lesson_visibilities.py b/src/whop_sdk/types/lesson_visibilities.py
new file mode 100644
index 00000000..8256e220
--- /dev/null
+++ b/src/whop_sdk/types/lesson_visibilities.py
@@ -0,0 +1,7 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing_extensions import Literal, TypeAlias
+
+__all__ = ["LessonVisibilities"]
+
+LessonVisibilities: TypeAlias = Literal["visible", "hidden"]
diff --git a/src/whop_sdk/types/member_list_params.py b/src/whop_sdk/types/member_list_params.py
index f139719e..157e6a21 100644
--- a/src/whop_sdk/types/member_list_params.py
+++ b/src/whop_sdk/types/member_list_params.py
@@ -68,3 +68,6 @@ class MemberListParams(TypedDict, total=False):
statuses: Optional[List[MemberStatuses]]
"""The statuses to filter the members by"""
+
+ user_ids: Optional[SequenceNotStr[str]]
+ """The user IDs to filter the members by"""
diff --git a/src/whop_sdk/types/membership_list_params.py b/src/whop_sdk/types/membership_list_params.py
index e1547809..f57898e1 100644
--- a/src/whop_sdk/types/membership_list_params.py
+++ b/src/whop_sdk/types/membership_list_params.py
@@ -4,7 +4,7 @@
from typing import List, Union, Optional
from datetime import datetime
-from typing_extensions import Literal, Required, Annotated, TypedDict
+from typing_extensions import Literal, Annotated, TypedDict
from .._types import SequenceNotStr
from .._utils import PropertyInfo
@@ -15,9 +15,6 @@
class MembershipListParams(TypedDict, total=False):
- company_id: Required[str]
- """The ID of the company to list memberships for"""
-
access_pass_ids: Optional[SequenceNotStr[str]]
"""The access pass IDs to filter the memberships by"""
@@ -42,6 +39,9 @@ class MembershipListParams(TypedDict, total=False):
]
"""The cancel options to filter the memberships by"""
+ company_id: Optional[str]
+ """The ID of the company to list memberships for"""
+
created_after: Annotated[Union[str, datetime, None], PropertyInfo(format="iso8601")]
"""The minimum creation date to filter by"""
@@ -68,3 +68,6 @@ class MembershipListParams(TypedDict, total=False):
statuses: Optional[List[MembershipStatus]]
"""The membership status to filter the memberships by"""
+
+ user_ids: Optional[SequenceNotStr[str]]
+ """Only return memberships from these whop user ids"""
diff --git a/src/whop_sdk/types/membership_list_response.py b/src/whop_sdk/types/membership_list_response.py
index 178a081f..ce811ae0 100644
--- a/src/whop_sdk/types/membership_list_response.py
+++ b/src/whop_sdk/types/membership_list_response.py
@@ -7,7 +7,7 @@
from .shared.currency import Currency
from .shared.membership_status import MembershipStatus
-__all__ = ["MembershipListResponse", "Company", "Member", "Plan", "PromoCode", "User"]
+__all__ = ["MembershipListResponse", "Company", "Member", "Plan", "Product", "PromoCode", "User"]
class Company(BaseModel):
@@ -28,6 +28,14 @@ class Plan(BaseModel):
"""The internal ID of the plan."""
+class Product(BaseModel):
+ id: str
+ """The internal ID of the public product."""
+
+ title: str
+ """The title of the product. Use for Whop 4.0."""
+
+
class PromoCode(BaseModel):
id: str
"""The ID of the promo."""
@@ -92,6 +100,9 @@ class MembershipListResponse(BaseModel):
plan: Plan
"""The Plan this Membership is for."""
+ product: Product
+ """The Product this Membership grants access to."""
+
promo_code: Optional[PromoCode] = None
"""The Promo Code that is currently applied to this Membership."""
diff --git a/src/whop_sdk/types/message_update_params.py b/src/whop_sdk/types/message_update_params.py
new file mode 100644
index 00000000..703a22eb
--- /dev/null
+++ b/src/whop_sdk/types/message_update_params.py
@@ -0,0 +1,35 @@
+# 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 TypedDict
+
+__all__ = ["MessageUpdateParams", "Attachment"]
+
+
+class MessageUpdateParams(TypedDict, total=False):
+ attachments: Optional[Iterable[Attachment]]
+ """The attachments for this message"""
+
+ content: Optional[str]
+ """The content of the message in Markdown format"""
+
+ is_pinned: Optional[bool]
+ """Whether this message is pinned"""
+
+
+class Attachment(TypedDict, total=False):
+ id: Optional[str]
+ """The ID of an existing attachment object.
+
+ Use this when updating a resource and keeping a subset of the attachments. Don't
+ use this unless you know what you're doing.
+ """
+
+ direct_upload_id: Optional[str]
+ """This ID should be used the first time you upload an attachment.
+
+ It is the ID of the direct upload that was created when uploading the file to S3
+ via the mediaDirectUpload mutation.
+ """
diff --git a/src/whop_sdk/types/payment_list_params.py b/src/whop_sdk/types/payment_list_params.py
index 861346af..583427ae 100644
--- a/src/whop_sdk/types/payment_list_params.py
+++ b/src/whop_sdk/types/payment_list_params.py
@@ -8,6 +8,7 @@
from .._types import SequenceNotStr
from .._utils import PropertyInfo
+from .billing_reasons import BillingReasons
from .shared.currency import Currency
from .shared.direction import Direction
from .shared.receipt_status import ReceiptStatus
@@ -26,13 +27,7 @@ class PaymentListParams(TypedDict, total=False):
before: Optional[str]
"""Returns the elements in the list that come before the specified cursor."""
- billing_reasons: Optional[
- List[
- Literal[
- "subscription_create", "subscription_cycle", "subscription_update", "one_time", "manual", "subscription"
- ]
- ]
- ]
+ billing_reasons: Optional[List[BillingReasons]]
"""The billing reason for the payment"""
created_after: Annotated[Union[str, datetime, None], PropertyInfo(format="iso8601")]
diff --git a/src/whop_sdk/types/payment_list_response.py b/src/whop_sdk/types/payment_list_response.py
index 61cf5f2a..6ff597b0 100644
--- a/src/whop_sdk/types/payment_list_response.py
+++ b/src/whop_sdk/types/payment_list_response.py
@@ -4,8 +4,11 @@
from datetime import datetime
from .._models import BaseModel
+from .card_brands import CardBrands
+from .billing_reasons import BillingReasons
from .shared.currency import Currency
from .shared.promo_type import PromoType
+from .payment_method_types import PaymentMethodTypes
from .shared.receipt_status import ReceiptStatus
from .shared.membership_status import MembershipStatus
from .shared.friendly_receipt_status import FriendlyReceiptStatus
@@ -103,7 +106,7 @@ class PromoCode(BaseModel):
"""The specific code used to apply the promo at checkout."""
number_of_intervals: Optional[int] = None
- """The number of billing cycles the promo is applied for."""
+ """The number of months the promo is applied for."""
promo_type: PromoType
"""The type (% or flat amount) of the promo."""
@@ -136,11 +139,11 @@ class PaymentListResponse(BaseModel):
billing_address: Optional[BillingAddress] = None
"""The address of the user who made the payment."""
- billing_reason: Optional[str] = None
- """The billing reason"""
+ billing_reason: Optional[BillingReasons] = None
+ """The reason why a specific payment was billed"""
- card_brand: Optional[str] = None
- """The type of card used as the payment method."""
+ card_brand: Optional[CardBrands] = None
+ """Possible card brands that a payment token can have"""
card_last4: Optional[str] = None
"""The last 4 digits of the card used to make the payment."""
@@ -172,11 +175,8 @@ class PaymentListResponse(BaseModel):
paid_at: Optional[datetime] = None
"""The datetime the payment was paid"""
- payment_method_type: Optional[str] = None
- """Returns the type of payment method used for the payment, if available.
-
- Ex. klarna, affirm, card, cashapp
- """
+ payment_method_type: Optional[PaymentMethodTypes] = None
+ """The different types of payment methods that can be used."""
plan: Optional[Plan] = None
"""The plan attached to this payment."""
@@ -188,7 +188,10 @@ class PaymentListResponse(BaseModel):
"""The promo code used for this payment."""
refundable: bool
- """Whether the payment can be refunded."""
+ """
+ True only for payments that are `paid`, have not been fully refunded, and were
+ processed by a payment processor that allows refunds.
+ """
refunded_amount: Optional[float] = None
"""The payment refund amount(if applicable)."""
@@ -197,7 +200,11 @@ class PaymentListResponse(BaseModel):
"""When the payment was refunded (if applicable)."""
retryable: bool
- """Whether the payment can be retried."""
+ """
+ True when the payment status is `open` and its membership is in one of the
+ retry-eligible states (`active`, `trialing`, `completed`, or `past_due`);
+ otherwise false. Used to decide if Whop can attempt the charge again.
+ """
status: Optional[ReceiptStatus] = None
"""The status of a receipt"""
@@ -218,4 +225,7 @@ class PaymentListResponse(BaseModel):
"""The user that made this payment."""
voidable: bool
- """Whether the payment can be voided."""
+ """
+ True when the payment is tied to a membership in `past_due`, the payment status
+ is `open`, and the processor allows voiding payments; otherwise false.
+ """
diff --git a/src/whop_sdk/types/payment_method_types.py b/src/whop_sdk/types/payment_method_types.py
new file mode 100644
index 00000000..72c576f6
--- /dev/null
+++ b/src/whop_sdk/types/payment_method_types.py
@@ -0,0 +1,92 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing_extensions import Literal, TypeAlias
+
+__all__ = ["PaymentMethodTypes"]
+
+PaymentMethodTypes: TypeAlias = Literal[
+ "acss_debit",
+ "affirm",
+ "afterpay_clearpay",
+ "alipay",
+ "alma",
+ "amazon_pay",
+ "apple_pay",
+ "au_becs_debit",
+ "bacs_debit",
+ "bancontact",
+ "billie",
+ "blik",
+ "boleto",
+ "card",
+ "cashapp",
+ "crypto",
+ "eps",
+ "fpx",
+ "giropay",
+ "google_pay",
+ "grabpay",
+ "ideal",
+ "kakao_pay",
+ "klarna",
+ "konbini",
+ "kr_card",
+ "link",
+ "mobilepay",
+ "multibanco",
+ "naver_pay",
+ "nz_bank_account",
+ "oxxo",
+ "p24",
+ "pay_by_bank",
+ "payco",
+ "paynow",
+ "pix",
+ "promptpay",
+ "revolut_pay",
+ "samsung_pay",
+ "satispay",
+ "sepa_debit",
+ "sofort",
+ "swish",
+ "twint",
+ "us_bank_account",
+ "wechat_pay",
+ "zip",
+ "bizum",
+ "capchase_pay",
+ "kriya",
+ "mondu",
+ "ng_wallet",
+ "paypay",
+ "sequra",
+ "scalapay",
+ "vipps",
+ "custom",
+ "customer_balance",
+ "gopay",
+ "mb_way",
+ "ng_bank",
+ "ng_bank_transfer",
+ "ng_card",
+ "ng_market",
+ "ng_ussd",
+ "paypal",
+ "payto",
+ "qris",
+ "rechnung",
+ "south_korea_market",
+ "kr_market",
+ "shopeepay",
+ "upi",
+ "sunbit",
+ "netbanking",
+ "id_bank_transfer",
+ "demo_pay",
+ "shop_pay",
+ "apple",
+ "sezzle",
+ "coinbase",
+ "splitit",
+ "unknown",
+]
diff --git a/src/whop_sdk/types/plan_create_params.py b/src/whop_sdk/types/plan_create_params.py
index 04aee462..d1c7dc4c 100644
--- a/src/whop_sdk/types/plan_create_params.py
+++ b/src/whop_sdk/types/plan_create_params.py
@@ -22,7 +22,7 @@ class PlanCreateParams(TypedDict, total=False):
"""The product the plan is related to."""
billing_period: Optional[int]
- """The interval at which the plan charges (renewal plans)."""
+ """The interval in days at which the plan charges (renewal plans)."""
currency: Optional[Currency]
"""The available currencies on the platform"""
@@ -68,12 +68,30 @@ class PlanCreateParams(TypedDict, total=False):
$10.43
"""
+ stock: Optional[int]
+ """The number of units available for purchase."""
+
+ strike_through_initial_price: Optional[float]
+ """The price to display with a strikethrough for the initial price.
+
+ Provided as a number in dollars. Eg: 19.99 for $19.99
+ """
+
+ strike_through_renewal_price: Optional[float]
+ """The price to display with a strikethrough for the renewal price.
+
+ Provided as a number in dollars. Eg: 19.99 for $19.99
+ """
+
title: Optional[str]
"""The title of the plan. This will be visible on the product page to customers."""
trial_period_days: Optional[int]
"""The number of free trial days added before a renewal plan."""
+ unlimited_stock: Optional[bool]
+ """Limits/doesn't limit the number of units available for purchase."""
+
visibility: Optional[Visibility]
"""Visibility of a resource"""
diff --git a/src/whop_sdk/types/plan_list_response.py b/src/whop_sdk/types/plan_list_response.py
index 58bb0a61..476da8f3 100644
--- a/src/whop_sdk/types/plan_list_response.py
+++ b/src/whop_sdk/types/plan_list_response.py
@@ -82,9 +82,18 @@ class PlanListResponse(BaseModel):
renewal_price: float
"""The price a person has to pay for a plan on the renewal purchase."""
+ stock: Optional[int] = None
+ """The number of units available for purchase. Only displayed to authorized actors"""
+
+ title: Optional[str] = None
+ """The title of the plan. This will be visible on the product page to customers."""
+
trial_period_days: Optional[int] = None
"""The number of free trial days added before a renewal plan."""
+ unlimited_stock: bool
+ """Limits/doesn't limit the number of units available for purchase."""
+
updated_at: datetime
"""When the plan was last updated."""
diff --git a/src/whop_sdk/types/plan_update_params.py b/src/whop_sdk/types/plan_update_params.py
index 5a2fd62d..4b7ab21c 100644
--- a/src/whop_sdk/types/plan_update_params.py
+++ b/src/whop_sdk/types/plan_update_params.py
@@ -49,12 +49,30 @@ class PlanUpdateParams(TypedDict, total=False):
renewal_price: Optional[float]
"""The amount the customer is charged every billing period."""
+ stock: Optional[int]
+ """The number of units available for purchase."""
+
+ strike_through_initial_price: Optional[float]
+ """The price to display with a strikethrough for the initial price.
+
+ Provided as a number in dollars. Eg: 19.99 for $19.99
+ """
+
+ strike_through_renewal_price: Optional[float]
+ """The price to display with a strikethrough for the renewal price.
+
+ Provided as a number in dollars. Eg: 19.99 for $19.99
+ """
+
title: Optional[str]
"""The title of the plan. This will be visible on the product page to customers."""
trial_period_days: Optional[int]
"""The number of free trial days added before a renewal plan."""
+ unlimited_stock: Optional[bool]
+ """Limits/doesn't limit the number of units available for purchase."""
+
visibility: Optional[Visibility]
"""Visibility of a resource"""
diff --git a/src/whop_sdk/types/product_create_params.py b/src/whop_sdk/types/product_create_params.py
index f1b6de65..3e5f5c73 100644
--- a/src/whop_sdk/types/product_create_params.py
+++ b/src/whop_sdk/types/product_create_params.py
@@ -13,10 +13,9 @@
from .shared.business_types import BusinessTypes
from .shared.industry_types import IndustryTypes
from .shared.release_method import ReleaseMethod
-from .shared.access_pass_type import AccessPassType
from .shared.global_affiliate_status import GlobalAffiliateStatus
-__all__ = ["ProductCreateParams", "BannerImage", "PlanOptions", "PlanOptionsCustomField", "ProductHighlight"]
+__all__ = ["ProductCreateParams", "PlanOptions", "PlanOptionsCustomField", "ProductHighlight"]
class ProductCreateParams(TypedDict, total=False):
@@ -26,12 +25,6 @@ class ProductCreateParams(TypedDict, total=False):
title: Required[str]
"""The title of the product."""
- access_pass_type: Optional[AccessPassType]
- """The different types an access pass can be."""
-
- banner_image: Optional[BannerImage]
- """A banner image for the product in png, jpeg format"""
-
business_type: Optional[BusinessTypes]
"""The different business types a company can be."""
@@ -94,22 +87,6 @@ class ProductCreateParams(TypedDict, total=False):
"""Visibility of a resource"""
-class BannerImage(TypedDict, total=False):
- id: Optional[str]
- """The ID of an existing attachment object.
-
- Use this when updating a resource and keeping a subset of the attachments. Don't
- use this unless you know what you're doing.
- """
-
- direct_upload_id: Optional[str]
- """This ID should be used the first time you upload an attachment.
-
- It is the ID of the direct upload that was created when uploading the file to S3
- via the mediaDirectUpload mutation.
- """
-
-
class PlanOptionsCustomField(TypedDict, total=False):
field_type: Required[Literal["text"]]
"""The type of the custom field."""
diff --git a/src/whop_sdk/types/product_update_params.py b/src/whop_sdk/types/product_update_params.py
index fc0b6524..8251a5ca 100644
--- a/src/whop_sdk/types/product_update_params.py
+++ b/src/whop_sdk/types/product_update_params.py
@@ -11,7 +11,7 @@
from .shared.industry_types import IndustryTypes
from .shared.global_affiliate_status import GlobalAffiliateStatus
-__all__ = ["ProductUpdateParams", "BannerImage"]
+__all__ = ["ProductUpdateParams", "BannerImage", "StorePageConfig"]
class ProductUpdateParams(TypedDict, total=False):
@@ -67,6 +67,9 @@ class ProductUpdateParams(TypedDict, total=False):
route: Optional[str]
"""The route of the product."""
+ store_page_config: Optional[StorePageConfig]
+ """Configuration for a product on the company's store page."""
+
title: Optional[str]
"""The title of the product."""
@@ -88,3 +91,11 @@ class BannerImage(TypedDict, total=False):
It is the ID of the direct upload that was created when uploading the file to S3
via the mediaDirectUpload mutation.
"""
+
+
+class StorePageConfig(TypedDict, total=False):
+ custom_cta: Optional[str]
+ """Custom call-to-action text for the product's store page."""
+
+ show_price: Optional[bool]
+ """Whether or not to show the price on the product's store page."""
diff --git a/src/whop_sdk/types/promo_code.py b/src/whop_sdk/types/promo_code.py
new file mode 100644
index 00000000..7a03658a
--- /dev/null
+++ b/src/whop_sdk/types/promo_code.py
@@ -0,0 +1,90 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Optional
+from datetime import datetime
+
+from .._models import BaseModel
+from .promo_duration import PromoDuration
+from .shared.currency import Currency
+from .promo_code_status import PromoCodeStatus
+from .shared.promo_type import PromoType
+
+__all__ = ["PromoCode", "Company", "Product"]
+
+
+class Company(BaseModel):
+ id: str
+ """The ID of the company"""
+
+ title: str
+ """The written name of the company."""
+
+
+class Product(BaseModel):
+ id: str
+ """The internal ID of the public product."""
+
+ title: str
+ """The title of the product. Use for Whop 4.0."""
+
+
+class PromoCode(BaseModel):
+ id: str
+ """The ID of the promo."""
+
+ amount_off: float
+ """The amount off (% or flat amount) for the promo."""
+
+ churned_users_only: bool
+ """Restricts promo use to only users who have churned from the company before."""
+
+ code: Optional[str] = None
+ """The specific code used to apply the promo at checkout."""
+
+ company: Company
+ """The company for the promo code."""
+
+ created_at: datetime
+ """The timestamp of when the promo was created."""
+
+ currency: Currency
+ """The monetary currency of the promo code."""
+
+ duration: Optional[PromoDuration] = None
+ """The duration setting for the promo code"""
+
+ existing_memberships_only: bool
+ """Restricts promo use to only be applied to already purchased memberships."""
+
+ expires_at: Optional[datetime] = None
+ """The date/time of when the promo expires."""
+
+ new_users_only: bool
+ """
+ Restricts promo use to only users who have never purchased from the company
+ before.
+ """
+
+ one_per_customer: bool
+ """Restricts promo use to only be applied once per customer."""
+
+ product: Optional[Product] = None
+ """The access pass associated with the promo code."""
+
+ promo_duration_months: Optional[int] = None
+ """The number of months the promo is applied for."""
+
+ promo_type: PromoType
+ """The type (% or flat amount) of the promo."""
+
+ status: PromoCodeStatus
+ """Indicates if the promo code is live or disabled."""
+
+ stock: int
+ """The quantity limit on the number of uses."""
+
+ unlimited_stock: bool
+ """Whether or not the promo code has unlimited stock."""
+
+ uses: int
+ """The amount of times the promo codes has been used."""
diff --git a/src/whop_sdk/types/promo_code_create_params.py b/src/whop_sdk/types/promo_code_create_params.py
new file mode 100644
index 00000000..264243bb
--- /dev/null
+++ b/src/whop_sdk/types/promo_code_create_params.py
@@ -0,0 +1,70 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Union, Optional
+from datetime import datetime
+from typing_extensions import Required, Annotated, TypedDict
+
+from .._types import SequenceNotStr
+from .._utils import PropertyInfo
+from .shared.currency import Currency
+from .shared.promo_type import PromoType
+
+__all__ = ["PromoCodeCreateParams"]
+
+
+class PromoCodeCreateParams(TypedDict, total=False):
+ amount_off: Required[float]
+ """The amount off (% or flat amount) for the promo."""
+
+ base_currency: Required[Currency]
+ """The monetary currency of the promo code."""
+
+ code: Required[str]
+ """The specific code used to apply the promo at checkout."""
+
+ company_id: Required[str]
+ """The id of the company to create the promo code for."""
+
+ new_users_only: Required[bool]
+ """
+ Restricts promo use to only users who have never purchased from the company
+ before.
+ """
+
+ promo_duration_months: Required[int]
+ """The number of months this promo code is applied and valid for."""
+
+ promo_type: Required[PromoType]
+ """The type (% or flat amount) of the promo."""
+
+ churned_users_only: Optional[bool]
+ """Restricts promo use to only users who have churned from the company before."""
+
+ existing_memberships_only: Optional[bool]
+ """Whether this promo code is for existing memberships only (cancelations)"""
+
+ expires_at: Annotated[Union[str, datetime, None], PropertyInfo(format="iso8601")]
+ """The date/time of when the promo expires."""
+
+ one_per_customer: Optional[bool]
+ """Restricts promo use to only be applied once per customer."""
+
+ plan_ids: Optional[SequenceNotStr[str]]
+ """The IDs of the plans that the promo code applies to.
+
+ If product_id is provided, it will only apply to plans attached to that product
+ """
+
+ product_id: Optional[str]
+ """The product to lock the promo code to, if any.
+
+ If provided will filter out any plan ids not attached to this product
+ """
+
+ stock: Optional[int]
+ """The quantity limit on the number of uses."""
+
+ unlimited_stock: Optional[bool]
+ """Whether or not the promo code should have unlimited stock."""
diff --git a/src/whop_sdk/types/promo_code_delete_response.py b/src/whop_sdk/types/promo_code_delete_response.py
new file mode 100644
index 00000000..f7939b18
--- /dev/null
+++ b/src/whop_sdk/types/promo_code_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__ = ["PromoCodeDeleteResponse"]
+
+PromoCodeDeleteResponse: TypeAlias = bool
diff --git a/src/whop_sdk/types/promo_code_list_params.py b/src/whop_sdk/types/promo_code_list_params.py
new file mode 100644
index 00000000..e632ba2d
--- /dev/null
+++ b/src/whop_sdk/types/promo_code_list_params.py
@@ -0,0 +1,37 @@
+# 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 .promo_code_status import PromoCodeStatus
+
+__all__ = ["PromoCodeListParams"]
+
+
+class PromoCodeListParams(TypedDict, total=False):
+ company_id: Required[str]
+ """The ID of the company to list promo codes for"""
+
+ after: Optional[str]
+ """Returns the elements in the list that come after the specified cursor."""
+
+ before: Optional[str]
+ """Returns the elements in the list that come before the specified cursor."""
+
+ first: Optional[int]
+ """Returns the first _n_ elements from the list."""
+
+ last: Optional[int]
+ """Returns the last _n_ elements from the list."""
+
+ plan_ids: Optional[SequenceNotStr[str]]
+ """Filter promo codes by plan ID(s)"""
+
+ product_ids: Optional[SequenceNotStr[str]]
+ """Filter promo codes by product ID(s)"""
+
+ status: Optional[PromoCodeStatus]
+ """Statuses for promo codes"""
diff --git a/src/whop_sdk/types/promo_code_list_response.py b/src/whop_sdk/types/promo_code_list_response.py
new file mode 100644
index 00000000..1c87acfe
--- /dev/null
+++ b/src/whop_sdk/types/promo_code_list_response.py
@@ -0,0 +1,79 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Optional
+from datetime import datetime
+
+from .._models import BaseModel
+from .promo_duration import PromoDuration
+from .shared.currency import Currency
+from .promo_code_status import PromoCodeStatus
+from .shared.promo_type import PromoType
+
+__all__ = ["PromoCodeListResponse", "Product"]
+
+
+class Product(BaseModel):
+ id: str
+ """The internal ID of the public product."""
+
+ title: str
+ """The title of the product. Use for Whop 4.0."""
+
+
+class PromoCodeListResponse(BaseModel):
+ id: str
+ """The ID of the promo."""
+
+ amount_off: float
+ """The amount off (% or flat amount) for the promo."""
+
+ churned_users_only: bool
+ """Restricts promo use to only users who have churned from the company before."""
+
+ code: Optional[str] = None
+ """The specific code used to apply the promo at checkout."""
+
+ created_at: datetime
+ """The timestamp of when the promo was created."""
+
+ currency: Currency
+ """The monetary currency of the promo code."""
+
+ duration: Optional[PromoDuration] = None
+ """The duration setting for the promo code"""
+
+ existing_memberships_only: bool
+ """Restricts promo use to only be applied to already purchased memberships."""
+
+ expires_at: Optional[datetime] = None
+ """The date/time of when the promo expires."""
+
+ new_users_only: bool
+ """
+ Restricts promo use to only users who have never purchased from the company
+ before.
+ """
+
+ one_per_customer: bool
+ """Restricts promo use to only be applied once per customer."""
+
+ product: Optional[Product] = None
+ """The access pass associated with the promo code."""
+
+ promo_duration_months: Optional[int] = None
+ """The number of months the promo is applied for."""
+
+ promo_type: PromoType
+ """The type (% or flat amount) of the promo."""
+
+ status: PromoCodeStatus
+ """Indicates if the promo code is live or disabled."""
+
+ stock: int
+ """The quantity limit on the number of uses."""
+
+ unlimited_stock: bool
+ """Whether or not the promo code has unlimited stock."""
+
+ uses: int
+ """The amount of times the promo codes has been used."""
diff --git a/src/whop_sdk/types/promo_code_status.py b/src/whop_sdk/types/promo_code_status.py
new file mode 100644
index 00000000..e38a4a7f
--- /dev/null
+++ b/src/whop_sdk/types/promo_code_status.py
@@ -0,0 +1,7 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing_extensions import Literal, TypeAlias
+
+__all__ = ["PromoCodeStatus"]
+
+PromoCodeStatus: TypeAlias = Literal["active", "inactive", "archived"]
diff --git a/src/whop_sdk/types/promo_duration.py b/src/whop_sdk/types/promo_duration.py
new file mode 100644
index 00000000..0145e77e
--- /dev/null
+++ b/src/whop_sdk/types/promo_duration.py
@@ -0,0 +1,7 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing_extensions import Literal, TypeAlias
+
+__all__ = ["PromoDuration"]
+
+PromoDuration: TypeAlias = Literal["forever", "once", "repeating"]
diff --git a/src/whop_sdk/types/review_list_params.py b/src/whop_sdk/types/review_list_params.py
new file mode 100644
index 00000000..25a19401
--- /dev/null
+++ b/src/whop_sdk/types/review_list_params.py
@@ -0,0 +1,31 @@
+# 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
+
+__all__ = ["ReviewListParams"]
+
+
+class ReviewListParams(TypedDict, total=False):
+ product_id: Required[str]
+ """The ID of the product"""
+
+ after: Optional[str]
+ """Returns the elements in the list that come after the specified cursor."""
+
+ before: Optional[str]
+ """Returns the elements in the list that come before the specified cursor."""
+
+ first: Optional[int]
+ """Returns the first _n_ elements from the list."""
+
+ last: Optional[int]
+ """Returns the last _n_ elements from the list."""
+
+ max_stars: Optional[int]
+ """The maximum star rating of the review (inclusive)"""
+
+ min_stars: Optional[int]
+ """The minimum star rating of the review (inclusive)"""
diff --git a/src/whop_sdk/types/review_list_response.py b/src/whop_sdk/types/review_list_response.py
new file mode 100644
index 00000000..5f463a80
--- /dev/null
+++ b/src/whop_sdk/types/review_list_response.py
@@ -0,0 +1,78 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import List, Optional
+from datetime import datetime
+
+from .._models import BaseModel
+from .review_status import ReviewStatus
+
+__all__ = ["ReviewListResponse", "Attachment", "User"]
+
+
+class Attachment(BaseModel):
+ id: str
+ """The ID of the attachment"""
+
+ content_type: Optional[str] = None
+ """The attachment's content type (e.g., image/jpg, video/mp4)"""
+
+ filename: Optional[str] = None
+ """The name of the file"""
+
+ url: Optional[str] = None
+ """This is the URL you use to render optimized attachments on the client.
+
+ This should be used for apps.
+ """
+
+
+class User(BaseModel):
+ id: str
+ """The internal ID of the user."""
+
+ name: Optional[str] = None
+ """The name of the user from their Whop account."""
+
+ username: str
+ """The username of the user from their Whop account."""
+
+
+class ReviewListResponse(BaseModel):
+ id: str
+ """The internal ID of the review."""
+
+ attachments: List[Attachment]
+ """The attachments attached to the review."""
+
+ created_at: datetime
+ """The timestamp of when the review was created."""
+
+ description: Optional[str] = None
+ """The description of the review."""
+
+ joined_at: Optional[datetime] = None
+ """The timestamp of when the user joined the product."""
+
+ paid_for_product: Optional[bool] = None
+ """Whether or not the user paid for the product.
+
+ If null, the payment status is unknown.
+ """
+
+ published_at: Optional[datetime] = None
+ """The timestamp of when the review was published."""
+
+ stars: int
+ """The number of stars the user gave the product."""
+
+ status: ReviewStatus
+ """The status of the review."""
+
+ title: Optional[str] = None
+ """The title of the review."""
+
+ updated_at: datetime
+ """The timestamp of when the review was last updated."""
+
+ user: User
+ """The user account that performed the action."""
diff --git a/src/whop_sdk/types/review_retrieve_response.py b/src/whop_sdk/types/review_retrieve_response.py
new file mode 100644
index 00000000..b71704fe
--- /dev/null
+++ b/src/whop_sdk/types/review_retrieve_response.py
@@ -0,0 +1,103 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import List, Optional
+from datetime import datetime
+
+from .._models import BaseModel
+from .review_status import ReviewStatus
+
+__all__ = ["ReviewRetrieveResponse", "Attachment", "Company", "Product", "User"]
+
+
+class Attachment(BaseModel):
+ id: str
+ """The ID of the attachment"""
+
+ content_type: Optional[str] = None
+ """The attachment's content type (e.g., image/jpg, video/mp4)"""
+
+ filename: Optional[str] = None
+ """The name of the file"""
+
+ url: Optional[str] = None
+ """This is the URL you use to render optimized attachments on the client.
+
+ This should be used for apps.
+ """
+
+
+class Company(BaseModel):
+ id: str
+ """The ID (tag) of the company."""
+
+ route: str
+ """The slug/route of the company on the Whop site."""
+
+ title: str
+ """The title of the company."""
+
+
+class Product(BaseModel):
+ id: str
+ """The internal ID of the public product."""
+
+ title: str
+ """The title of the product. Use for Whop 4.0."""
+
+
+class User(BaseModel):
+ id: str
+ """The internal ID of the user."""
+
+ name: Optional[str] = None
+ """The name of the user from their Whop account."""
+
+ username: str
+ """The username of the user from their Whop account."""
+
+
+class ReviewRetrieveResponse(BaseModel):
+ id: str
+ """The internal ID of the review."""
+
+ attachments: List[Attachment]
+ """The attachments attached to the review."""
+
+ company: Company
+ """The company the review is for."""
+
+ created_at: datetime
+ """The timestamp of when the review was created."""
+
+ description: Optional[str] = None
+ """The description of the review."""
+
+ joined_at: Optional[datetime] = None
+ """The timestamp of when the user joined the product."""
+
+ paid_for_product: Optional[bool] = None
+ """Whether or not the user paid for the product.
+
+ If null, the payment status is unknown.
+ """
+
+ product: Product
+ """The product the review is for."""
+
+ published_at: Optional[datetime] = None
+ """The timestamp of when the review was published."""
+
+ stars: int
+ """The number of stars the user gave the product."""
+
+ status: ReviewStatus
+ """The status of the review."""
+
+ title: Optional[str] = None
+ """The title of the review."""
+
+ updated_at: datetime
+ """The timestamp of when the review was last updated."""
+
+ user: User
+ """The user account that performed the action."""
diff --git a/src/whop_sdk/types/review_status.py b/src/whop_sdk/types/review_status.py
new file mode 100644
index 00000000..fb262e40
--- /dev/null
+++ b/src/whop_sdk/types/review_status.py
@@ -0,0 +1,7 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing_extensions import Literal, TypeAlias
+
+__all__ = ["ReviewStatus"]
+
+ReviewStatus: TypeAlias = Literal["pending", "published", "removed"]
diff --git a/src/whop_sdk/types/shared/checkout_configuration.py b/src/whop_sdk/types/shared/checkout_configuration.py
index 0812fa47..6a139a81 100644
--- a/src/whop_sdk/types/shared/checkout_configuration.py
+++ b/src/whop_sdk/types/shared/checkout_configuration.py
@@ -60,7 +60,10 @@ class CheckoutConfiguration(BaseModel):
"""The plan to use for the checkout configuration"""
purchase_url: str
- """The URL to redirect the user to after the checkout configuration is created"""
+ """A URL you can send to customers to complete a checkout.
+
+ It looks like `/checkout/plan_xxxx?session={id}`
+ """
redirect_url: Optional[str] = None
"""The URL to redirect the user to after the checkout configuration is created"""
diff --git a/src/whop_sdk/types/shared/forum_post.py b/src/whop_sdk/types/shared/forum_post.py
index 7ad4855e..9156694f 100644
--- a/src/whop_sdk/types/shared/forum_post.py
+++ b/src/whop_sdk/types/shared/forum_post.py
@@ -1,6 +1,7 @@
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
from typing import Optional
+from datetime import datetime
from ..._models import BaseModel
@@ -28,6 +29,9 @@ class ForumPost(BaseModel):
content: Optional[str] = None
"""The content of the forum post in Markdown format"""
+ created_at: datetime
+ """The timestamp when the post was created"""
+
is_edited: bool
"""Whether the forum post has been edited"""
@@ -46,6 +50,9 @@ class ForumPost(BaseModel):
title: Optional[str] = None
"""The title of the forum post"""
+ updated_at: datetime
+ """The timestamp when the post was last updated"""
+
user: User
"""The user who created this forum post"""
diff --git a/src/whop_sdk/types/shared/invoice.py b/src/whop_sdk/types/shared/invoice.py
index 597d63cc..e4372cf6 100644
--- a/src/whop_sdk/types/shared/invoice.py
+++ b/src/whop_sdk/types/shared/invoice.py
@@ -49,7 +49,10 @@ class Invoice(BaseModel):
"""The email address that the invoice was created for."""
fetch_invoice_token: str
- """The token to fetch the invoice."""
+ """
+ A signed token that allows fetching the invoice data publically without being
+ authenticated.
+ """
number: str
"""The number of the invoice."""
diff --git a/src/whop_sdk/types/shared/invoice_list_item.py b/src/whop_sdk/types/shared/invoice_list_item.py
index 2d739aae..2763ceee 100644
--- a/src/whop_sdk/types/shared/invoice_list_item.py
+++ b/src/whop_sdk/types/shared/invoice_list_item.py
@@ -49,7 +49,10 @@ class InvoiceListItem(BaseModel):
"""The email address that the invoice was created for."""
fetch_invoice_token: str
- """The token to fetch the invoice."""
+ """
+ A signed token that allows fetching the invoice data publically without being
+ authenticated.
+ """
number: str
"""The number of the invoice."""
diff --git a/src/whop_sdk/types/shared/membership.py b/src/whop_sdk/types/shared/membership.py
index d1df5310..81f8926c 100644
--- a/src/whop_sdk/types/shared/membership.py
+++ b/src/whop_sdk/types/shared/membership.py
@@ -7,7 +7,7 @@
from ..._models import BaseModel
from .membership_status import MembershipStatus
-__all__ = ["Membership", "Company", "Member", "Plan", "PromoCode", "User"]
+__all__ = ["Membership", "Company", "Member", "Plan", "Product", "PromoCode", "User"]
class Company(BaseModel):
@@ -28,6 +28,14 @@ class Plan(BaseModel):
"""The internal ID of the plan."""
+class Product(BaseModel):
+ id: str
+ """The internal ID of the public product."""
+
+ title: str
+ """The title of the product. Use for Whop 4.0."""
+
+
class PromoCode(BaseModel):
id: str
"""The ID of the promo."""
@@ -92,6 +100,9 @@ class Membership(BaseModel):
plan: Plan
"""The Plan this Membership is for."""
+ product: Product
+ """The Product this Membership grants access to."""
+
promo_code: Optional[PromoCode] = None
"""The Promo Code that is currently applied to this Membership."""
diff --git a/src/whop_sdk/types/shared/payment.py b/src/whop_sdk/types/shared/payment.py
index d84426f5..73e60912 100644
--- a/src/whop_sdk/types/shared/payment.py
+++ b/src/whop_sdk/types/shared/payment.py
@@ -6,8 +6,11 @@
from .currency import Currency
from ..._models import BaseModel
from .promo_type import PromoType
+from ..card_brands import CardBrands
from .receipt_status import ReceiptStatus
+from ..billing_reasons import BillingReasons
from .membership_status import MembershipStatus
+from ..payment_method_types import PaymentMethodTypes
from .friendly_receipt_status import FriendlyReceiptStatus
__all__ = ["Payment", "BillingAddress", "Company", "Member", "Membership", "Plan", "Product", "PromoCode", "User"]
@@ -93,7 +96,7 @@ class PromoCode(BaseModel):
"""The specific code used to apply the promo at checkout."""
number_of_intervals: Optional[int] = None
- """The number of billing cycles the promo is applied for."""
+ """The number of months the promo is applied for."""
promo_type: PromoType
"""The type (% or flat amount) of the promo."""
@@ -126,11 +129,11 @@ class Payment(BaseModel):
billing_address: Optional[BillingAddress] = None
"""The address of the user who made the payment."""
- billing_reason: Optional[str] = None
- """The billing reason"""
+ billing_reason: Optional[BillingReasons] = None
+ """The reason why a specific payment was billed"""
- card_brand: Optional[str] = None
- """The type of card used as the payment method."""
+ card_brand: Optional[CardBrands] = None
+ """Possible card brands that a payment token can have"""
card_last4: Optional[str] = None
"""The last 4 digits of the card used to make the payment."""
@@ -162,11 +165,8 @@ class Payment(BaseModel):
paid_at: Optional[datetime] = None
"""The datetime the payment was paid"""
- payment_method_type: Optional[str] = None
- """Returns the type of payment method used for the payment, if available.
-
- Ex. klarna, affirm, card, cashapp
- """
+ payment_method_type: Optional[PaymentMethodTypes] = None
+ """The different types of payment methods that can be used."""
plan: Optional[Plan] = None
"""The plan attached to this payment."""
@@ -178,7 +178,10 @@ class Payment(BaseModel):
"""The promo code used for this payment."""
refundable: bool
- """Whether the payment can be refunded."""
+ """
+ True only for payments that are `paid`, have not been fully refunded, and were
+ processed by a payment processor that allows refunds.
+ """
refunded_amount: Optional[float] = None
"""The payment refund amount(if applicable)."""
@@ -187,7 +190,11 @@ class Payment(BaseModel):
"""When the payment was refunded (if applicable)."""
retryable: bool
- """Whether the payment can be retried."""
+ """
+ True when the payment status is `open` and its membership is in one of the
+ retry-eligible states (`active`, `trialing`, `completed`, or `past_due`);
+ otherwise false. Used to decide if Whop can attempt the charge again.
+ """
status: Optional[ReceiptStatus] = None
"""The status of a receipt"""
@@ -208,4 +215,7 @@ class Payment(BaseModel):
"""The user that made this payment."""
voidable: bool
- """Whether the payment can be voided."""
+ """
+ True when the payment is tied to a membership in `past_due`, the payment status
+ is `open`, and the processor allows voiding payments; otherwise false.
+ """
diff --git a/src/whop_sdk/types/shared/plan.py b/src/whop_sdk/types/shared/plan.py
index c55eca08..10175a0c 100644
--- a/src/whop_sdk/types/shared/plan.py
+++ b/src/whop_sdk/types/shared/plan.py
@@ -110,12 +110,21 @@ class Plan(BaseModel):
renewal_price: float
"""The price a person has to pay for a plan on the renewal purchase."""
+ stock: Optional[int] = None
+ """The number of units available for purchase. Only displayed to authorized actors"""
+
tax_type: TaxType
"""The tax type for the plan."""
+ title: Optional[str] = None
+ """The title of the plan. This will be visible on the product page to customers."""
+
trial_period_days: Optional[int] = None
"""The number of free trial days added before a renewal plan."""
+ unlimited_stock: bool
+ """Limits/doesn't limit the number of units available for purchase."""
+
updated_at: datetime
"""When the plan was last updated."""
diff --git a/src/whop_sdk/types/shared/product.py b/src/whop_sdk/types/shared/product.py
index 5db38e08..da8bfcf9 100644
--- a/src/whop_sdk/types/shared/product.py
+++ b/src/whop_sdk/types/shared/product.py
@@ -72,6 +72,14 @@ class Product(BaseModel):
description: Optional[str] = None
"""A short description of what the company offers or does."""
+ external_identifier: Optional[str] = None
+ """A unique identifier used to create or update products.
+
+ When provided on product creation endpoints, we’ll look up an existing product
+ by this identifier — if it exists, we’ll update it; if not, we’ll create a new
+ one.
+ """
+
global_affiliate_percentage: Optional[float] = None
"""
The percentage of a transaction a user is eligible to earn from the whop
diff --git a/src/whop_sdk/types/shared/product_list_item.py b/src/whop_sdk/types/shared/product_list_item.py
index c15dbb30..adc56a56 100644
--- a/src/whop_sdk/types/shared/product_list_item.py
+++ b/src/whop_sdk/types/shared/product_list_item.py
@@ -21,6 +21,14 @@ class ProductListItem(BaseModel):
created_at: datetime
"""When the product was created."""
+ external_identifier: Optional[str] = None
+ """A unique identifier used to create or update products.
+
+ When provided on product creation endpoints, we’ll look up an existing product
+ by this identifier — if it exists, we’ll update it; if not, we’ll create a new
+ one.
+ """
+
headline: Optional[str] = None
"""The headline of the product."""
diff --git a/src/whop_sdk/types/shared_params/__init__.py b/src/whop_sdk/types/shared_params/__init__.py
index c6e79558..8ed721e0 100644
--- a/src/whop_sdk/types/shared_params/__init__.py
+++ b/src/whop_sdk/types/shared_params/__init__.py
@@ -5,6 +5,7 @@
from .direction import Direction as Direction
from .plan_type import PlanType as PlanType
from .custom_cta import CustomCta as CustomCta
+from .promo_type import PromoType as PromoType
from .visibility import Visibility as Visibility
from .access_level import AccessLevel as AccessLevel
from .app_statuses import AppStatuses as AppStatuses
diff --git a/src/whop_sdk/types/shared_params/promo_type.py b/src/whop_sdk/types/shared_params/promo_type.py
new file mode 100644
index 00000000..098f25fe
--- /dev/null
+++ b/src/whop_sdk/types/shared_params/promo_type.py
@@ -0,0 +1,9 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Literal, TypeAlias
+
+__all__ = ["PromoType"]
+
+PromoType: TypeAlias = Literal["percentage", "flat_amount"]
diff --git a/tests/api_resources/test_checkout_configurations.py b/tests/api_resources/test_checkout_configurations.py
index 13ad6753..b6401c2c 100644
--- a/tests/api_resources/test_checkout_configurations.py
+++ b/tests/api_resources/test_checkout_configurations.py
@@ -58,6 +58,22 @@ def test_method_create_with_all_params(self, client: Whop) -> None:
"internal_notes": "internal_notes",
"override_tax_type": "inclusive",
"plan_type": "renewal",
+ "product": {
+ "external_identifier": "external_identifier",
+ "title": "title",
+ "business_type": "education_program",
+ "collect_shipping_address": True,
+ "custom_statement_descriptor": "custom_statement_descriptor",
+ "description": "description",
+ "global_affiliate_percentage": 6.9,
+ "global_affiliate_status": "enabled",
+ "headline": "headline",
+ "industry_type": "trading",
+ "product_tax_code_id": "ptc_xxxxxxxxxxxxxx",
+ "redirect_purchase_url": "redirect_purchase_url",
+ "route": "route",
+ "visibility": "visible",
+ },
"product_id": "prod_xxxxxxxxxxxxx",
"release_method": "buy_now",
"renewal_price": 6.9,
@@ -233,6 +249,22 @@ async def test_method_create_with_all_params(self, async_client: AsyncWhop) -> N
"internal_notes": "internal_notes",
"override_tax_type": "inclusive",
"plan_type": "renewal",
+ "product": {
+ "external_identifier": "external_identifier",
+ "title": "title",
+ "business_type": "education_program",
+ "collect_shipping_address": True,
+ "custom_statement_descriptor": "custom_statement_descriptor",
+ "description": "description",
+ "global_affiliate_percentage": 6.9,
+ "global_affiliate_status": "enabled",
+ "headline": "headline",
+ "industry_type": "trading",
+ "product_tax_code_id": "ptc_xxxxxxxxxxxxxx",
+ "redirect_purchase_url": "redirect_purchase_url",
+ "route": "route",
+ "visibility": "visible",
+ },
"product_id": "prod_xxxxxxxxxxxxx",
"release_method": "buy_now",
"renewal_price": 6.9,
diff --git a/tests/api_resources/test_course_chapters.py b/tests/api_resources/test_course_chapters.py
new file mode 100644
index 00000000..e7358f66
--- /dev/null
+++ b/tests/api_resources/test_course_chapters.py
@@ -0,0 +1,467 @@
+# 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 (
+ CourseChapter,
+ CourseChapterListResponse,
+ CourseChapterDeleteResponse,
+)
+from whop_sdk.pagination import SyncCursorPage, AsyncCursorPage
+
+base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
+
+
+class TestCourseChapters:
+ parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ def test_method_create(self, client: Whop) -> None:
+ course_chapter = client.course_chapters.create(
+ course_id="cors_xxxxxxxxxxxxx",
+ )
+ assert_matches_type(CourseChapter, course_chapter, path=["response"])
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ def test_method_create_with_all_params(self, client: Whop) -> None:
+ course_chapter = client.course_chapters.create(
+ course_id="cors_xxxxxxxxxxxxx",
+ title="title",
+ )
+ assert_matches_type(CourseChapter, course_chapter, path=["response"])
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ def test_raw_response_create(self, client: Whop) -> None:
+ response = client.course_chapters.with_raw_response.create(
+ course_id="cors_xxxxxxxxxxxxx",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ course_chapter = response.parse()
+ assert_matches_type(CourseChapter, course_chapter, path=["response"])
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ def test_streaming_response_create(self, client: Whop) -> None:
+ with client.course_chapters.with_streaming_response.create(
+ course_id="cors_xxxxxxxxxxxxx",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ course_chapter = response.parse()
+ assert_matches_type(CourseChapter, course_chapter, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ def test_method_retrieve(self, client: Whop) -> None:
+ course_chapter = client.course_chapters.retrieve(
+ "chap_xxxxxxxxxxxxx",
+ )
+ assert_matches_type(CourseChapter, course_chapter, path=["response"])
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ def test_raw_response_retrieve(self, client: Whop) -> None:
+ response = client.course_chapters.with_raw_response.retrieve(
+ "chap_xxxxxxxxxxxxx",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ course_chapter = response.parse()
+ assert_matches_type(CourseChapter, course_chapter, path=["response"])
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ def test_streaming_response_retrieve(self, client: Whop) -> None:
+ with client.course_chapters.with_streaming_response.retrieve(
+ "chap_xxxxxxxxxxxxx",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ course_chapter = response.parse()
+ assert_matches_type(CourseChapter, course_chapter, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Prism 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.course_chapters.with_raw_response.retrieve(
+ "",
+ )
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ def test_method_update(self, client: Whop) -> None:
+ course_chapter = client.course_chapters.update(
+ id="chap_xxxxxxxxxxxxx",
+ title="title",
+ )
+ assert_matches_type(CourseChapter, course_chapter, path=["response"])
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ def test_raw_response_update(self, client: Whop) -> None:
+ response = client.course_chapters.with_raw_response.update(
+ id="chap_xxxxxxxxxxxxx",
+ title="title",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ course_chapter = response.parse()
+ assert_matches_type(CourseChapter, course_chapter, path=["response"])
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ def test_streaming_response_update(self, client: Whop) -> None:
+ with client.course_chapters.with_streaming_response.update(
+ id="chap_xxxxxxxxxxxxx",
+ title="title",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ course_chapter = response.parse()
+ assert_matches_type(CourseChapter, course_chapter, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Prism 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.course_chapters.with_raw_response.update(
+ id="",
+ title="title",
+ )
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ def test_method_list(self, client: Whop) -> None:
+ course_chapter = client.course_chapters.list(
+ course_id="cors_xxxxxxxxxxxxx",
+ )
+ assert_matches_type(SyncCursorPage[CourseChapterListResponse], course_chapter, path=["response"])
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ def test_method_list_with_all_params(self, client: Whop) -> None:
+ course_chapter = client.course_chapters.list(
+ course_id="cors_xxxxxxxxxxxxx",
+ after="after",
+ before="before",
+ first=42,
+ last=42,
+ )
+ assert_matches_type(SyncCursorPage[CourseChapterListResponse], course_chapter, path=["response"])
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ def test_raw_response_list(self, client: Whop) -> None:
+ response = client.course_chapters.with_raw_response.list(
+ course_id="cors_xxxxxxxxxxxxx",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ course_chapter = response.parse()
+ assert_matches_type(SyncCursorPage[CourseChapterListResponse], course_chapter, path=["response"])
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ def test_streaming_response_list(self, client: Whop) -> None:
+ with client.course_chapters.with_streaming_response.list(
+ course_id="cors_xxxxxxxxxxxxx",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ course_chapter = response.parse()
+ assert_matches_type(SyncCursorPage[CourseChapterListResponse], course_chapter, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ def test_method_delete(self, client: Whop) -> None:
+ course_chapter = client.course_chapters.delete(
+ "chap_xxxxxxxxxxxxx",
+ )
+ assert_matches_type(CourseChapterDeleteResponse, course_chapter, path=["response"])
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ def test_raw_response_delete(self, client: Whop) -> None:
+ response = client.course_chapters.with_raw_response.delete(
+ "chap_xxxxxxxxxxxxx",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ course_chapter = response.parse()
+ assert_matches_type(CourseChapterDeleteResponse, course_chapter, path=["response"])
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ def test_streaming_response_delete(self, client: Whop) -> None:
+ with client.course_chapters.with_streaming_response.delete(
+ "chap_xxxxxxxxxxxxx",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ course_chapter = response.parse()
+ assert_matches_type(CourseChapterDeleteResponse, course_chapter, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Prism 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.course_chapters.with_raw_response.delete(
+ "",
+ )
+
+
+class TestAsyncCourseChapters:
+ parametrize = pytest.mark.parametrize(
+ "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
+ )
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ async def test_method_create(self, async_client: AsyncWhop) -> None:
+ course_chapter = await async_client.course_chapters.create(
+ course_id="cors_xxxxxxxxxxxxx",
+ )
+ assert_matches_type(CourseChapter, course_chapter, path=["response"])
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ async def test_method_create_with_all_params(self, async_client: AsyncWhop) -> None:
+ course_chapter = await async_client.course_chapters.create(
+ course_id="cors_xxxxxxxxxxxxx",
+ title="title",
+ )
+ assert_matches_type(CourseChapter, course_chapter, path=["response"])
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ async def test_raw_response_create(self, async_client: AsyncWhop) -> None:
+ response = await async_client.course_chapters.with_raw_response.create(
+ course_id="cors_xxxxxxxxxxxxx",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ course_chapter = await response.parse()
+ assert_matches_type(CourseChapter, course_chapter, path=["response"])
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ async def test_streaming_response_create(self, async_client: AsyncWhop) -> None:
+ async with async_client.course_chapters.with_streaming_response.create(
+ course_id="cors_xxxxxxxxxxxxx",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ course_chapter = await response.parse()
+ assert_matches_type(CourseChapter, course_chapter, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ async def test_method_retrieve(self, async_client: AsyncWhop) -> None:
+ course_chapter = await async_client.course_chapters.retrieve(
+ "chap_xxxxxxxxxxxxx",
+ )
+ assert_matches_type(CourseChapter, course_chapter, path=["response"])
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ async def test_raw_response_retrieve(self, async_client: AsyncWhop) -> None:
+ response = await async_client.course_chapters.with_raw_response.retrieve(
+ "chap_xxxxxxxxxxxxx",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ course_chapter = await response.parse()
+ assert_matches_type(CourseChapter, course_chapter, path=["response"])
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ async def test_streaming_response_retrieve(self, async_client: AsyncWhop) -> None:
+ async with async_client.course_chapters.with_streaming_response.retrieve(
+ "chap_xxxxxxxxxxxxx",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ course_chapter = await response.parse()
+ assert_matches_type(CourseChapter, course_chapter, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Prism 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.course_chapters.with_raw_response.retrieve(
+ "",
+ )
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ async def test_method_update(self, async_client: AsyncWhop) -> None:
+ course_chapter = await async_client.course_chapters.update(
+ id="chap_xxxxxxxxxxxxx",
+ title="title",
+ )
+ assert_matches_type(CourseChapter, course_chapter, path=["response"])
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ async def test_raw_response_update(self, async_client: AsyncWhop) -> None:
+ response = await async_client.course_chapters.with_raw_response.update(
+ id="chap_xxxxxxxxxxxxx",
+ title="title",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ course_chapter = await response.parse()
+ assert_matches_type(CourseChapter, course_chapter, path=["response"])
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ async def test_streaming_response_update(self, async_client: AsyncWhop) -> None:
+ async with async_client.course_chapters.with_streaming_response.update(
+ id="chap_xxxxxxxxxxxxx",
+ title="title",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ course_chapter = await response.parse()
+ assert_matches_type(CourseChapter, course_chapter, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Prism 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.course_chapters.with_raw_response.update(
+ id="",
+ title="title",
+ )
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ async def test_method_list(self, async_client: AsyncWhop) -> None:
+ course_chapter = await async_client.course_chapters.list(
+ course_id="cors_xxxxxxxxxxxxx",
+ )
+ assert_matches_type(AsyncCursorPage[CourseChapterListResponse], course_chapter, path=["response"])
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ async def test_method_list_with_all_params(self, async_client: AsyncWhop) -> None:
+ course_chapter = await async_client.course_chapters.list(
+ course_id="cors_xxxxxxxxxxxxx",
+ after="after",
+ before="before",
+ first=42,
+ last=42,
+ )
+ assert_matches_type(AsyncCursorPage[CourseChapterListResponse], course_chapter, path=["response"])
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ async def test_raw_response_list(self, async_client: AsyncWhop) -> None:
+ response = await async_client.course_chapters.with_raw_response.list(
+ course_id="cors_xxxxxxxxxxxxx",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ course_chapter = await response.parse()
+ assert_matches_type(AsyncCursorPage[CourseChapterListResponse], course_chapter, path=["response"])
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ async def test_streaming_response_list(self, async_client: AsyncWhop) -> None:
+ async with async_client.course_chapters.with_streaming_response.list(
+ course_id="cors_xxxxxxxxxxxxx",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ course_chapter = await response.parse()
+ assert_matches_type(AsyncCursorPage[CourseChapterListResponse], course_chapter, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ async def test_method_delete(self, async_client: AsyncWhop) -> None:
+ course_chapter = await async_client.course_chapters.delete(
+ "chap_xxxxxxxxxxxxx",
+ )
+ assert_matches_type(CourseChapterDeleteResponse, course_chapter, path=["response"])
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ async def test_raw_response_delete(self, async_client: AsyncWhop) -> None:
+ response = await async_client.course_chapters.with_raw_response.delete(
+ "chap_xxxxxxxxxxxxx",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ course_chapter = await response.parse()
+ assert_matches_type(CourseChapterDeleteResponse, course_chapter, path=["response"])
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ async def test_streaming_response_delete(self, async_client: AsyncWhop) -> None:
+ async with async_client.course_chapters.with_streaming_response.delete(
+ "chap_xxxxxxxxxxxxx",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ course_chapter = await response.parse()
+ assert_matches_type(CourseChapterDeleteResponse, course_chapter, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Prism 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.course_chapters.with_raw_response.delete(
+ "",
+ )
diff --git a/tests/api_resources/test_course_lessons.py b/tests/api_resources/test_course_lessons.py
new file mode 100644
index 00000000..dfcb126a
--- /dev/null
+++ b/tests/api_resources/test_course_lessons.py
@@ -0,0 +1,547 @@
+# 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 (
+ Lesson,
+ CourseLessonListResponse,
+ CourseLessonDeleteResponse,
+)
+from whop_sdk.pagination import SyncCursorPage, AsyncCursorPage
+
+base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
+
+
+class TestCourseLessons:
+ parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ def test_method_create(self, client: Whop) -> None:
+ course_lesson = client.course_lessons.create(
+ chapter_id="chap_xxxxxxxxxxxxx",
+ lesson_type="text",
+ )
+ assert_matches_type(Lesson, course_lesson, path=["response"])
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ def test_method_create_with_all_params(self, client: Whop) -> None:
+ course_lesson = client.course_lessons.create(
+ chapter_id="chap_xxxxxxxxxxxxx",
+ lesson_type="text",
+ content="content",
+ days_from_course_start_until_unlock=42,
+ title="title",
+ )
+ assert_matches_type(Lesson, course_lesson, path=["response"])
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ def test_raw_response_create(self, client: Whop) -> None:
+ response = client.course_lessons.with_raw_response.create(
+ chapter_id="chap_xxxxxxxxxxxxx",
+ lesson_type="text",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ course_lesson = response.parse()
+ assert_matches_type(Lesson, course_lesson, path=["response"])
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ def test_streaming_response_create(self, client: Whop) -> None:
+ with client.course_lessons.with_streaming_response.create(
+ chapter_id="chap_xxxxxxxxxxxxx",
+ lesson_type="text",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ course_lesson = response.parse()
+ assert_matches_type(Lesson, course_lesson, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ def test_method_retrieve(self, client: Whop) -> None:
+ course_lesson = client.course_lessons.retrieve(
+ "lesn_xxxxxxxxxxxxx",
+ )
+ assert_matches_type(Lesson, course_lesson, path=["response"])
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ def test_raw_response_retrieve(self, client: Whop) -> None:
+ response = client.course_lessons.with_raw_response.retrieve(
+ "lesn_xxxxxxxxxxxxx",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ course_lesson = response.parse()
+ assert_matches_type(Lesson, course_lesson, path=["response"])
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ def test_streaming_response_retrieve(self, client: Whop) -> None:
+ with client.course_lessons.with_streaming_response.retrieve(
+ "lesn_xxxxxxxxxxxxx",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ course_lesson = response.parse()
+ assert_matches_type(Lesson, course_lesson, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Prism 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.course_lessons.with_raw_response.retrieve(
+ "",
+ )
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ def test_method_update(self, client: Whop) -> None:
+ course_lesson = client.course_lessons.update(
+ id="lesn_xxxxxxxxxxxxx",
+ )
+ assert_matches_type(Lesson, course_lesson, path=["response"])
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ def test_method_update_with_all_params(self, client: Whop) -> None:
+ course_lesson = client.course_lessons.update(
+ id="lesn_xxxxxxxxxxxxx",
+ assessment_questions=[
+ {
+ "correct_answer": "correct_answer",
+ "question_text": "question_text",
+ "question_type": "short_answer",
+ "id": "id",
+ "image": {
+ "id": "id",
+ "direct_upload_id": "direct_upload_id",
+ },
+ "options": [
+ {
+ "is_correct": True,
+ "option_text": "option_text",
+ "id": "id",
+ }
+ ],
+ }
+ ],
+ attachments=[
+ {
+ "id": "id",
+ "direct_upload_id": "direct_upload_id",
+ }
+ ],
+ content="content",
+ days_from_course_start_until_unlock=42,
+ lesson_type="text",
+ main_pdf={
+ "id": "id",
+ "direct_upload_id": "direct_upload_id",
+ },
+ mux_asset_id="mux_xxxxxxxxxxxxxx",
+ title="title",
+ visibility="visible",
+ )
+ assert_matches_type(Lesson, course_lesson, path=["response"])
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ def test_raw_response_update(self, client: Whop) -> None:
+ response = client.course_lessons.with_raw_response.update(
+ id="lesn_xxxxxxxxxxxxx",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ course_lesson = response.parse()
+ assert_matches_type(Lesson, course_lesson, path=["response"])
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ def test_streaming_response_update(self, client: Whop) -> None:
+ with client.course_lessons.with_streaming_response.update(
+ id="lesn_xxxxxxxxxxxxx",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ course_lesson = response.parse()
+ assert_matches_type(Lesson, course_lesson, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Prism 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.course_lessons.with_raw_response.update(
+ id="",
+ )
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ def test_method_list(self, client: Whop) -> None:
+ course_lesson = client.course_lessons.list()
+ assert_matches_type(SyncCursorPage[CourseLessonListResponse], course_lesson, path=["response"])
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ def test_method_list_with_all_params(self, client: Whop) -> None:
+ course_lesson = client.course_lessons.list(
+ after="after",
+ before="before",
+ chapter_id="chap_xxxxxxxxxxxxx",
+ course_id="cors_xxxxxxxxxxxxx",
+ first=42,
+ last=42,
+ )
+ assert_matches_type(SyncCursorPage[CourseLessonListResponse], course_lesson, path=["response"])
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ def test_raw_response_list(self, client: Whop) -> None:
+ response = client.course_lessons.with_raw_response.list()
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ course_lesson = response.parse()
+ assert_matches_type(SyncCursorPage[CourseLessonListResponse], course_lesson, path=["response"])
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ def test_streaming_response_list(self, client: Whop) -> None:
+ with client.course_lessons.with_streaming_response.list() as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ course_lesson = response.parse()
+ assert_matches_type(SyncCursorPage[CourseLessonListResponse], course_lesson, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ def test_method_delete(self, client: Whop) -> None:
+ course_lesson = client.course_lessons.delete(
+ "lesn_xxxxxxxxxxxxx",
+ )
+ assert_matches_type(CourseLessonDeleteResponse, course_lesson, path=["response"])
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ def test_raw_response_delete(self, client: Whop) -> None:
+ response = client.course_lessons.with_raw_response.delete(
+ "lesn_xxxxxxxxxxxxx",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ course_lesson = response.parse()
+ assert_matches_type(CourseLessonDeleteResponse, course_lesson, path=["response"])
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ def test_streaming_response_delete(self, client: Whop) -> None:
+ with client.course_lessons.with_streaming_response.delete(
+ "lesn_xxxxxxxxxxxxx",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ course_lesson = response.parse()
+ assert_matches_type(CourseLessonDeleteResponse, course_lesson, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Prism 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.course_lessons.with_raw_response.delete(
+ "",
+ )
+
+
+class TestAsyncCourseLessons:
+ parametrize = pytest.mark.parametrize(
+ "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
+ )
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ async def test_method_create(self, async_client: AsyncWhop) -> None:
+ course_lesson = await async_client.course_lessons.create(
+ chapter_id="chap_xxxxxxxxxxxxx",
+ lesson_type="text",
+ )
+ assert_matches_type(Lesson, course_lesson, path=["response"])
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ async def test_method_create_with_all_params(self, async_client: AsyncWhop) -> None:
+ course_lesson = await async_client.course_lessons.create(
+ chapter_id="chap_xxxxxxxxxxxxx",
+ lesson_type="text",
+ content="content",
+ days_from_course_start_until_unlock=42,
+ title="title",
+ )
+ assert_matches_type(Lesson, course_lesson, path=["response"])
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ async def test_raw_response_create(self, async_client: AsyncWhop) -> None:
+ response = await async_client.course_lessons.with_raw_response.create(
+ chapter_id="chap_xxxxxxxxxxxxx",
+ lesson_type="text",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ course_lesson = await response.parse()
+ assert_matches_type(Lesson, course_lesson, path=["response"])
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ async def test_streaming_response_create(self, async_client: AsyncWhop) -> None:
+ async with async_client.course_lessons.with_streaming_response.create(
+ chapter_id="chap_xxxxxxxxxxxxx",
+ lesson_type="text",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ course_lesson = await response.parse()
+ assert_matches_type(Lesson, course_lesson, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ async def test_method_retrieve(self, async_client: AsyncWhop) -> None:
+ course_lesson = await async_client.course_lessons.retrieve(
+ "lesn_xxxxxxxxxxxxx",
+ )
+ assert_matches_type(Lesson, course_lesson, path=["response"])
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ async def test_raw_response_retrieve(self, async_client: AsyncWhop) -> None:
+ response = await async_client.course_lessons.with_raw_response.retrieve(
+ "lesn_xxxxxxxxxxxxx",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ course_lesson = await response.parse()
+ assert_matches_type(Lesson, course_lesson, path=["response"])
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ async def test_streaming_response_retrieve(self, async_client: AsyncWhop) -> None:
+ async with async_client.course_lessons.with_streaming_response.retrieve(
+ "lesn_xxxxxxxxxxxxx",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ course_lesson = await response.parse()
+ assert_matches_type(Lesson, course_lesson, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Prism 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.course_lessons.with_raw_response.retrieve(
+ "",
+ )
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ async def test_method_update(self, async_client: AsyncWhop) -> None:
+ course_lesson = await async_client.course_lessons.update(
+ id="lesn_xxxxxxxxxxxxx",
+ )
+ assert_matches_type(Lesson, course_lesson, path=["response"])
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ async def test_method_update_with_all_params(self, async_client: AsyncWhop) -> None:
+ course_lesson = await async_client.course_lessons.update(
+ id="lesn_xxxxxxxxxxxxx",
+ assessment_questions=[
+ {
+ "correct_answer": "correct_answer",
+ "question_text": "question_text",
+ "question_type": "short_answer",
+ "id": "id",
+ "image": {
+ "id": "id",
+ "direct_upload_id": "direct_upload_id",
+ },
+ "options": [
+ {
+ "is_correct": True,
+ "option_text": "option_text",
+ "id": "id",
+ }
+ ],
+ }
+ ],
+ attachments=[
+ {
+ "id": "id",
+ "direct_upload_id": "direct_upload_id",
+ }
+ ],
+ content="content",
+ days_from_course_start_until_unlock=42,
+ lesson_type="text",
+ main_pdf={
+ "id": "id",
+ "direct_upload_id": "direct_upload_id",
+ },
+ mux_asset_id="mux_xxxxxxxxxxxxxx",
+ title="title",
+ visibility="visible",
+ )
+ assert_matches_type(Lesson, course_lesson, path=["response"])
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ async def test_raw_response_update(self, async_client: AsyncWhop) -> None:
+ response = await async_client.course_lessons.with_raw_response.update(
+ id="lesn_xxxxxxxxxxxxx",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ course_lesson = await response.parse()
+ assert_matches_type(Lesson, course_lesson, path=["response"])
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ async def test_streaming_response_update(self, async_client: AsyncWhop) -> None:
+ async with async_client.course_lessons.with_streaming_response.update(
+ id="lesn_xxxxxxxxxxxxx",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ course_lesson = await response.parse()
+ assert_matches_type(Lesson, course_lesson, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Prism 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.course_lessons.with_raw_response.update(
+ id="",
+ )
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ async def test_method_list(self, async_client: AsyncWhop) -> None:
+ course_lesson = await async_client.course_lessons.list()
+ assert_matches_type(AsyncCursorPage[CourseLessonListResponse], course_lesson, path=["response"])
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ async def test_method_list_with_all_params(self, async_client: AsyncWhop) -> None:
+ course_lesson = await async_client.course_lessons.list(
+ after="after",
+ before="before",
+ chapter_id="chap_xxxxxxxxxxxxx",
+ course_id="cors_xxxxxxxxxxxxx",
+ first=42,
+ last=42,
+ )
+ assert_matches_type(AsyncCursorPage[CourseLessonListResponse], course_lesson, path=["response"])
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ async def test_raw_response_list(self, async_client: AsyncWhop) -> None:
+ response = await async_client.course_lessons.with_raw_response.list()
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ course_lesson = await response.parse()
+ assert_matches_type(AsyncCursorPage[CourseLessonListResponse], course_lesson, path=["response"])
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ async def test_streaming_response_list(self, async_client: AsyncWhop) -> None:
+ async with async_client.course_lessons.with_streaming_response.list() as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ course_lesson = await response.parse()
+ assert_matches_type(AsyncCursorPage[CourseLessonListResponse], course_lesson, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ async def test_method_delete(self, async_client: AsyncWhop) -> None:
+ course_lesson = await async_client.course_lessons.delete(
+ "lesn_xxxxxxxxxxxxx",
+ )
+ assert_matches_type(CourseLessonDeleteResponse, course_lesson, path=["response"])
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ async def test_raw_response_delete(self, async_client: AsyncWhop) -> None:
+ response = await async_client.course_lessons.with_raw_response.delete(
+ "lesn_xxxxxxxxxxxxx",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ course_lesson = await response.parse()
+ assert_matches_type(CourseLessonDeleteResponse, course_lesson, path=["response"])
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ async def test_streaming_response_delete(self, async_client: AsyncWhop) -> None:
+ async with async_client.course_lessons.with_streaming_response.delete(
+ "lesn_xxxxxxxxxxxxx",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ course_lesson = await response.parse()
+ assert_matches_type(CourseLessonDeleteResponse, course_lesson, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Prism 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.course_lessons.with_raw_response.delete(
+ "",
+ )
diff --git a/tests/api_resources/test_courses.py b/tests/api_resources/test_courses.py
new file mode 100644
index 00000000..9d827d67
--- /dev/null
+++ b/tests/api_resources/test_courses.py
@@ -0,0 +1,535 @@
+# 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 (
+ Course,
+ CourseListResponse,
+ CourseDeleteResponse,
+)
+from whop_sdk.pagination import SyncCursorPage, AsyncCursorPage
+
+base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
+
+
+class TestCourses:
+ parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ def test_method_create(self, client: Whop) -> None:
+ course = client.courses.create(
+ experience_id="exp_xxxxxxxxxxxxxx",
+ title="title",
+ )
+ assert_matches_type(Course, course, path=["response"])
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ def test_method_create_with_all_params(self, client: Whop) -> None:
+ course = client.courses.create(
+ experience_id="exp_xxxxxxxxxxxxxx",
+ title="title",
+ cover_image="cover_image",
+ tagline="tagline",
+ thumbnail={
+ "id": "id",
+ "direct_upload_id": "direct_upload_id",
+ },
+ )
+ assert_matches_type(Course, course, path=["response"])
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ def test_raw_response_create(self, client: Whop) -> None:
+ response = client.courses.with_raw_response.create(
+ experience_id="exp_xxxxxxxxxxxxxx",
+ title="title",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ course = response.parse()
+ assert_matches_type(Course, course, path=["response"])
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ def test_streaming_response_create(self, client: Whop) -> None:
+ with client.courses.with_streaming_response.create(
+ experience_id="exp_xxxxxxxxxxxxxx",
+ title="title",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ course = response.parse()
+ assert_matches_type(Course, course, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ def test_method_retrieve(self, client: Whop) -> None:
+ course = client.courses.retrieve(
+ "cors_xxxxxxxxxxxxx",
+ )
+ assert_matches_type(Course, course, path=["response"])
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ def test_raw_response_retrieve(self, client: Whop) -> None:
+ response = client.courses.with_raw_response.retrieve(
+ "cors_xxxxxxxxxxxxx",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ course = response.parse()
+ assert_matches_type(Course, course, path=["response"])
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ def test_streaming_response_retrieve(self, client: Whop) -> None:
+ with client.courses.with_streaming_response.retrieve(
+ "cors_xxxxxxxxxxxxx",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ course = response.parse()
+ assert_matches_type(Course, course, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Prism 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.courses.with_raw_response.retrieve(
+ "",
+ )
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ def test_method_update(self, client: Whop) -> None:
+ course = client.courses.update(
+ id="cors_xxxxxxxxxxxxx",
+ )
+ assert_matches_type(Course, course, path=["response"])
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ def test_method_update_with_all_params(self, client: Whop) -> None:
+ course = client.courses.update(
+ id="cors_xxxxxxxxxxxxx",
+ certificate_after_completion_enabled=True,
+ chapters=[
+ {
+ "id": "id",
+ "order": 42,
+ "title": "title",
+ "lessons": [
+ {
+ "id": "id",
+ "chapter_id": "chap_xxxxxxxxxxxxx",
+ "order": 42,
+ "title": "title",
+ }
+ ],
+ }
+ ],
+ cover_image="cover_image",
+ description="description",
+ language="en",
+ require_completing_lessons_in_order=True,
+ tagline="tagline",
+ thumbnail={
+ "id": "id",
+ "direct_upload_id": "direct_upload_id",
+ },
+ title="title",
+ )
+ assert_matches_type(Course, course, path=["response"])
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ def test_raw_response_update(self, client: Whop) -> None:
+ response = client.courses.with_raw_response.update(
+ id="cors_xxxxxxxxxxxxx",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ course = response.parse()
+ assert_matches_type(Course, course, path=["response"])
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ def test_streaming_response_update(self, client: Whop) -> None:
+ with client.courses.with_streaming_response.update(
+ id="cors_xxxxxxxxxxxxx",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ course = response.parse()
+ assert_matches_type(Course, course, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Prism 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.courses.with_raw_response.update(
+ id="",
+ )
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ def test_method_list(self, client: Whop) -> None:
+ course = client.courses.list()
+ assert_matches_type(SyncCursorPage[CourseListResponse], course, path=["response"])
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ def test_method_list_with_all_params(self, client: Whop) -> None:
+ course = client.courses.list(
+ after="after",
+ before="before",
+ company_id="biz_xxxxxxxxxxxxxx",
+ experience_id="exp_xxxxxxxxxxxxxx",
+ first=42,
+ last=42,
+ )
+ assert_matches_type(SyncCursorPage[CourseListResponse], course, path=["response"])
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ def test_raw_response_list(self, client: Whop) -> None:
+ response = client.courses.with_raw_response.list()
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ course = response.parse()
+ assert_matches_type(SyncCursorPage[CourseListResponse], course, path=["response"])
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ def test_streaming_response_list(self, client: Whop) -> None:
+ with client.courses.with_streaming_response.list() as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ course = response.parse()
+ assert_matches_type(SyncCursorPage[CourseListResponse], course, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ def test_method_delete(self, client: Whop) -> None:
+ course = client.courses.delete(
+ "cors_xxxxxxxxxxxxx",
+ )
+ assert_matches_type(CourseDeleteResponse, course, path=["response"])
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ def test_raw_response_delete(self, client: Whop) -> None:
+ response = client.courses.with_raw_response.delete(
+ "cors_xxxxxxxxxxxxx",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ course = response.parse()
+ assert_matches_type(CourseDeleteResponse, course, path=["response"])
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ def test_streaming_response_delete(self, client: Whop) -> None:
+ with client.courses.with_streaming_response.delete(
+ "cors_xxxxxxxxxxxxx",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ course = response.parse()
+ assert_matches_type(CourseDeleteResponse, course, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Prism 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.courses.with_raw_response.delete(
+ "",
+ )
+
+
+class TestAsyncCourses:
+ parametrize = pytest.mark.parametrize(
+ "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
+ )
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ async def test_method_create(self, async_client: AsyncWhop) -> None:
+ course = await async_client.courses.create(
+ experience_id="exp_xxxxxxxxxxxxxx",
+ title="title",
+ )
+ assert_matches_type(Course, course, path=["response"])
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ async def test_method_create_with_all_params(self, async_client: AsyncWhop) -> None:
+ course = await async_client.courses.create(
+ experience_id="exp_xxxxxxxxxxxxxx",
+ title="title",
+ cover_image="cover_image",
+ tagline="tagline",
+ thumbnail={
+ "id": "id",
+ "direct_upload_id": "direct_upload_id",
+ },
+ )
+ assert_matches_type(Course, course, path=["response"])
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ async def test_raw_response_create(self, async_client: AsyncWhop) -> None:
+ response = await async_client.courses.with_raw_response.create(
+ experience_id="exp_xxxxxxxxxxxxxx",
+ title="title",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ course = await response.parse()
+ assert_matches_type(Course, course, path=["response"])
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ async def test_streaming_response_create(self, async_client: AsyncWhop) -> None:
+ async with async_client.courses.with_streaming_response.create(
+ experience_id="exp_xxxxxxxxxxxxxx",
+ title="title",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ course = await response.parse()
+ assert_matches_type(Course, course, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ async def test_method_retrieve(self, async_client: AsyncWhop) -> None:
+ course = await async_client.courses.retrieve(
+ "cors_xxxxxxxxxxxxx",
+ )
+ assert_matches_type(Course, course, path=["response"])
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ async def test_raw_response_retrieve(self, async_client: AsyncWhop) -> None:
+ response = await async_client.courses.with_raw_response.retrieve(
+ "cors_xxxxxxxxxxxxx",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ course = await response.parse()
+ assert_matches_type(Course, course, path=["response"])
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ async def test_streaming_response_retrieve(self, async_client: AsyncWhop) -> None:
+ async with async_client.courses.with_streaming_response.retrieve(
+ "cors_xxxxxxxxxxxxx",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ course = await response.parse()
+ assert_matches_type(Course, course, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Prism 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.courses.with_raw_response.retrieve(
+ "",
+ )
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ async def test_method_update(self, async_client: AsyncWhop) -> None:
+ course = await async_client.courses.update(
+ id="cors_xxxxxxxxxxxxx",
+ )
+ assert_matches_type(Course, course, path=["response"])
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ async def test_method_update_with_all_params(self, async_client: AsyncWhop) -> None:
+ course = await async_client.courses.update(
+ id="cors_xxxxxxxxxxxxx",
+ certificate_after_completion_enabled=True,
+ chapters=[
+ {
+ "id": "id",
+ "order": 42,
+ "title": "title",
+ "lessons": [
+ {
+ "id": "id",
+ "chapter_id": "chap_xxxxxxxxxxxxx",
+ "order": 42,
+ "title": "title",
+ }
+ ],
+ }
+ ],
+ cover_image="cover_image",
+ description="description",
+ language="en",
+ require_completing_lessons_in_order=True,
+ tagline="tagline",
+ thumbnail={
+ "id": "id",
+ "direct_upload_id": "direct_upload_id",
+ },
+ title="title",
+ )
+ assert_matches_type(Course, course, path=["response"])
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ async def test_raw_response_update(self, async_client: AsyncWhop) -> None:
+ response = await async_client.courses.with_raw_response.update(
+ id="cors_xxxxxxxxxxxxx",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ course = await response.parse()
+ assert_matches_type(Course, course, path=["response"])
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ async def test_streaming_response_update(self, async_client: AsyncWhop) -> None:
+ async with async_client.courses.with_streaming_response.update(
+ id="cors_xxxxxxxxxxxxx",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ course = await response.parse()
+ assert_matches_type(Course, course, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Prism 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.courses.with_raw_response.update(
+ id="",
+ )
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ async def test_method_list(self, async_client: AsyncWhop) -> None:
+ course = await async_client.courses.list()
+ assert_matches_type(AsyncCursorPage[CourseListResponse], course, path=["response"])
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ async def test_method_list_with_all_params(self, async_client: AsyncWhop) -> None:
+ course = await async_client.courses.list(
+ after="after",
+ before="before",
+ company_id="biz_xxxxxxxxxxxxxx",
+ experience_id="exp_xxxxxxxxxxxxxx",
+ first=42,
+ last=42,
+ )
+ assert_matches_type(AsyncCursorPage[CourseListResponse], course, path=["response"])
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ async def test_raw_response_list(self, async_client: AsyncWhop) -> None:
+ response = await async_client.courses.with_raw_response.list()
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ course = await response.parse()
+ assert_matches_type(AsyncCursorPage[CourseListResponse], course, path=["response"])
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ async def test_streaming_response_list(self, async_client: AsyncWhop) -> None:
+ async with async_client.courses.with_streaming_response.list() as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ course = await response.parse()
+ assert_matches_type(AsyncCursorPage[CourseListResponse], course, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ async def test_method_delete(self, async_client: AsyncWhop) -> None:
+ course = await async_client.courses.delete(
+ "cors_xxxxxxxxxxxxx",
+ )
+ assert_matches_type(CourseDeleteResponse, course, path=["response"])
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ async def test_raw_response_delete(self, async_client: AsyncWhop) -> None:
+ response = await async_client.courses.with_raw_response.delete(
+ "cors_xxxxxxxxxxxxx",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ course = await response.parse()
+ assert_matches_type(CourseDeleteResponse, course, path=["response"])
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ async def test_streaming_response_delete(self, async_client: AsyncWhop) -> None:
+ async with async_client.courses.with_streaming_response.delete(
+ "cors_xxxxxxxxxxxxx",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ course = await response.parse()
+ assert_matches_type(CourseDeleteResponse, course, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Prism 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.courses.with_raw_response.delete(
+ "",
+ )
diff --git a/tests/api_resources/test_experiences.py b/tests/api_resources/test_experiences.py
index 9112113c..a57c7916 100644
--- a/tests/api_resources/test_experiences.py
+++ b/tests/api_resources/test_experiences.py
@@ -352,6 +352,57 @@ def test_path_params_detach(self, client: Whop) -> None:
product_id="prod_xxxxxxxxxxxxx",
)
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ def test_method_duplicate(self, client: Whop) -> None:
+ experience = client.experiences.duplicate(
+ id="exp_xxxxxxxxxxxxxx",
+ )
+ assert_matches_type(Experience, experience, path=["response"])
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ def test_method_duplicate_with_all_params(self, client: Whop) -> None:
+ experience = client.experiences.duplicate(
+ id="exp_xxxxxxxxxxxxxx",
+ name="name",
+ )
+ assert_matches_type(Experience, experience, path=["response"])
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ def test_raw_response_duplicate(self, client: Whop) -> None:
+ response = client.experiences.with_raw_response.duplicate(
+ id="exp_xxxxxxxxxxxxxx",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ experience = response.parse()
+ assert_matches_type(Experience, experience, path=["response"])
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ def test_streaming_response_duplicate(self, client: Whop) -> None:
+ with client.experiences.with_streaming_response.duplicate(
+ id="exp_xxxxxxxxxxxxxx",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ experience = response.parse()
+ assert_matches_type(Experience, experience, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ def test_path_params_duplicate(self, client: Whop) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"):
+ client.experiences.with_raw_response.duplicate(
+ id="",
+ )
+
class TestAsyncExperiences:
parametrize = pytest.mark.parametrize(
@@ -687,3 +738,54 @@ async def test_path_params_detach(self, async_client: AsyncWhop) -> None:
id="",
product_id="prod_xxxxxxxxxxxxx",
)
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ async def test_method_duplicate(self, async_client: AsyncWhop) -> None:
+ experience = await async_client.experiences.duplicate(
+ id="exp_xxxxxxxxxxxxxx",
+ )
+ assert_matches_type(Experience, experience, path=["response"])
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ async def test_method_duplicate_with_all_params(self, async_client: AsyncWhop) -> None:
+ experience = await async_client.experiences.duplicate(
+ id="exp_xxxxxxxxxxxxxx",
+ name="name",
+ )
+ assert_matches_type(Experience, experience, path=["response"])
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ async def test_raw_response_duplicate(self, async_client: AsyncWhop) -> None:
+ response = await async_client.experiences.with_raw_response.duplicate(
+ id="exp_xxxxxxxxxxxxxx",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ experience = await response.parse()
+ assert_matches_type(Experience, experience, path=["response"])
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ async def test_streaming_response_duplicate(self, async_client: AsyncWhop) -> None:
+ async with async_client.experiences.with_streaming_response.duplicate(
+ id="exp_xxxxxxxxxxxxxx",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ experience = await response.parse()
+ assert_matches_type(Experience, experience, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ async def test_path_params_duplicate(self, async_client: AsyncWhop) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"):
+ await async_client.experiences.with_raw_response.duplicate(
+ id="",
+ )
diff --git a/tests/api_resources/test_forum_posts.py b/tests/api_resources/test_forum_posts.py
index 9487c0de..e0ef2933 100644
--- a/tests/api_resources/test_forum_posts.py
+++ b/tests/api_resources/test_forum_posts.py
@@ -9,7 +9,9 @@
from whop_sdk import Whop, AsyncWhop
from tests.utils import assert_matches_type
-from whop_sdk.types import ForumPostListResponse
+from whop_sdk.types import (
+ ForumPostListResponse,
+)
from whop_sdk.pagination import SyncCursorPage, AsyncCursorPage
from whop_sdk.types.shared import ForumPost
@@ -124,6 +126,65 @@ def test_path_params_retrieve(self, client: Whop) -> None:
"",
)
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ def test_method_update(self, client: Whop) -> None:
+ forum_post = client.forum_posts.update(
+ id="id",
+ )
+ assert_matches_type(ForumPost, forum_post, path=["response"])
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ def test_method_update_with_all_params(self, client: Whop) -> None:
+ forum_post = client.forum_posts.update(
+ id="id",
+ attachments=[
+ {
+ "id": "id",
+ "direct_upload_id": "direct_upload_id",
+ }
+ ],
+ content="content",
+ is_pinned=True,
+ title="title",
+ )
+ assert_matches_type(ForumPost, forum_post, path=["response"])
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ def test_raw_response_update(self, client: Whop) -> None:
+ response = client.forum_posts.with_raw_response.update(
+ id="id",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ forum_post = response.parse()
+ assert_matches_type(ForumPost, forum_post, path=["response"])
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ def test_streaming_response_update(self, client: Whop) -> None:
+ with client.forum_posts.with_streaming_response.update(
+ id="id",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ forum_post = response.parse()
+ assert_matches_type(ForumPost, forum_post, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Prism 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.forum_posts.with_raw_response.update(
+ id="",
+ )
+
@pytest.mark.skip(reason="Prism tests are disabled")
@parametrize
def test_method_list(self, client: Whop) -> None:
@@ -283,6 +344,65 @@ async def test_path_params_retrieve(self, async_client: AsyncWhop) -> None:
"",
)
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ async def test_method_update(self, async_client: AsyncWhop) -> None:
+ forum_post = await async_client.forum_posts.update(
+ id="id",
+ )
+ assert_matches_type(ForumPost, forum_post, path=["response"])
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ async def test_method_update_with_all_params(self, async_client: AsyncWhop) -> None:
+ forum_post = await async_client.forum_posts.update(
+ id="id",
+ attachments=[
+ {
+ "id": "id",
+ "direct_upload_id": "direct_upload_id",
+ }
+ ],
+ content="content",
+ is_pinned=True,
+ title="title",
+ )
+ assert_matches_type(ForumPost, forum_post, path=["response"])
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ async def test_raw_response_update(self, async_client: AsyncWhop) -> None:
+ response = await async_client.forum_posts.with_raw_response.update(
+ id="id",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ forum_post = await response.parse()
+ assert_matches_type(ForumPost, forum_post, path=["response"])
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ async def test_streaming_response_update(self, async_client: AsyncWhop) -> None:
+ async with async_client.forum_posts.with_streaming_response.update(
+ id="id",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ forum_post = await response.parse()
+ assert_matches_type(ForumPost, forum_post, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Prism 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.forum_posts.with_raw_response.update(
+ id="",
+ )
+
@pytest.mark.skip(reason="Prism tests are disabled")
@parametrize
async def test_method_list(self, async_client: AsyncWhop) -> None:
diff --git a/tests/api_resources/test_members.py b/tests/api_resources/test_members.py
index fc600750..05887df1 100644
--- a/tests/api_resources/test_members.py
+++ b/tests/api_resources/test_members.py
@@ -89,6 +89,7 @@ def test_method_list_with_all_params(self, client: Whop) -> None:
promo_code_ids=["string"],
query="query",
statuses=["drafted"],
+ user_ids=["string"],
)
assert_matches_type(SyncCursorPage[MemberListResponse], member, path=["response"])
@@ -194,6 +195,7 @@ async def test_method_list_with_all_params(self, async_client: AsyncWhop) -> Non
promo_code_ids=["string"],
query="query",
statuses=["drafted"],
+ user_ids=["string"],
)
assert_matches_type(AsyncCursorPage[MemberListResponse], member, path=["response"])
diff --git a/tests/api_resources/test_memberships.py b/tests/api_resources/test_memberships.py
index f0edf0ab..7423dd0e 100644
--- a/tests/api_resources/test_memberships.py
+++ b/tests/api_resources/test_memberships.py
@@ -118,20 +118,18 @@ def test_path_params_update(self, client: Whop) -> None:
@pytest.mark.skip(reason="Prism tests are disabled")
@parametrize
def test_method_list(self, client: Whop) -> None:
- membership = client.memberships.list(
- company_id="biz_xxxxxxxxxxxxxx",
- )
+ membership = client.memberships.list()
assert_matches_type(SyncCursorPage[MembershipListResponse], membership, path=["response"])
@pytest.mark.skip(reason="Prism tests are disabled")
@parametrize
def test_method_list_with_all_params(self, client: Whop) -> None:
membership = client.memberships.list(
- company_id="biz_xxxxxxxxxxxxxx",
access_pass_ids=["string"],
after="after",
before="before",
cancel_options=["too_expensive"],
+ company_id="biz_xxxxxxxxxxxxxx",
created_after=parse_datetime("2023-12-01T05:00:00.401Z"),
created_before=parse_datetime("2023-12-01T05:00:00.401Z"),
direction="asc",
@@ -141,15 +139,14 @@ def test_method_list_with_all_params(self, client: Whop) -> None:
plan_ids=["string"],
promo_code_ids=["string"],
statuses=["trialing"],
+ user_ids=["string"],
)
assert_matches_type(SyncCursorPage[MembershipListResponse], membership, path=["response"])
@pytest.mark.skip(reason="Prism tests are disabled")
@parametrize
def test_raw_response_list(self, client: Whop) -> None:
- response = client.memberships.with_raw_response.list(
- company_id="biz_xxxxxxxxxxxxxx",
- )
+ response = client.memberships.with_raw_response.list()
assert response.is_closed is True
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -159,9 +156,7 @@ def test_raw_response_list(self, client: Whop) -> None:
@pytest.mark.skip(reason="Prism tests are disabled")
@parametrize
def test_streaming_response_list(self, client: Whop) -> None:
- with client.memberships.with_streaming_response.list(
- company_id="biz_xxxxxxxxxxxxxx",
- ) as response:
+ with client.memberships.with_streaming_response.list() as response:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -416,20 +411,18 @@ async def test_path_params_update(self, async_client: AsyncWhop) -> None:
@pytest.mark.skip(reason="Prism tests are disabled")
@parametrize
async def test_method_list(self, async_client: AsyncWhop) -> None:
- membership = await async_client.memberships.list(
- company_id="biz_xxxxxxxxxxxxxx",
- )
+ membership = await async_client.memberships.list()
assert_matches_type(AsyncCursorPage[MembershipListResponse], membership, path=["response"])
@pytest.mark.skip(reason="Prism tests are disabled")
@parametrize
async def test_method_list_with_all_params(self, async_client: AsyncWhop) -> None:
membership = await async_client.memberships.list(
- company_id="biz_xxxxxxxxxxxxxx",
access_pass_ids=["string"],
after="after",
before="before",
cancel_options=["too_expensive"],
+ company_id="biz_xxxxxxxxxxxxxx",
created_after=parse_datetime("2023-12-01T05:00:00.401Z"),
created_before=parse_datetime("2023-12-01T05:00:00.401Z"),
direction="asc",
@@ -439,15 +432,14 @@ async def test_method_list_with_all_params(self, async_client: AsyncWhop) -> Non
plan_ids=["string"],
promo_code_ids=["string"],
statuses=["trialing"],
+ user_ids=["string"],
)
assert_matches_type(AsyncCursorPage[MembershipListResponse], membership, path=["response"])
@pytest.mark.skip(reason="Prism tests are disabled")
@parametrize
async def test_raw_response_list(self, async_client: AsyncWhop) -> None:
- response = await async_client.memberships.with_raw_response.list(
- company_id="biz_xxxxxxxxxxxxxx",
- )
+ response = await async_client.memberships.with_raw_response.list()
assert response.is_closed is True
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -457,9 +449,7 @@ async def test_raw_response_list(self, async_client: AsyncWhop) -> None:
@pytest.mark.skip(reason="Prism tests are disabled")
@parametrize
async def test_streaming_response_list(self, async_client: AsyncWhop) -> None:
- async with async_client.memberships.with_streaming_response.list(
- company_id="biz_xxxxxxxxxxxxxx",
- ) as response:
+ async with async_client.memberships.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/api_resources/test_messages.py b/tests/api_resources/test_messages.py
index 4e79462c..17d4b01e 100644
--- a/tests/api_resources/test_messages.py
+++ b/tests/api_resources/test_messages.py
@@ -9,7 +9,9 @@
from whop_sdk import Whop, AsyncWhop
from tests.utils import assert_matches_type
-from whop_sdk.types import MessageListResponse
+from whop_sdk.types import (
+ MessageListResponse,
+)
from whop_sdk.pagination import SyncCursorPage, AsyncCursorPage
from whop_sdk.types.shared import Message
@@ -121,6 +123,64 @@ def test_path_params_retrieve(self, client: Whop) -> None:
"",
)
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ def test_method_update(self, client: Whop) -> None:
+ message = client.messages.update(
+ id="id",
+ )
+ assert_matches_type(Message, message, path=["response"])
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ def test_method_update_with_all_params(self, client: Whop) -> None:
+ message = client.messages.update(
+ id="id",
+ attachments=[
+ {
+ "id": "id",
+ "direct_upload_id": "direct_upload_id",
+ }
+ ],
+ content="content",
+ is_pinned=True,
+ )
+ assert_matches_type(Message, message, path=["response"])
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ def test_raw_response_update(self, client: Whop) -> None:
+ response = client.messages.with_raw_response.update(
+ id="id",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ message = response.parse()
+ assert_matches_type(Message, message, path=["response"])
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ def test_streaming_response_update(self, client: Whop) -> None:
+ with client.messages.with_streaming_response.update(
+ id="id",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ message = response.parse()
+ assert_matches_type(Message, message, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Prism 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.messages.with_raw_response.update(
+ id="",
+ )
+
@pytest.mark.skip(reason="Prism tests are disabled")
@parametrize
def test_method_list(self, client: Whop) -> None:
@@ -276,6 +336,64 @@ async def test_path_params_retrieve(self, async_client: AsyncWhop) -> None:
"",
)
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ async def test_method_update(self, async_client: AsyncWhop) -> None:
+ message = await async_client.messages.update(
+ id="id",
+ )
+ assert_matches_type(Message, message, path=["response"])
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ async def test_method_update_with_all_params(self, async_client: AsyncWhop) -> None:
+ message = await async_client.messages.update(
+ id="id",
+ attachments=[
+ {
+ "id": "id",
+ "direct_upload_id": "direct_upload_id",
+ }
+ ],
+ content="content",
+ is_pinned=True,
+ )
+ assert_matches_type(Message, message, path=["response"])
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ async def test_raw_response_update(self, async_client: AsyncWhop) -> None:
+ response = await async_client.messages.with_raw_response.update(
+ id="id",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ message = await response.parse()
+ assert_matches_type(Message, message, path=["response"])
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ async def test_streaming_response_update(self, async_client: AsyncWhop) -> None:
+ async with async_client.messages.with_streaming_response.update(
+ id="id",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ message = await response.parse()
+ assert_matches_type(Message, message, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Prism 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.messages.with_raw_response.update(
+ id="",
+ )
+
@pytest.mark.skip(reason="Prism tests are disabled")
@parametrize
async def test_method_list(self, async_client: AsyncWhop) -> None:
diff --git a/tests/api_resources/test_plans.py b/tests/api_resources/test_plans.py
index b86a579f..c77368a8 100644
--- a/tests/api_resources/test_plans.py
+++ b/tests/api_resources/test_plans.py
@@ -61,8 +61,12 @@ def test_method_create_with_all_params(self, client: Whop) -> None:
plan_type="renewal",
release_method="buy_now",
renewal_price=6.9,
+ stock=42,
+ strike_through_initial_price=6.9,
+ strike_through_renewal_price=6.9,
title="title",
trial_period_days=42,
+ unlimited_stock=True,
visibility="visible",
)
assert_matches_type(Plan, plan, path=["response"])
@@ -173,8 +177,12 @@ def test_method_update_with_all_params(self, client: Whop) -> None:
offer_cancel_discount=True,
override_tax_type="inclusive",
renewal_price=6.9,
+ stock=42,
+ strike_through_initial_price=6.9,
+ strike_through_renewal_price=6.9,
title="title",
trial_period_days=42,
+ unlimited_stock=True,
visibility="visible",
)
assert_matches_type(Plan, plan, path=["response"])
@@ -352,8 +360,12 @@ async def test_method_create_with_all_params(self, async_client: AsyncWhop) -> N
plan_type="renewal",
release_method="buy_now",
renewal_price=6.9,
+ stock=42,
+ strike_through_initial_price=6.9,
+ strike_through_renewal_price=6.9,
title="title",
trial_period_days=42,
+ unlimited_stock=True,
visibility="visible",
)
assert_matches_type(Plan, plan, path=["response"])
@@ -464,8 +476,12 @@ async def test_method_update_with_all_params(self, async_client: AsyncWhop) -> N
offer_cancel_discount=True,
override_tax_type="inclusive",
renewal_price=6.9,
+ stock=42,
+ strike_through_initial_price=6.9,
+ strike_through_renewal_price=6.9,
title="title",
trial_period_days=42,
+ unlimited_stock=True,
visibility="visible",
)
assert_matches_type(Plan, plan, path=["response"])
diff --git a/tests/api_resources/test_products.py b/tests/api_resources/test_products.py
index 2d66be66..5b292411 100644
--- a/tests/api_resources/test_products.py
+++ b/tests/api_resources/test_products.py
@@ -36,11 +36,6 @@ def test_method_create_with_all_params(self, client: Whop) -> None:
product = client.products.create(
company_id="biz_xxxxxxxxxxxxxx",
title="title",
- access_pass_type="regular",
- banner_image={
- "id": "id",
- "direct_upload_id": "direct_upload_id",
- },
business_type="education_program",
collect_shipping_address=True,
custom_cta="get_access",
@@ -189,6 +184,10 @@ def test_method_update_with_all_params(self, client: Whop) -> None:
product_tax_code_id="ptc_xxxxxxxxxxxxxx",
redirect_purchase_url="redirect_purchase_url",
route="route",
+ store_page_config={
+ "custom_cta": "custom_cta",
+ "show_price": True,
+ },
title="title",
visibility="visible",
)
@@ -341,11 +340,6 @@ async def test_method_create_with_all_params(self, async_client: AsyncWhop) -> N
product = await async_client.products.create(
company_id="biz_xxxxxxxxxxxxxx",
title="title",
- access_pass_type="regular",
- banner_image={
- "id": "id",
- "direct_upload_id": "direct_upload_id",
- },
business_type="education_program",
collect_shipping_address=True,
custom_cta="get_access",
@@ -494,6 +488,10 @@ async def test_method_update_with_all_params(self, async_client: AsyncWhop) -> N
product_tax_code_id="ptc_xxxxxxxxxxxxxx",
redirect_purchase_url="redirect_purchase_url",
route="route",
+ store_page_config={
+ "custom_cta": "custom_cta",
+ "show_price": True,
+ },
title="title",
visibility="visible",
)
diff --git a/tests/api_resources/test_promo_codes.py b/tests/api_resources/test_promo_codes.py
new file mode 100644
index 00000000..db6acbf2
--- /dev/null
+++ b/tests/api_resources/test_promo_codes.py
@@ -0,0 +1,444 @@
+# 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 (
+ PromoCode,
+ PromoCodeListResponse,
+ PromoCodeDeleteResponse,
+)
+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 TestPromoCodes:
+ parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ def test_method_create(self, client: Whop) -> None:
+ promo_code = client.promo_codes.create(
+ amount_off=6.9,
+ base_currency="usd",
+ code="code",
+ company_id="biz_xxxxxxxxxxxxxx",
+ new_users_only=True,
+ promo_duration_months=42,
+ promo_type="percentage",
+ )
+ assert_matches_type(PromoCode, promo_code, path=["response"])
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ def test_method_create_with_all_params(self, client: Whop) -> None:
+ promo_code = client.promo_codes.create(
+ amount_off=6.9,
+ base_currency="usd",
+ code="code",
+ company_id="biz_xxxxxxxxxxxxxx",
+ new_users_only=True,
+ promo_duration_months=42,
+ promo_type="percentage",
+ churned_users_only=True,
+ existing_memberships_only=True,
+ expires_at=parse_datetime("2023-12-01T05:00:00.401Z"),
+ one_per_customer=True,
+ plan_ids=["string"],
+ product_id="prod_xxxxxxxxxxxxx",
+ stock=42,
+ unlimited_stock=True,
+ )
+ assert_matches_type(PromoCode, promo_code, path=["response"])
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ def test_raw_response_create(self, client: Whop) -> None:
+ response = client.promo_codes.with_raw_response.create(
+ amount_off=6.9,
+ base_currency="usd",
+ code="code",
+ company_id="biz_xxxxxxxxxxxxxx",
+ new_users_only=True,
+ promo_duration_months=42,
+ promo_type="percentage",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ promo_code = response.parse()
+ assert_matches_type(PromoCode, promo_code, path=["response"])
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ def test_streaming_response_create(self, client: Whop) -> None:
+ with client.promo_codes.with_streaming_response.create(
+ amount_off=6.9,
+ base_currency="usd",
+ code="code",
+ company_id="biz_xxxxxxxxxxxxxx",
+ new_users_only=True,
+ promo_duration_months=42,
+ promo_type="percentage",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ promo_code = response.parse()
+ assert_matches_type(PromoCode, promo_code, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ def test_method_retrieve(self, client: Whop) -> None:
+ promo_code = client.promo_codes.retrieve(
+ "promo_xxxxxxxxxxxx",
+ )
+ assert_matches_type(PromoCode, promo_code, path=["response"])
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ def test_raw_response_retrieve(self, client: Whop) -> None:
+ response = client.promo_codes.with_raw_response.retrieve(
+ "promo_xxxxxxxxxxxx",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ promo_code = response.parse()
+ assert_matches_type(PromoCode, promo_code, path=["response"])
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ def test_streaming_response_retrieve(self, client: Whop) -> None:
+ with client.promo_codes.with_streaming_response.retrieve(
+ "promo_xxxxxxxxxxxx",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ promo_code = response.parse()
+ assert_matches_type(PromoCode, promo_code, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Prism 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.promo_codes.with_raw_response.retrieve(
+ "",
+ )
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ def test_method_list(self, client: Whop) -> None:
+ promo_code = client.promo_codes.list(
+ company_id="biz_xxxxxxxxxxxxxx",
+ )
+ assert_matches_type(SyncCursorPage[PromoCodeListResponse], promo_code, path=["response"])
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ def test_method_list_with_all_params(self, client: Whop) -> None:
+ promo_code = client.promo_codes.list(
+ company_id="biz_xxxxxxxxxxxxxx",
+ after="after",
+ before="before",
+ first=42,
+ last=42,
+ plan_ids=["string"],
+ product_ids=["string"],
+ status="active",
+ )
+ assert_matches_type(SyncCursorPage[PromoCodeListResponse], promo_code, path=["response"])
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ def test_raw_response_list(self, client: Whop) -> None:
+ response = client.promo_codes.with_raw_response.list(
+ company_id="biz_xxxxxxxxxxxxxx",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ promo_code = response.parse()
+ assert_matches_type(SyncCursorPage[PromoCodeListResponse], promo_code, path=["response"])
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ def test_streaming_response_list(self, client: Whop) -> None:
+ with client.promo_codes.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"
+
+ promo_code = response.parse()
+ assert_matches_type(SyncCursorPage[PromoCodeListResponse], promo_code, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ def test_method_delete(self, client: Whop) -> None:
+ promo_code = client.promo_codes.delete(
+ "promo_xxxxxxxxxxxx",
+ )
+ assert_matches_type(PromoCodeDeleteResponse, promo_code, path=["response"])
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ def test_raw_response_delete(self, client: Whop) -> None:
+ response = client.promo_codes.with_raw_response.delete(
+ "promo_xxxxxxxxxxxx",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ promo_code = response.parse()
+ assert_matches_type(PromoCodeDeleteResponse, promo_code, path=["response"])
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ def test_streaming_response_delete(self, client: Whop) -> None:
+ with client.promo_codes.with_streaming_response.delete(
+ "promo_xxxxxxxxxxxx",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ promo_code = response.parse()
+ assert_matches_type(PromoCodeDeleteResponse, promo_code, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Prism 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.promo_codes.with_raw_response.delete(
+ "",
+ )
+
+
+class TestAsyncPromoCodes:
+ parametrize = pytest.mark.parametrize(
+ "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
+ )
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ async def test_method_create(self, async_client: AsyncWhop) -> None:
+ promo_code = await async_client.promo_codes.create(
+ amount_off=6.9,
+ base_currency="usd",
+ code="code",
+ company_id="biz_xxxxxxxxxxxxxx",
+ new_users_only=True,
+ promo_duration_months=42,
+ promo_type="percentage",
+ )
+ assert_matches_type(PromoCode, promo_code, path=["response"])
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ async def test_method_create_with_all_params(self, async_client: AsyncWhop) -> None:
+ promo_code = await async_client.promo_codes.create(
+ amount_off=6.9,
+ base_currency="usd",
+ code="code",
+ company_id="biz_xxxxxxxxxxxxxx",
+ new_users_only=True,
+ promo_duration_months=42,
+ promo_type="percentage",
+ churned_users_only=True,
+ existing_memberships_only=True,
+ expires_at=parse_datetime("2023-12-01T05:00:00.401Z"),
+ one_per_customer=True,
+ plan_ids=["string"],
+ product_id="prod_xxxxxxxxxxxxx",
+ stock=42,
+ unlimited_stock=True,
+ )
+ assert_matches_type(PromoCode, promo_code, path=["response"])
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ async def test_raw_response_create(self, async_client: AsyncWhop) -> None:
+ response = await async_client.promo_codes.with_raw_response.create(
+ amount_off=6.9,
+ base_currency="usd",
+ code="code",
+ company_id="biz_xxxxxxxxxxxxxx",
+ new_users_only=True,
+ promo_duration_months=42,
+ promo_type="percentage",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ promo_code = await response.parse()
+ assert_matches_type(PromoCode, promo_code, path=["response"])
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ async def test_streaming_response_create(self, async_client: AsyncWhop) -> None:
+ async with async_client.promo_codes.with_streaming_response.create(
+ amount_off=6.9,
+ base_currency="usd",
+ code="code",
+ company_id="biz_xxxxxxxxxxxxxx",
+ new_users_only=True,
+ promo_duration_months=42,
+ promo_type="percentage",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ promo_code = await response.parse()
+ assert_matches_type(PromoCode, promo_code, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ async def test_method_retrieve(self, async_client: AsyncWhop) -> None:
+ promo_code = await async_client.promo_codes.retrieve(
+ "promo_xxxxxxxxxxxx",
+ )
+ assert_matches_type(PromoCode, promo_code, path=["response"])
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ async def test_raw_response_retrieve(self, async_client: AsyncWhop) -> None:
+ response = await async_client.promo_codes.with_raw_response.retrieve(
+ "promo_xxxxxxxxxxxx",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ promo_code = await response.parse()
+ assert_matches_type(PromoCode, promo_code, path=["response"])
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ async def test_streaming_response_retrieve(self, async_client: AsyncWhop) -> None:
+ async with async_client.promo_codes.with_streaming_response.retrieve(
+ "promo_xxxxxxxxxxxx",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ promo_code = await response.parse()
+ assert_matches_type(PromoCode, promo_code, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Prism 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.promo_codes.with_raw_response.retrieve(
+ "",
+ )
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ async def test_method_list(self, async_client: AsyncWhop) -> None:
+ promo_code = await async_client.promo_codes.list(
+ company_id="biz_xxxxxxxxxxxxxx",
+ )
+ assert_matches_type(AsyncCursorPage[PromoCodeListResponse], promo_code, path=["response"])
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ async def test_method_list_with_all_params(self, async_client: AsyncWhop) -> None:
+ promo_code = await async_client.promo_codes.list(
+ company_id="biz_xxxxxxxxxxxxxx",
+ after="after",
+ before="before",
+ first=42,
+ last=42,
+ plan_ids=["string"],
+ product_ids=["string"],
+ status="active",
+ )
+ assert_matches_type(AsyncCursorPage[PromoCodeListResponse], promo_code, path=["response"])
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ async def test_raw_response_list(self, async_client: AsyncWhop) -> None:
+ response = await async_client.promo_codes.with_raw_response.list(
+ company_id="biz_xxxxxxxxxxxxxx",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ promo_code = await response.parse()
+ assert_matches_type(AsyncCursorPage[PromoCodeListResponse], promo_code, path=["response"])
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ async def test_streaming_response_list(self, async_client: AsyncWhop) -> None:
+ async with async_client.promo_codes.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"
+
+ promo_code = await response.parse()
+ assert_matches_type(AsyncCursorPage[PromoCodeListResponse], promo_code, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ async def test_method_delete(self, async_client: AsyncWhop) -> None:
+ promo_code = await async_client.promo_codes.delete(
+ "promo_xxxxxxxxxxxx",
+ )
+ assert_matches_type(PromoCodeDeleteResponse, promo_code, path=["response"])
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ async def test_raw_response_delete(self, async_client: AsyncWhop) -> None:
+ response = await async_client.promo_codes.with_raw_response.delete(
+ "promo_xxxxxxxxxxxx",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ promo_code = await response.parse()
+ assert_matches_type(PromoCodeDeleteResponse, promo_code, path=["response"])
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ async def test_streaming_response_delete(self, async_client: AsyncWhop) -> None:
+ async with async_client.promo_codes.with_streaming_response.delete(
+ "promo_xxxxxxxxxxxx",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ promo_code = await response.parse()
+ assert_matches_type(PromoCodeDeleteResponse, promo_code, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Prism 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.promo_codes.with_raw_response.delete(
+ "",
+ )
diff --git a/tests/api_resources/test_reviews.py b/tests/api_resources/test_reviews.py
new file mode 100644
index 00000000..640ab0cd
--- /dev/null
+++ b/tests/api_resources/test_reviews.py
@@ -0,0 +1,205 @@
+# 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 ReviewListResponse, ReviewRetrieveResponse
+from whop_sdk.pagination import SyncCursorPage, AsyncCursorPage
+
+base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
+
+
+class TestReviews:
+ parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ def test_method_retrieve(self, client: Whop) -> None:
+ review = client.reviews.retrieve(
+ "rev_xxxxxxxxxxxxxx",
+ )
+ assert_matches_type(ReviewRetrieveResponse, review, path=["response"])
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ def test_raw_response_retrieve(self, client: Whop) -> None:
+ response = client.reviews.with_raw_response.retrieve(
+ "rev_xxxxxxxxxxxxxx",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ review = response.parse()
+ assert_matches_type(ReviewRetrieveResponse, review, path=["response"])
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ def test_streaming_response_retrieve(self, client: Whop) -> None:
+ with client.reviews.with_streaming_response.retrieve(
+ "rev_xxxxxxxxxxxxxx",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ review = response.parse()
+ assert_matches_type(ReviewRetrieveResponse, review, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Prism 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.reviews.with_raw_response.retrieve(
+ "",
+ )
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ def test_method_list(self, client: Whop) -> None:
+ review = client.reviews.list(
+ product_id="prod_xxxxxxxxxxxxx",
+ )
+ assert_matches_type(SyncCursorPage[ReviewListResponse], review, path=["response"])
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ def test_method_list_with_all_params(self, client: Whop) -> None:
+ review = client.reviews.list(
+ product_id="prod_xxxxxxxxxxxxx",
+ after="after",
+ before="before",
+ first=42,
+ last=42,
+ max_stars=42,
+ min_stars=42,
+ )
+ assert_matches_type(SyncCursorPage[ReviewListResponse], review, path=["response"])
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ def test_raw_response_list(self, client: Whop) -> None:
+ response = client.reviews.with_raw_response.list(
+ product_id="prod_xxxxxxxxxxxxx",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ review = response.parse()
+ assert_matches_type(SyncCursorPage[ReviewListResponse], review, path=["response"])
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ def test_streaming_response_list(self, client: Whop) -> None:
+ with client.reviews.with_streaming_response.list(
+ product_id="prod_xxxxxxxxxxxxx",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ review = response.parse()
+ assert_matches_type(SyncCursorPage[ReviewListResponse], review, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+
+class TestAsyncReviews:
+ parametrize = pytest.mark.parametrize(
+ "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
+ )
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ async def test_method_retrieve(self, async_client: AsyncWhop) -> None:
+ review = await async_client.reviews.retrieve(
+ "rev_xxxxxxxxxxxxxx",
+ )
+ assert_matches_type(ReviewRetrieveResponse, review, path=["response"])
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ async def test_raw_response_retrieve(self, async_client: AsyncWhop) -> None:
+ response = await async_client.reviews.with_raw_response.retrieve(
+ "rev_xxxxxxxxxxxxxx",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ review = await response.parse()
+ assert_matches_type(ReviewRetrieveResponse, review, path=["response"])
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ async def test_streaming_response_retrieve(self, async_client: AsyncWhop) -> None:
+ async with async_client.reviews.with_streaming_response.retrieve(
+ "rev_xxxxxxxxxxxxxx",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ review = await response.parse()
+ assert_matches_type(ReviewRetrieveResponse, review, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Prism 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.reviews.with_raw_response.retrieve(
+ "",
+ )
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ async def test_method_list(self, async_client: AsyncWhop) -> None:
+ review = await async_client.reviews.list(
+ product_id="prod_xxxxxxxxxxxxx",
+ )
+ assert_matches_type(AsyncCursorPage[ReviewListResponse], review, path=["response"])
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ async def test_method_list_with_all_params(self, async_client: AsyncWhop) -> None:
+ review = await async_client.reviews.list(
+ product_id="prod_xxxxxxxxxxxxx",
+ after="after",
+ before="before",
+ first=42,
+ last=42,
+ max_stars=42,
+ min_stars=42,
+ )
+ assert_matches_type(AsyncCursorPage[ReviewListResponse], review, path=["response"])
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ async def test_raw_response_list(self, async_client: AsyncWhop) -> None:
+ response = await async_client.reviews.with_raw_response.list(
+ product_id="prod_xxxxxxxxxxxxx",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ review = await response.parse()
+ assert_matches_type(AsyncCursorPage[ReviewListResponse], review, path=["response"])
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ async def test_streaming_response_list(self, async_client: AsyncWhop) -> None:
+ async with async_client.reviews.with_streaming_response.list(
+ product_id="prod_xxxxxxxxxxxxx",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ review = await response.parse()
+ assert_matches_type(AsyncCursorPage[ReviewListResponse], review, path=["response"])
+
+ assert cast(Any, response.is_closed) is True