From ef1ae2ba72930b9243fd239dbc7e25675c9c6c97 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Fri, 31 Jul 2026 13:30:19 +0000
Subject: [PATCH 1/2] feat(api): align reply extraction contracts
Regenerate every SDK from the exact production OpenAPI contract.
---
.stats.yml | 4 +-
api.md | 6 +-
src/x_twitter_scraper/resources/x/accounts.py | 73 ++++----
.../resources/x/communities/communities.py | 12 +-
.../resources/x/communities/tweets.py | 24 +--
src/x_twitter_scraper/resources/x/lists.py | 12 +-
.../resources/x/tweets/tweets.py | 89 +++++-----
.../resources/x/users/users.py | 60 +++----
src/x_twitter_scraper/types/__init__.py | 19 ++
.../types/shared/content_disclosure.py | 3 -
.../types/shared/embedded_tweet.py | 163 +++++++++++++++++-
.../types/shared/paginated_tweets.py | 12 +-
.../types/shared/search_tweet.py | 155 ++++++++++++++++-
.../types/shared/tweet_media.py | 80 ++++++++-
.../types/shared/user_profile.py | 79 ++++++++-
.../types/support/ticket_list_response.py | 17 +-
.../types/support/ticket_retrieve_response.py | 22 +--
.../types/support/ticket_update_response.py | 6 +-
.../types/trend_list_response.py | 11 ++
src/x_twitter_scraper/types/x/__init__.py | 2 +
...nt_connection_attempt_retrieve_response.py | 16 +-
...nt_connection_challenge_submit_response.py | 2 +-
.../types/x/account_create_response.py | 68 ++++++++
.../types/x/account_reauth_response.py | 2 +-
.../tweet_list_by_community_params.py | 8 +-
.../types/x/communities/tweet_list_params.py | 8 +-
.../x/community_retrieve_info_response.py | 6 -
.../x/community_retrieve_search_params.py | 8 +-
.../types/x/list_retrieve_tweets_params.py | 8 +-
src/x_twitter_scraper/types/x/tweet_author.py | 4 +-
src/x_twitter_scraper/types/x/tweet_detail.py | 155 ++++++++++++++++-
.../types/x/tweet_get_quotes_params.py | 8 +-
.../types/x/tweet_get_replies_params.py | 22 ++-
.../types/x/tweet_get_replies_response.py | 151 ++++++++++++++++
.../types/x/tweet_get_thread_params.py | 8 +-
.../types/x/tweet_retrieve_response.py | 8 +-
.../types/x/user_retrieve_likes_params.py | 8 +-
.../types/x/user_retrieve_media_params.py | 8 +-
.../types/x/user_retrieve_mentions_params.py | 8 +-
.../types/x/user_retrieve_replies_params.py | 8 +-
.../types/x/user_retrieve_tweets_params.py | 8 +-
.../types/x_get_article_response.py | 2 -
.../types/x_get_trends_response.py | 11 ++
tests/api_resources/x/test_accounts.py | 13 +-
tests/api_resources/x/test_tweets.py | 21 ++-
45 files changed, 1131 insertions(+), 287 deletions(-)
create mode 100644 src/x_twitter_scraper/types/x/account_create_response.py
create mode 100644 src/x_twitter_scraper/types/x/tweet_get_replies_response.py
diff --git a/.stats.yml b/.stats.yml
index 722a8b3..ec9dedf 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,4 +1,4 @@
configured_endpoints: 123
-openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/xquik/x-twitter-scraper-323137ebb1ee9824cd9cfb63a325c0839deb81f17244e42e8154d6567e7af954.yml
-openapi_spec_hash: 48da9c2747c1e7c474dd514d39c28c49
+openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/xquik/x-twitter-scraper-5f26f3694399b4ac5a4431c66f7d27c4024f9d0dcd899702de5fde1150e28330.yml
+openapi_spec_hash: 362e24e622f9a74ded8b38018e50460c
config_hash: dab26921eb273c16e5570c0485c9ac2c
diff --git a/api.md b/api.md
index 7f630d5..b652b9b 100644
--- a/api.md
+++ b/api.md
@@ -276,6 +276,7 @@ from x_twitter_scraper.types.x import (
TweetCreateResponse,
TweetRetrieveResponse,
TweetDeleteResponse,
+ TweetGetRepliesResponse,
)
```
@@ -287,7 +288,7 @@ Methods:
- client.x.tweets.delete(id, \*\*params) -> TweetDeleteResponse
- client.x.tweets.get_favoriters(id, \*\*params) -> PaginatedUsers
- client.x.tweets.get_quotes(id, \*\*params) -> PaginatedTweets
-- client.x.tweets.get_replies(id, \*\*params) -> PaginatedTweets
+- client.x.tweets.get_replies(id, \*\*params) -> TweetGetRepliesResponse
- client.x.tweets.get_retweeters(id, \*\*params) -> PaginatedUsers
- client.x.tweets.get_thread(id, \*\*params) -> PaginatedTweets
- client.x.tweets.search(\*\*params) -> PaginatedTweets
@@ -460,6 +461,7 @@ Types:
from x_twitter_scraper.types.x import (
XAccount,
XAccountDetail,
+ AccountCreateResponse,
AccountListResponse,
AccountDeleteResponse,
AccountBulkRetryResponse,
@@ -469,7 +471,7 @@ from x_twitter_scraper.types.x import (
Methods:
-- client.x.accounts.create(\*\*params) -> object
+- client.x.accounts.create(\*\*params) -> AccountCreateResponse
- client.x.accounts.retrieve(id) -> XAccountDetail
- client.x.accounts.list() -> AccountListResponse
- client.x.accounts.delete(id) -> AccountDeleteResponse
diff --git a/src/x_twitter_scraper/resources/x/accounts.py b/src/x_twitter_scraper/resources/x/accounts.py
index e2c7a34..0776c04 100644
--- a/src/x_twitter_scraper/resources/x/accounts.py
+++ b/src/x_twitter_scraper/resources/x/accounts.py
@@ -6,6 +6,8 @@
from __future__ import annotations
+from typing import Any, cast
+
import httpx
from ..._types import Body, Omit, Query, Headers, NotGiven, omit, not_given
@@ -22,6 +24,7 @@
from ..._base_client import make_request_options
from ...types.x.x_account_detail import XAccountDetail
from ...types.x.account_list_response import AccountListResponse
+from ...types.x.account_create_response import AccountCreateResponse
from ...types.x.account_delete_response import AccountDeleteResponse
from ...types.x.account_reauth_response import AccountReauthResponse
from ...types.x.account_bulk_retry_response import AccountBulkRetryResponse
@@ -64,7 +67,7 @@ def create(
extra_query: Query | None = None,
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
- ) -> object:
+ ) -> AccountCreateResponse:
"""
Connect X account
@@ -85,21 +88,26 @@ def create(
timeout: Override the client-level default timeout for this request, in seconds
"""
- return self._post(
- "/x/accounts",
- body=maybe_transform(
- {
- "email": email,
- "password": password,
- "totp_secret": totp_secret,
- "username": username,
- },
- account_create_params.AccountCreateParams,
- ),
- options=make_request_options(
- extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ return cast(
+ AccountCreateResponse,
+ self._post(
+ "/x/accounts",
+ body=maybe_transform(
+ {
+ "email": email,
+ "password": password,
+ "totp_secret": totp_secret,
+ "username": username,
+ },
+ account_create_params.AccountCreateParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=cast(
+ Any, AccountCreateResponse
+ ), # Union types cannot be passed in as arguments in the type system
),
- cast_to=object,
)
def retrieve(
@@ -295,7 +303,7 @@ async def create(
extra_query: Query | None = None,
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
- ) -> object:
+ ) -> AccountCreateResponse:
"""
Connect X account
@@ -316,21 +324,26 @@ async def create(
timeout: Override the client-level default timeout for this request, in seconds
"""
- return await self._post(
- "/x/accounts",
- body=await async_maybe_transform(
- {
- "email": email,
- "password": password,
- "totp_secret": totp_secret,
- "username": username,
- },
- account_create_params.AccountCreateParams,
- ),
- options=make_request_options(
- extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ return cast(
+ AccountCreateResponse,
+ await self._post(
+ "/x/accounts",
+ body=await async_maybe_transform(
+ {
+ "email": email,
+ "password": password,
+ "totp_secret": totp_secret,
+ "username": username,
+ },
+ account_create_params.AccountCreateParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=cast(
+ Any, AccountCreateResponse
+ ), # Union types cannot be passed in as arguments in the type system
),
- cast_to=object,
)
async def retrieve(
diff --git a/src/x_twitter_scraper/resources/x/communities/communities.py b/src/x_twitter_scraper/resources/x/communities/communities.py
index 390741c..60ee7f8 100644
--- a/src/x_twitter_scraper/resources/x/communities/communities.py
+++ b/src/x_twitter_scraper/resources/x/communities/communities.py
@@ -332,10 +332,8 @@ def retrieve_search(
cursor: Pagination cursor for community search
- page_size: Maximum items requested from this page (1-100, default 20). The response can
- contain fewer items because the source returned fewer, filters removed items, or
- remaining credits cover fewer results. Keep requesting next_cursor while
- has_next_page is true, even when a page is empty. The deprecated limit and count
+ page_size: Maximum page items (1-100, default 20). Source, filters, or credits can reduce
+ results. Continue while has_next_page is true. Deprecated limit and count
aliases remain accepted.
query_type: Sort order (Latest or Top)
@@ -649,10 +647,8 @@ async def retrieve_search(
cursor: Pagination cursor for community search
- page_size: Maximum items requested from this page (1-100, default 20). The response can
- contain fewer items because the source returned fewer, filters removed items, or
- remaining credits cover fewer results. Keep requesting next_cursor while
- has_next_page is true, even when a page is empty. The deprecated limit and count
+ page_size: Maximum page items (1-100, default 20). Source, filters, or credits can reduce
+ results. Continue while has_next_page is true. Deprecated limit and count
aliases remain accepted.
query_type: Sort order (Latest or Top)
diff --git a/src/x_twitter_scraper/resources/x/communities/tweets.py b/src/x_twitter_scraper/resources/x/communities/tweets.py
index 751b6e9..8922783 100644
--- a/src/x_twitter_scraper/resources/x/communities/tweets.py
+++ b/src/x_twitter_scraper/resources/x/communities/tweets.py
@@ -74,10 +74,8 @@ def list(
cursor: Pagination cursor for community results
- page_size: Maximum items requested from this page (1-100, default 20). The response can
- contain fewer items because the source returned fewer, filters removed items, or
- remaining credits cover fewer results. Keep requesting next_cursor while
- has_next_page is true, even when a page is empty. The deprecated limit and count
+ page_size: Maximum page items (1-100, default 20). Source, filters, or credits can reduce
+ results. Continue while has_next_page is true. Deprecated limit and count
aliases remain accepted.
query_type: Sort order for community results (Latest or Top)
@@ -130,10 +128,8 @@ def list_by_community(
Args:
cursor: Pagination cursor for community tweets
- page_size: Maximum items requested from this page (1-100, default 20). The response can
- contain fewer items because the source returned fewer, filters removed items, or
- remaining credits cover fewer results. Keep requesting next_cursor while
- has_next_page is true, even when a page is empty. The deprecated limit and count
+ page_size: Maximum page items (1-100, default 20). Source, filters, or credits can reduce
+ results. Continue while has_next_page is true. Deprecated limit and count
aliases remain accepted.
extra_headers: Send extra headers
@@ -212,10 +208,8 @@ async def list(
cursor: Pagination cursor for community results
- page_size: Maximum items requested from this page (1-100, default 20). The response can
- contain fewer items because the source returned fewer, filters removed items, or
- remaining credits cover fewer results. Keep requesting next_cursor while
- has_next_page is true, even when a page is empty. The deprecated limit and count
+ page_size: Maximum page items (1-100, default 20). Source, filters, or credits can reduce
+ results. Continue while has_next_page is true. Deprecated limit and count
aliases remain accepted.
query_type: Sort order for community results (Latest or Top)
@@ -268,10 +262,8 @@ async def list_by_community(
Args:
cursor: Pagination cursor for community tweets
- page_size: Maximum items requested from this page (1-100, default 20). The response can
- contain fewer items because the source returned fewer, filters removed items, or
- remaining credits cover fewer results. Keep requesting next_cursor while
- has_next_page is true, even when a page is empty. The deprecated limit and count
+ page_size: Maximum page items (1-100, default 20). Source, filters, or credits can reduce
+ results. Continue while has_next_page is true. Deprecated limit and count
aliases remain accepted.
extra_headers: Send extra headers
diff --git a/src/x_twitter_scraper/resources/x/lists.py b/src/x_twitter_scraper/resources/x/lists.py
index f1b7eaf..a3454d3 100644
--- a/src/x_twitter_scraper/resources/x/lists.py
+++ b/src/x_twitter_scraper/resources/x/lists.py
@@ -173,10 +173,8 @@ def retrieve_tweets(
include_replies: Include replies (default false)
- page_size: Maximum items requested from this page (1-100, default 20). The response can
- contain fewer items because the source returned fewer, filters removed items, or
- remaining credits cover fewer results. Keep requesting next_cursor while
- has_next_page is true, even when a page is empty. The deprecated limit and count
+ page_size: Maximum page items (1-100, default 20). Source, filters, or credits can reduce
+ results. Continue while has_next_page is true. Deprecated limit and count
aliases remain accepted.
since_time: Unix timestamp - filter after
@@ -362,10 +360,8 @@ async def retrieve_tweets(
include_replies: Include replies (default false)
- page_size: Maximum items requested from this page (1-100, default 20). The response can
- contain fewer items because the source returned fewer, filters removed items, or
- remaining credits cover fewer results. Keep requesting next_cursor while
- has_next_page is true, even when a page is empty. The deprecated limit and count
+ page_size: Maximum page items (1-100, default 20). Source, filters, or credits can reduce
+ results. Continue while has_next_page is true. Deprecated limit and count
aliases remain accepted.
since_time: Unix timestamp - filter after
diff --git a/src/x_twitter_scraper/resources/x/tweets/tweets.py b/src/x_twitter_scraper/resources/x/tweets/tweets.py
index 3037620..3f4cc10 100644
--- a/src/x_twitter_scraper/resources/x/tweets/tweets.py
+++ b/src/x_twitter_scraper/resources/x/tweets/tweets.py
@@ -55,6 +55,7 @@
from ....types.x.tweet_create_response import TweetCreateResponse
from ....types.x.tweet_delete_response import TweetDeleteResponse
from ....types.x.tweet_retrieve_response import TweetRetrieveResponse
+from ....types.x.tweet_get_replies_response import TweetGetRepliesResponse
__all__ = ["TweetsResource", "AsyncTweetsResource"]
@@ -390,10 +391,8 @@ def get_quotes(
min_retweets: Minimum retweets threshold.
- page_size: Maximum items requested from this page (1-100, default 20). The response can
- contain fewer items because the source returned fewer, filters removed items, or
- remaining credits cover fewer results. Keep requesting next_cursor while
- has_next_page is true, even when a page is empty. The deprecated limit and count
+ page_size: Maximum page items (1-100, default 20). Source, filters, or credits can reduce
+ results. Continue while has_next_page is true. Deprecated limit and count
aliases remain accepted.
quotes: Quote mode.
@@ -490,12 +489,14 @@ def get_replies(
hashtags: str | Omit = omit,
in_reply_to_tweet_id: str | Omit = omit,
language: str | Omit = omit,
+ limit: int | Omit = omit,
media_type: Literal["images", "videos", "gifs", "media", "links", "none"] | Omit = omit,
mentioning: str | Omit = omit,
min_faves: int | Omit = omit,
min_quotes: int | Omit = omit,
min_replies: int | Omit = omit,
min_retweets: int | Omit = omit,
+ mode: Literal["complete"] | Omit = omit,
page_size: int | Omit = omit,
quotes: Literal["include", "exclude", "only"] | Omit = omit,
quotes_of_tweet_id: str | Omit = omit,
@@ -515,14 +516,13 @@ def get_replies(
extra_query: Query | None = None,
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
- ) -> PaginatedTweets:
- """Returns visible replies.
+ ) -> TweetGetRepliesResponse:
+ """Returns direct replies.
- For an unfiltered first page, Xquik compares a terminal
- page with the post's reported reply count. If the page is visibly incomplete,
- the endpoint returns 424 `replies_incomplete` instead of presenting partial
- coverage as complete. Use tweet search with a `conversation_id:{id}` query as
- the broader fallback.
+ Complete mode merges available timeline views, supported
+ rankings, every forward cursor module, labeled hidden-content branches,
+ exact-parent time partitions scaled to the reported reply count, and search. It
+ separates nested replies and returns 424 below 80% coverage.
Args:
any_words: Words or quoted phrases where any one can match. Separate with spaces, commas,
@@ -546,6 +546,10 @@ def get_replies(
language: Language code filter, e.g. en or tr.
+ limit: With mode=complete, maximum combined direct and nested reply rows (1-25000).
+ Without complete mode, this is the deprecated pageSize alias and uses the normal
+ 1-100 page range.
+
media_type: Filter by media type.
mentioning: Filter tweets mentioning a username.
@@ -558,10 +562,11 @@ def get_replies(
min_retweets: Minimum retweets threshold.
- page_size: Maximum items requested from this page (1-100, default 20). The response can
- contain fewer items because the source returned fewer, filters removed items, or
- remaining credits cover fewer results. Keep requesting next_cursor while
- has_next_page is true, even when a page is empty. The deprecated limit and count
+ mode: Set complete for maximum-coverage collection. Complete mode accepts only limit.
+ Remove cursor, pageSize, count, time ranges, and tweet filters.
+
+ page_size: Maximum page items (1-100, default 20). Source, filters, or credits can reduce
+ results. Continue while has_next_page is true. Deprecated limit and count
aliases remain accepted.
quotes: Quote mode.
@@ -617,12 +622,14 @@ def get_replies(
"hashtags": hashtags,
"in_reply_to_tweet_id": in_reply_to_tweet_id,
"language": language,
+ "limit": limit,
"media_type": media_type,
"mentioning": mentioning,
"min_faves": min_faves,
"min_quotes": min_quotes,
"min_replies": min_replies,
"min_retweets": min_retweets,
+ "mode": mode,
"page_size": page_size,
"quotes": quotes,
"quotes_of_tweet_id": quotes_of_tweet_id,
@@ -640,7 +647,7 @@ def get_replies(
tweet_get_replies_params.TweetGetRepliesParams,
),
),
- cast_to=PaginatedTweets,
+ cast_to=TweetGetRepliesResponse,
)
def get_retweeters(
@@ -714,10 +721,8 @@ def get_thread(
Args:
cursor: Pagination cursor for thread tweets
- page_size: Maximum items requested from this page (1-100, default 20). The response can
- contain fewer items because the source returned fewer, filters removed items, or
- remaining credits cover fewer results. Keep requesting next_cursor while
- has_next_page is true, even when a page is empty. The deprecated limit and count
+ page_size: Maximum page items (1-100, default 20). Source, filters, or credits can reduce
+ results. Continue while has_next_page is true. Deprecated limit and count
aliases remain accepted.
extra_headers: Send extra headers
@@ -1270,10 +1275,8 @@ async def get_quotes(
min_retweets: Minimum retweets threshold.
- page_size: Maximum items requested from this page (1-100, default 20). The response can
- contain fewer items because the source returned fewer, filters removed items, or
- remaining credits cover fewer results. Keep requesting next_cursor while
- has_next_page is true, even when a page is empty. The deprecated limit and count
+ page_size: Maximum page items (1-100, default 20). Source, filters, or credits can reduce
+ results. Continue while has_next_page is true. Deprecated limit and count
aliases remain accepted.
quotes: Quote mode.
@@ -1370,12 +1373,14 @@ async def get_replies(
hashtags: str | Omit = omit,
in_reply_to_tweet_id: str | Omit = omit,
language: str | Omit = omit,
+ limit: int | Omit = omit,
media_type: Literal["images", "videos", "gifs", "media", "links", "none"] | Omit = omit,
mentioning: str | Omit = omit,
min_faves: int | Omit = omit,
min_quotes: int | Omit = omit,
min_replies: int | Omit = omit,
min_retweets: int | Omit = omit,
+ mode: Literal["complete"] | Omit = omit,
page_size: int | Omit = omit,
quotes: Literal["include", "exclude", "only"] | Omit = omit,
quotes_of_tweet_id: str | Omit = omit,
@@ -1395,14 +1400,13 @@ async def get_replies(
extra_query: Query | None = None,
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
- ) -> PaginatedTweets:
- """Returns visible replies.
+ ) -> TweetGetRepliesResponse:
+ """Returns direct replies.
- For an unfiltered first page, Xquik compares a terminal
- page with the post's reported reply count. If the page is visibly incomplete,
- the endpoint returns 424 `replies_incomplete` instead of presenting partial
- coverage as complete. Use tweet search with a `conversation_id:{id}` query as
- the broader fallback.
+ Complete mode merges available timeline views, supported
+ rankings, every forward cursor module, labeled hidden-content branches,
+ exact-parent time partitions scaled to the reported reply count, and search. It
+ separates nested replies and returns 424 below 80% coverage.
Args:
any_words: Words or quoted phrases where any one can match. Separate with spaces, commas,
@@ -1426,6 +1430,10 @@ async def get_replies(
language: Language code filter, e.g. en or tr.
+ limit: With mode=complete, maximum combined direct and nested reply rows (1-25000).
+ Without complete mode, this is the deprecated pageSize alias and uses the normal
+ 1-100 page range.
+
media_type: Filter by media type.
mentioning: Filter tweets mentioning a username.
@@ -1438,10 +1446,11 @@ async def get_replies(
min_retweets: Minimum retweets threshold.
- page_size: Maximum items requested from this page (1-100, default 20). The response can
- contain fewer items because the source returned fewer, filters removed items, or
- remaining credits cover fewer results. Keep requesting next_cursor while
- has_next_page is true, even when a page is empty. The deprecated limit and count
+ mode: Set complete for maximum-coverage collection. Complete mode accepts only limit.
+ Remove cursor, pageSize, count, time ranges, and tweet filters.
+
+ page_size: Maximum page items (1-100, default 20). Source, filters, or credits can reduce
+ results. Continue while has_next_page is true. Deprecated limit and count
aliases remain accepted.
quotes: Quote mode.
@@ -1497,12 +1506,14 @@ async def get_replies(
"hashtags": hashtags,
"in_reply_to_tweet_id": in_reply_to_tweet_id,
"language": language,
+ "limit": limit,
"media_type": media_type,
"mentioning": mentioning,
"min_faves": min_faves,
"min_quotes": min_quotes,
"min_replies": min_replies,
"min_retweets": min_retweets,
+ "mode": mode,
"page_size": page_size,
"quotes": quotes,
"quotes_of_tweet_id": quotes_of_tweet_id,
@@ -1520,7 +1531,7 @@ async def get_replies(
tweet_get_replies_params.TweetGetRepliesParams,
),
),
- cast_to=PaginatedTweets,
+ cast_to=TweetGetRepliesResponse,
)
async def get_retweeters(
@@ -1594,10 +1605,8 @@ async def get_thread(
Args:
cursor: Pagination cursor for thread tweets
- page_size: Maximum items requested from this page (1-100, default 20). The response can
- contain fewer items because the source returned fewer, filters removed items, or
- remaining credits cover fewer results. Keep requesting next_cursor while
- has_next_page is true, even when a page is empty. The deprecated limit and count
+ page_size: Maximum page items (1-100, default 20). Source, filters, or credits can reduce
+ results. Continue while has_next_page is true. Deprecated limit and count
aliases remain accepted.
extra_headers: Send extra headers
diff --git a/src/x_twitter_scraper/resources/x/users/users.py b/src/x_twitter_scraper/resources/x/users/users.py
index b235156..c32eb4c 100644
--- a/src/x_twitter_scraper/resources/x/users/users.py
+++ b/src/x_twitter_scraper/resources/x/users/users.py
@@ -438,10 +438,8 @@ def retrieve_likes(
min_retweets: Minimum retweets threshold.
- page_size: Maximum items requested from this page (1-100, default 20). The response can
- contain fewer items because the source returned fewer, filters removed items, or
- remaining credits cover fewer results. Keep requesting next_cursor while
- has_next_page is true, even when a page is empty. The deprecated limit and count
+ page_size: Maximum page items (1-100, default 20). Source, filters, or credits can reduce
+ results. Continue while has_next_page is true. Deprecated limit and count
aliases remain accepted.
quotes: Quote mode.
@@ -592,10 +590,8 @@ def retrieve_media(
min_retweets: Minimum retweets threshold.
- page_size: Maximum items requested from this page (1-100, default 20). The response can
- contain fewer items because the source returned fewer, filters removed items, or
- remaining credits cover fewer results. Keep requesting next_cursor while
- has_next_page is true, even when a page is empty. The deprecated limit and count
+ page_size: Maximum page items (1-100, default 20). Source, filters, or credits can reduce
+ results. Continue while has_next_page is true. Deprecated limit and count
aliases remain accepted.
quotes: Quote mode.
@@ -748,10 +744,8 @@ def retrieve_mentions(
min_retweets: Minimum retweets threshold.
- page_size: Maximum items requested from this page (1-100, default 20). The response can
- contain fewer items because the source returned fewer, filters removed items, or
- remaining credits cover fewer results. Keep requesting next_cursor while
- has_next_page is true, even when a page is empty. The deprecated limit and count
+ page_size: Maximum page items (1-100, default 20). Source, filters, or credits can reduce
+ results. Continue while has_next_page is true. Deprecated limit and count
aliases remain accepted.
quotes: Quote mode.
@@ -911,10 +905,8 @@ def retrieve_replies(
min_retweets: Minimum retweets threshold.
- page_size: Maximum items requested from this page (1-100, default 20). The response can
- contain fewer items because the source returned fewer, filters removed items, or
- remaining credits cover fewer results. Keep requesting next_cursor while
- has_next_page is true, even when a page is empty. The deprecated limit and count
+ page_size: Maximum page items (1-100, default 20). Source, filters, or credits can reduce
+ results. Continue while has_next_page is true. Deprecated limit and count
aliases remain accepted.
quotes: Quote mode.
@@ -1118,10 +1110,8 @@ def retrieve_tweets(
min_retweets: Minimum retweets threshold.
- page_size: Maximum items requested from this page (1-100, default 20). The response can
- contain fewer items because the source returned fewer, filters removed items, or
- remaining credits cover fewer results. Keep requesting next_cursor while
- has_next_page is true, even when a page is empty. The deprecated limit and count
+ page_size: Maximum page items (1-100, default 20). Source, filters, or credits can reduce
+ results. Continue while has_next_page is true. Deprecated limit and count
aliases remain accepted.
quotes: Quote mode.
@@ -1638,10 +1628,8 @@ async def retrieve_likes(
min_retweets: Minimum retweets threshold.
- page_size: Maximum items requested from this page (1-100, default 20). The response can
- contain fewer items because the source returned fewer, filters removed items, or
- remaining credits cover fewer results. Keep requesting next_cursor while
- has_next_page is true, even when a page is empty. The deprecated limit and count
+ page_size: Maximum page items (1-100, default 20). Source, filters, or credits can reduce
+ results. Continue while has_next_page is true. Deprecated limit and count
aliases remain accepted.
quotes: Quote mode.
@@ -1792,10 +1780,8 @@ async def retrieve_media(
min_retweets: Minimum retweets threshold.
- page_size: Maximum items requested from this page (1-100, default 20). The response can
- contain fewer items because the source returned fewer, filters removed items, or
- remaining credits cover fewer results. Keep requesting next_cursor while
- has_next_page is true, even when a page is empty. The deprecated limit and count
+ page_size: Maximum page items (1-100, default 20). Source, filters, or credits can reduce
+ results. Continue while has_next_page is true. Deprecated limit and count
aliases remain accepted.
quotes: Quote mode.
@@ -1948,10 +1934,8 @@ async def retrieve_mentions(
min_retweets: Minimum retweets threshold.
- page_size: Maximum items requested from this page (1-100, default 20). The response can
- contain fewer items because the source returned fewer, filters removed items, or
- remaining credits cover fewer results. Keep requesting next_cursor while
- has_next_page is true, even when a page is empty. The deprecated limit and count
+ page_size: Maximum page items (1-100, default 20). Source, filters, or credits can reduce
+ results. Continue while has_next_page is true. Deprecated limit and count
aliases remain accepted.
quotes: Quote mode.
@@ -2111,10 +2095,8 @@ async def retrieve_replies(
min_retweets: Minimum retweets threshold.
- page_size: Maximum items requested from this page (1-100, default 20). The response can
- contain fewer items because the source returned fewer, filters removed items, or
- remaining credits cover fewer results. Keep requesting next_cursor while
- has_next_page is true, even when a page is empty. The deprecated limit and count
+ page_size: Maximum page items (1-100, default 20). Source, filters, or credits can reduce
+ results. Continue while has_next_page is true. Deprecated limit and count
aliases remain accepted.
quotes: Quote mode.
@@ -2318,10 +2300,8 @@ async def retrieve_tweets(
min_retweets: Minimum retweets threshold.
- page_size: Maximum items requested from this page (1-100, default 20). The response can
- contain fewer items because the source returned fewer, filters removed items, or
- remaining credits cover fewer results. Keep requesting next_cursor while
- has_next_page is true, even when a page is empty. The deprecated limit and count
+ page_size: Maximum page items (1-100, default 20). Source, filters, or credits can reduce
+ results. Continue while has_next_page is true. Deprecated limit and count
aliases remain accepted.
quotes: Quote mode.
diff --git a/src/x_twitter_scraper/types/__init__.py b/src/x_twitter_scraper/types/__init__.py
index a9d5574..26495ac 100644
--- a/src/x_twitter_scraper/types/__init__.py
+++ b/src/x_twitter_scraper/types/__init__.py
@@ -6,6 +6,8 @@
from __future__ import annotations
+from . import x, shared
+from .. import _compat
from .draft import Draft as Draft
from .event import Event as Event
from .shared import (
@@ -111,3 +113,20 @@
from .radar_retrieve_trending_topics_response import (
RadarRetrieveTrendingTopicsResponse as RadarRetrieveTrendingTopicsResponse,
)
+
+# Rebuild cyclical models only after all modules are imported.
+# This ensures that, when building the deferred (due to cyclical references) model schema,
+# Pydantic can resolve the necessary references.
+# See: https://github.com/pydantic/pydantic/issues/11250 for more context.
+if _compat.PYDANTIC_V1:
+ x.tweet_detail.TweetDetail.update_forward_refs() # type: ignore
+ x.tweet_retrieve_response.TweetRetrieveResponse.update_forward_refs() # type: ignore
+ shared.embedded_tweet.EmbeddedTweet.update_forward_refs() # type: ignore
+ shared.paginated_tweets.PaginatedTweets.update_forward_refs() # type: ignore
+ shared.search_tweet.SearchTweet.update_forward_refs() # type: ignore
+else:
+ x.tweet_detail.TweetDetail.model_rebuild(_parent_namespace_depth=0)
+ x.tweet_retrieve_response.TweetRetrieveResponse.model_rebuild(_parent_namespace_depth=0)
+ shared.embedded_tweet.EmbeddedTweet.model_rebuild(_parent_namespace_depth=0)
+ shared.paginated_tweets.PaginatedTweets.model_rebuild(_parent_namespace_depth=0)
+ shared.search_tweet.SearchTweet.model_rebuild(_parent_namespace_depth=0)
diff --git a/src/x_twitter_scraper/types/shared/content_disclosure.py b/src/x_twitter_scraper/types/shared/content_disclosure.py
index 0010340..d3dfd04 100644
--- a/src/x_twitter_scraper/types/shared/content_disclosure.py
+++ b/src/x_twitter_scraper/types/shared/content_disclosure.py
@@ -19,9 +19,6 @@ class Advertising(BaseModel):
class AIGenerated(BaseModel):
- can_edit: Optional[bool] = FieldInfo(alias="canEdit", default=None)
- """Whether the disclosure can be edited on X."""
-
detection_source: Optional[str] = FieldInfo(alias="detectionSource", default=None)
"""Source of the AI-generated media disclosure."""
diff --git a/src/x_twitter_scraper/types/shared/embedded_tweet.py b/src/x_twitter_scraper/types/shared/embedded_tweet.py
index 3a00162..e30366a 100644
--- a/src/x_twitter_scraper/types/shared/embedded_tweet.py
+++ b/src/x_twitter_scraper/types/shared/embedded_tweet.py
@@ -4,6 +4,8 @@
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+from __future__ import annotations
+
from typing import Dict, List, Optional
from pydantic import Field as FieldInfo
@@ -13,7 +15,123 @@
from .user_profile import UserProfile
from .content_disclosure import ContentDisclosure
-__all__ = ["EmbeddedTweet"]
+__all__ = [
+ "EmbeddedTweet",
+ "Article",
+ "Card",
+ "CommunityNote",
+ "Edit",
+ "NoteTweet",
+ "NoteTweetRichtextTag",
+ "Place",
+ "PreviousCounts",
+]
+
+
+class Article(BaseModel):
+ """Article metadata attached to a tweet."""
+
+ id: Optional[str] = None
+
+ cover_media_url: Optional[str] = FieldInfo(alias="coverMediaUrl", default=None)
+
+ preview_text: Optional[str] = FieldInfo(alias="previewText", default=None)
+
+ title: Optional[str] = None
+
+
+class Card(BaseModel):
+ """Public card metadata attached to a tweet."""
+
+ id: Optional[str] = None
+
+ binding_values: Optional[Dict[str, object]] = FieldInfo(alias="bindingValues", default=None)
+
+ name: Optional[str] = None
+
+ url: Optional[str] = None
+
+
+class CommunityNote(BaseModel):
+ """Community Note presentation metadata returned by X."""
+
+ id: Optional[str] = None
+
+ destination_url: Optional[str] = FieldInfo(alias="destinationUrl", default=None)
+
+ footer: Optional[str] = None
+
+ short_title: Optional[str] = FieldInfo(alias="shortTitle", default=None)
+
+ subtitle: Optional[str] = None
+
+ title: Optional[str] = None
+
+ visual_style: Optional[str] = FieldInfo(alias="visualStyle", default=None)
+
+
+class Edit(BaseModel):
+ """Edit history metadata returned by X."""
+
+ editable_until_msecs: Optional[str] = FieldInfo(alias="editableUntilMsecs", default=None)
+
+ edit_tweet_ids: Optional[List[str]] = FieldInfo(alias="editTweetIds", default=None)
+
+
+class NoteTweetRichtextTag(BaseModel):
+ from_index: int = FieldInfo(alias="fromIndex")
+
+ to_index: int = FieldInfo(alias="toIndex")
+
+ types: List[str]
+
+
+class NoteTweet(BaseModel):
+ """Complete Note Tweet content and rich-text metadata."""
+
+ text: str
+
+ id: Optional[str] = None
+
+ entities: Optional[Dict[str, object]] = None
+
+ is_expandable: Optional[bool] = FieldInfo(alias="isExpandable", default=None)
+
+ richtext_tags: Optional[List[NoteTweetRichtextTag]] = FieldInfo(alias="richtextTags", default=None)
+
+
+class Place(BaseModel):
+ """Public place metadata attached to a tweet."""
+
+ id: Optional[str] = None
+
+ bounding_box: Optional[Dict[str, object]] = FieldInfo(alias="boundingBox", default=None)
+
+ country: Optional[str] = None
+
+ country_code: Optional[str] = FieldInfo(alias="countryCode", default=None)
+
+ full_name: Optional[str] = FieldInfo(alias="fullName", default=None)
+
+ name: Optional[str] = None
+
+ place_type: Optional[str] = FieldInfo(alias="placeType", default=None)
+
+ url: Optional[str] = None
+
+
+class PreviousCounts(BaseModel):
+ """Engagement counts retained from a prior tweet edit."""
+
+ bookmark_count: Optional[int] = FieldInfo(alias="bookmarkCount", default=None)
+
+ like_count: Optional[int] = FieldInfo(alias="likeCount", default=None)
+
+ quote_count: Optional[int] = FieldInfo(alias="quoteCount", default=None)
+
+ reply_count: Optional[int] = FieldInfo(alias="replyCount", default=None)
+
+ retweet_count: Optional[int] = FieldInfo(alias="retweetCount", default=None)
class EmbeddedTweet(BaseModel):
@@ -38,9 +156,18 @@ class EmbeddedTweet(BaseModel):
view_count: int = FieldInfo(alias="viewCount")
+ article: Optional[Article] = None
+ """Article metadata attached to a tweet."""
+
author: Optional[UserProfile] = None
"""X user profile with bio, follower counts, and verification status."""
+ card: Optional[Card] = None
+ """Public card metadata attached to a tweet."""
+
+ community_note: Optional[CommunityNote] = FieldInfo(alias="communityNote", default=None)
+ """Community Note presentation metadata returned by X."""
+
content_disclosure: Optional[ContentDisclosure] = FieldInfo(alias="contentDisclosure", default=None)
"""
Content disclosure metadata shown by X when a tweet is labeled as paid
@@ -53,6 +180,9 @@ class EmbeddedTweet(BaseModel):
display_text_range: Optional[List[int]] = FieldInfo(alias="displayTextRange", default=None)
+ edit: Optional[Edit] = None
+ """Edit history metadata returned by X."""
+
entities: Optional[Dict[str, object]] = None
in_reply_to_id: Optional[str] = FieldInfo(alias="inReplyToId", default=None)
@@ -69,12 +199,43 @@ class EmbeddedTweet(BaseModel):
is_reply: Optional[bool] = FieldInfo(alias="isReply", default=None)
+ is_translatable: Optional[bool] = FieldInfo(alias="isTranslatable", default=None)
+
lang: Optional[str] = None
media: Optional[List[TweetMedia]] = None
+ note_tweet: Optional[NoteTweet] = FieldInfo(alias="noteTweet", default=None)
+ """Complete Note Tweet content and rich-text metadata."""
+
+ place: Optional[Place] = None
+ """Public place metadata attached to a tweet."""
+
+ possibly_sensitive: Optional[bool] = FieldInfo(alias="possiblySensitive", default=None)
+
+ previous_counts: Optional[PreviousCounts] = FieldInfo(alias="previousCounts", default=None)
+ """Engagement counts retained from a prior tweet edit."""
+
+ quoted_tweet: Optional["EmbeddedTweet"] = None
+ """Quoted or retweeted tweet context.
+
+ Every object includes id, text, and engagement metrics. A zero metric can mean X
+ did not report the count. Author, media, and conversation fields appear when
+ available.
+ """
+
+ retweeted_tweet: Optional["EmbeddedTweet"] = None
+ """Quoted or retweeted tweet context.
+
+ Every object includes id, text, and engagement metrics. A zero metric can mean X
+ did not report the count. Author, media, and conversation fields appear when
+ available.
+ """
+
source: Optional[str] = None
type: Optional[str] = None
url: Optional[str] = None
+
+ view_state: Optional[str] = FieldInfo(alias="viewState", default=None)
diff --git a/src/x_twitter_scraper/types/shared/paginated_tweets.py b/src/x_twitter_scraper/types/shared/paginated_tweets.py
index b94f94d..4ad997a 100644
--- a/src/x_twitter_scraper/types/shared/paginated_tweets.py
+++ b/src/x_twitter_scraper/types/shared/paginated_tweets.py
@@ -4,22 +4,26 @@
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+from __future__ import annotations
+
from typing import List
from ..._models import BaseModel
-from .search_tweet import SearchTweet
__all__ = ["PaginatedTweets"]
class PaginatedTweets(BaseModel):
- """Paginated tweet results.
+ """Paginated tweets.
- The item count can be lower than pageSize when the source returns fewer tweets, filters remove tweets, or remaining credits cover fewer results. Follow next_cursor while has_next_page is true. An empty page can still have has_next_page true after filtering. Zero affordable results returns 402 insufficient_credits.
+ Source visibility, filters, or remaining credits can reduce results. An empty filtered page can still have has_next_page true. Follow next_cursor while has_next_page is true. Zero affordable results returns 402 insufficient_credits.
"""
has_next_page: bool
next_cursor: str
- tweets: List[SearchTweet]
+ tweets: List["SearchTweet"]
+
+
+from .search_tweet import SearchTweet
diff --git a/src/x_twitter_scraper/types/shared/search_tweet.py b/src/x_twitter_scraper/types/shared/search_tweet.py
index 91dfdd6..1ed7a5a 100644
--- a/src/x_twitter_scraper/types/shared/search_tweet.py
+++ b/src/x_twitter_scraper/types/shared/search_tweet.py
@@ -4,6 +4,8 @@
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+from __future__ import annotations
+
from typing import Dict, List, Optional
from pydantic import Field as FieldInfo
@@ -11,10 +13,125 @@
from ..._models import BaseModel
from .tweet_media import TweetMedia
from .user_profile import UserProfile
-from .embedded_tweet import EmbeddedTweet
from .content_disclosure import ContentDisclosure
-__all__ = ["SearchTweet"]
+__all__ = [
+ "SearchTweet",
+ "Article",
+ "Card",
+ "CommunityNote",
+ "Edit",
+ "NoteTweet",
+ "NoteTweetRichtextTag",
+ "Place",
+ "PreviousCounts",
+]
+
+
+class Article(BaseModel):
+ """Article metadata attached to a tweet."""
+
+ id: Optional[str] = None
+
+ cover_media_url: Optional[str] = FieldInfo(alias="coverMediaUrl", default=None)
+
+ preview_text: Optional[str] = FieldInfo(alias="previewText", default=None)
+
+ title: Optional[str] = None
+
+
+class Card(BaseModel):
+ """Public card metadata attached to a tweet."""
+
+ id: Optional[str] = None
+
+ binding_values: Optional[Dict[str, object]] = FieldInfo(alias="bindingValues", default=None)
+
+ name: Optional[str] = None
+
+ url: Optional[str] = None
+
+
+class CommunityNote(BaseModel):
+ """Community Note presentation metadata returned by X."""
+
+ id: Optional[str] = None
+
+ destination_url: Optional[str] = FieldInfo(alias="destinationUrl", default=None)
+
+ footer: Optional[str] = None
+
+ short_title: Optional[str] = FieldInfo(alias="shortTitle", default=None)
+
+ subtitle: Optional[str] = None
+
+ title: Optional[str] = None
+
+ visual_style: Optional[str] = FieldInfo(alias="visualStyle", default=None)
+
+
+class Edit(BaseModel):
+ """Edit history metadata returned by X."""
+
+ editable_until_msecs: Optional[str] = FieldInfo(alias="editableUntilMsecs", default=None)
+
+ edit_tweet_ids: Optional[List[str]] = FieldInfo(alias="editTweetIds", default=None)
+
+
+class NoteTweetRichtextTag(BaseModel):
+ from_index: int = FieldInfo(alias="fromIndex")
+
+ to_index: int = FieldInfo(alias="toIndex")
+
+ types: List[str]
+
+
+class NoteTweet(BaseModel):
+ """Complete Note Tweet content and rich-text metadata."""
+
+ text: str
+
+ id: Optional[str] = None
+
+ entities: Optional[Dict[str, object]] = None
+
+ is_expandable: Optional[bool] = FieldInfo(alias="isExpandable", default=None)
+
+ richtext_tags: Optional[List[NoteTweetRichtextTag]] = FieldInfo(alias="richtextTags", default=None)
+
+
+class Place(BaseModel):
+ """Public place metadata attached to a tweet."""
+
+ id: Optional[str] = None
+
+ bounding_box: Optional[Dict[str, object]] = FieldInfo(alias="boundingBox", default=None)
+
+ country: Optional[str] = None
+
+ country_code: Optional[str] = FieldInfo(alias="countryCode", default=None)
+
+ full_name: Optional[str] = FieldInfo(alias="fullName", default=None)
+
+ name: Optional[str] = None
+
+ place_type: Optional[str] = FieldInfo(alias="placeType", default=None)
+
+ url: Optional[str] = None
+
+
+class PreviousCounts(BaseModel):
+ """Engagement counts retained from a prior tweet edit."""
+
+ bookmark_count: Optional[int] = FieldInfo(alias="bookmarkCount", default=None)
+
+ like_count: Optional[int] = FieldInfo(alias="likeCount", default=None)
+
+ quote_count: Optional[int] = FieldInfo(alias="quoteCount", default=None)
+
+ reply_count: Optional[int] = FieldInfo(alias="replyCount", default=None)
+
+ retweet_count: Optional[int] = FieldInfo(alias="retweetCount", default=None)
class SearchTweet(BaseModel):
@@ -39,9 +156,18 @@ class SearchTweet(BaseModel):
view_count: int = FieldInfo(alias="viewCount")
+ article: Optional[Article] = None
+ """Article metadata attached to a tweet."""
+
author: Optional[UserProfile] = None
"""X user profile with bio, follower counts, and verification status."""
+ card: Optional[Card] = None
+ """Public card metadata attached to a tweet."""
+
+ community_note: Optional[CommunityNote] = FieldInfo(alias="communityNote", default=None)
+ """Community Note presentation metadata returned by X."""
+
content_disclosure: Optional[ContentDisclosure] = FieldInfo(alias="contentDisclosure", default=None)
"""
Content disclosure metadata shown by X when a tweet is labeled as paid
@@ -56,6 +182,9 @@ class SearchTweet(BaseModel):
display_text_range: Optional[List[int]] = FieldInfo(alias="displayTextRange", default=None)
"""Rendered text's start and end offsets."""
+ edit: Optional[Edit] = None
+ """Edit history metadata returned by X."""
+
entities: Optional[Dict[str, object]] = None
"""
Parsed search-result entities including URLs, mentions, hashtags, and media
@@ -83,13 +212,26 @@ class SearchTweet(BaseModel):
is_reply: Optional[bool] = FieldInfo(alias="isReply", default=None)
"""True when this search result is a reply"""
+ is_translatable: Optional[bool] = FieldInfo(alias="isTranslatable", default=None)
+
lang: Optional[str] = None
"""Search result language code."""
media: Optional[List[TweetMedia]] = None
"""Search-result media attachments, omitted when no media is present"""
- quoted_tweet: Optional[EmbeddedTweet] = None
+ note_tweet: Optional[NoteTweet] = FieldInfo(alias="noteTweet", default=None)
+ """Complete Note Tweet content and rich-text metadata."""
+
+ place: Optional[Place] = None
+ """Public place metadata attached to a tweet."""
+
+ possibly_sensitive: Optional[bool] = FieldInfo(alias="possiblySensitive", default=None)
+
+ previous_counts: Optional[PreviousCounts] = FieldInfo(alias="previousCounts", default=None)
+ """Engagement counts retained from a prior tweet edit."""
+
+ quoted_tweet: Optional["EmbeddedTweet"] = None
"""Quoted or retweeted tweet context.
Every object includes id, text, and engagement metrics. A zero metric can mean X
@@ -97,7 +239,7 @@ class SearchTweet(BaseModel):
available.
"""
- retweeted_tweet: Optional[EmbeddedTweet] = None
+ retweeted_tweet: Optional["EmbeddedTweet"] = None
"""Quoted or retweeted tweet context.
Every object includes id, text, and engagement metrics. A zero metric can mean X
@@ -112,3 +254,8 @@ class SearchTweet(BaseModel):
url: Optional[str] = None
"""Search result permalink."""
+
+ view_state: Optional[str] = FieldInfo(alias="viewState", default=None)
+
+
+from .embedded_tweet import EmbeddedTweet
diff --git a/src/x_twitter_scraper/types/shared/tweet_media.py b/src/x_twitter_scraper/types/shared/tweet_media.py
index 2b35fdf..4c306a7 100644
--- a/src/x_twitter_scraper/types/shared/tweet_media.py
+++ b/src/x_twitter_scraper/types/shared/tweet_media.py
@@ -4,14 +4,42 @@
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
-from typing import List, Optional
+from typing import Dict, List, Optional
from typing_extensions import Literal
from pydantic import Field as FieldInfo
from ..._models import BaseModel
-__all__ = ["TweetMedia", "VideoVariant"]
+__all__ = ["TweetMedia", "FaceRect", "FocusRect", "Sizes", "VideoVariant"]
+
+
+class FaceRect(BaseModel):
+ h: int
+
+ w: int
+
+ x: int
+
+ y: int
+
+
+class FocusRect(BaseModel):
+ h: int
+
+ w: int
+
+ x: int
+
+ y: int
+
+
+class Sizes(BaseModel):
+ h: int
+
+ resize: str
+
+ w: int
class VideoVariant(BaseModel):
@@ -33,5 +61,53 @@ class TweetMedia(BaseModel):
url: str
"""X media link from the tweet"""
+ id: Optional[str] = None
+ """X media entity ID."""
+
+ allow_download: Optional[bool] = FieldInfo(alias="allowDownload", default=None)
+ """Whether X permits direct media download."""
+
+ alt_text: Optional[str] = FieldInfo(alias="altText", default=None)
+ """Accessibility text supplied for the media."""
+
+ aspect_ratio: Optional[List[int]] = FieldInfo(alias="aspectRatio", default=None)
+ """Video aspect ratio as width and height."""
+
+ availability_status: Optional[str] = FieldInfo(alias="availabilityStatus", default=None)
+ """Media availability state reported by X."""
+
+ display_url: Optional[str] = FieldInfo(alias="displayUrl", default=None)
+ """Display-friendly media URL reported by X."""
+
+ duration_millis: Optional[int] = FieldInfo(alias="durationMillis", default=None)
+ """Video duration in milliseconds."""
+
+ expanded_url: Optional[str] = FieldInfo(alias="expandedUrl", default=None)
+ """Expanded X media URL."""
+
+ face_rects: Optional[Dict[str, List[FaceRect]]] = FieldInfo(alias="faceRects", default=None)
+ """Face-aware crop rectangles grouped by media size."""
+
+ focus_rects: Optional[List[FocusRect]] = FieldInfo(alias="focusRects", default=None)
+ """Suggested image crops reported by X."""
+
+ height: Optional[int] = None
+ """Original media height."""
+
+ indices: Optional[List[int]] = None
+ """Media entity offsets in the tweet text."""
+
+ media_key: Optional[str] = FieldInfo(alias="mediaKey", default=None)
+ """Stable X media key."""
+
+ monetizable: Optional[bool] = None
+ """Whether X reports the media as monetizable."""
+
+ sizes: Optional[Dict[str, Sizes]] = None
+ """Named media renditions and resize modes."""
+
video_variants: Optional[List[VideoVariant]] = FieldInfo(alias="videoVariants", default=None)
"""Available video encodings, ordered as returned"""
+
+ width: Optional[int] = None
+ """Original media width."""
diff --git a/src/x_twitter_scraper/types/shared/user_profile.py b/src/x_twitter_scraper/types/shared/user_profile.py
index d863bcc..2686cd9 100644
--- a/src/x_twitter_scraper/types/shared/user_profile.py
+++ b/src/x_twitter_scraper/types/shared/user_profile.py
@@ -10,7 +10,41 @@
from ..._models import BaseModel
-__all__ = ["UserProfile"]
+__all__ = ["UserProfile", "AffiliatesHighlightedLabel", "HighlightsInfo", "IdentityVerification"]
+
+
+class AffiliatesHighlightedLabel(BaseModel):
+ """Organization affiliation label shown on an X profile."""
+
+ badge_url: Optional[str] = FieldInfo(alias="badgeUrl", default=None)
+
+ description: Optional[str] = None
+
+ url: Optional[str] = None
+
+ url_type: Optional[str] = FieldInfo(alias="urlType", default=None)
+
+ user_label_display_type: Optional[str] = FieldInfo(alias="userLabelDisplayType", default=None)
+
+ user_label_type: Optional[str] = FieldInfo(alias="userLabelType", default=None)
+
+
+class HighlightsInfo(BaseModel):
+ """Profile highlight availability and count metadata."""
+
+ can_highlight_tweets: Optional[bool] = FieldInfo(alias="canHighlightTweets", default=None)
+
+ highlighted_tweets: Optional[str] = FieldInfo(alias="highlightedTweets", default=None)
+
+
+class IdentityVerification(BaseModel):
+ """Identity verification metadata displayed by X."""
+
+ description: Optional[str] = None
+
+ is_identity_verified: Optional[bool] = FieldInfo(alias="isIdentityVerified", default=None)
+
+ verified_since_msec: Optional[str] = FieldInfo(alias="verifiedSinceMsec", default=None)
class UserProfile(BaseModel):
@@ -22,9 +56,14 @@ class UserProfile(BaseModel):
username: str
+ affiliates_highlighted_label: Optional[AffiliatesHighlightedLabel] = FieldInfo(
+ alias="affiliatesHighlightedLabel", default=None
+ )
+ """Organization affiliation label shown on an X profile."""
+
automated_by: Optional[str] = FieldInfo(alias="automatedBy", default=None)
- can_dm: Optional[bool] = FieldInfo(alias="canDm", default=None)
+ business_account_affiliates_count: Optional[int] = FieldInfo(alias="businessAccountAffiliatesCount", default=None)
community_role: Optional[str] = FieldInfo(alias="communityRole", default=None)
"""Community role when returned by community member reads"""
@@ -33,6 +72,8 @@ class UserProfile(BaseModel):
created_at: Optional[str] = FieldInfo(alias="createdAt", default=None)
+ creator_subscriptions_count: Optional[int] = FieldInfo(alias="creatorSubscriptionsCount", default=None)
+
description: Optional[str] = None
favourites_count: Optional[int] = FieldInfo(alias="favouritesCount", default=None)
@@ -43,11 +84,25 @@ class UserProfile(BaseModel):
has_custom_timelines: Optional[bool] = FieldInfo(alias="hasCustomTimelines", default=None)
+ has_graduated_access: Optional[bool] = FieldInfo(alias="hasGraduatedAccess", default=None)
+
+ has_hidden_subscriptions_on_profile: Optional[bool] = FieldInfo(
+ alias="hasHiddenSubscriptionsOnProfile", default=None
+ )
+
+ highlights_info: Optional[HighlightsInfo] = FieldInfo(alias="highlightsInfo", default=None)
+ """Profile highlight availability and count metadata."""
+
+ identity_verification: Optional[IdentityVerification] = FieldInfo(alias="identityVerification", default=None)
+ """Identity verification metadata displayed by X."""
+
is_automated: Optional[bool] = FieldInfo(alias="isAutomated", default=None)
is_blue_verified: Optional[bool] = FieldInfo(alias="isBlueVerified", default=None)
"""Whether X shows a blue verification badge"""
+ is_profile_translatable: Optional[bool] = FieldInfo(alias="isProfileTranslatable", default=None)
+
is_translator: Optional[bool] = FieldInfo(alias="isTranslator", default=None)
is_verified: Optional[bool] = FieldInfo(alias="isVerified", default=None)
@@ -57,6 +112,8 @@ class UserProfile(BaseModel):
media_count: Optional[int] = FieldInfo(alias="mediaCount", default=None)
+ parody_commentary_fan_label: Optional[str] = FieldInfo(alias="parodyCommentaryFanLabel", default=None)
+
pinned_tweet_ids: Optional[List[str]] = FieldInfo(alias="pinnedTweetIds", default=None)
possibly_sensitive: Optional[bool] = FieldInfo(alias="possiblySensitive", default=None)
@@ -67,13 +124,25 @@ class UserProfile(BaseModel):
profile_banner_url: Optional[str] = FieldInfo(alias="profileBannerUrl", default=None)
"""Original X profile banner field when available"""
+ profile_description_language: Optional[str] = FieldInfo(alias="profileDescriptionLanguage", default=None)
+
+ profile_image_shape: Optional[str] = FieldInfo(alias="profileImageShape", default=None)
+
+ profile_interstitial_type: Optional[str] = FieldInfo(alias="profileInterstitialType", default=None)
+
profile_picture: Optional[str] = FieldInfo(alias="profilePicture", default=None)
+ profile_sort_enabled: Optional[bool] = FieldInfo(alias="profileSortEnabled", default=None)
+
+ profile_translator_type: Optional[str] = FieldInfo(alias="profileTranslatorType", default=None)
+
protected: Optional[bool] = None
"""Whether the profile protects its posts"""
statuses_count: Optional[int] = FieldInfo(alias="statusesCount", default=None)
+ super_follow_eligible: Optional[bool] = FieldInfo(alias="superFollowEligible", default=None)
+
unavailable: Optional[bool] = None
unavailable_reason: Optional[str] = FieldInfo(alias="unavailableReason", default=None)
@@ -84,10 +153,4 @@ class UserProfile(BaseModel):
verified_type: Optional[str] = FieldInfo(alias="verifiedType", default=None)
- viewer_followed_by: Optional[bool] = FieldInfo(alias="viewerFollowedBy", default=None)
- """Whether this profile follows the authenticated viewer"""
-
- viewer_following: Optional[bool] = FieldInfo(alias="viewerFollowing", default=None)
- """Whether the authenticated viewer follows this profile"""
-
withheld_in_countries: Optional[List[str]] = FieldInfo(alias="withheldInCountries", default=None)
diff --git a/src/x_twitter_scraper/types/support/ticket_list_response.py b/src/x_twitter_scraper/types/support/ticket_list_response.py
index 44c4b55..0a80685 100644
--- a/src/x_twitter_scraper/types/support/ticket_list_response.py
+++ b/src/x_twitter_scraper/types/support/ticket_list_response.py
@@ -4,8 +4,9 @@
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
-from typing import List, Optional
+from typing import List
from datetime import datetime
+from typing_extensions import Literal
from pydantic import Field as FieldInfo
@@ -15,18 +16,18 @@
class Ticket(BaseModel):
- created_at: Optional[datetime] = FieldInfo(alias="createdAt", default=None)
+ created_at: datetime = FieldInfo(alias="createdAt")
- message_count: Optional[int] = FieldInfo(alias="messageCount", default=None)
+ message_count: int = FieldInfo(alias="messageCount")
- public_id: Optional[str] = FieldInfo(alias="publicId", default=None)
+ public_id: str = FieldInfo(alias="publicId")
- status: Optional[str] = None
+ status: Literal["open", "in_progress", "resolved", "closed"]
- subject: Optional[str] = None
+ subject: str
- updated_at: Optional[datetime] = FieldInfo(alias="updatedAt", default=None)
+ updated_at: datetime = FieldInfo(alias="updatedAt")
class TicketListResponse(BaseModel):
- tickets: Optional[List[Ticket]] = None
+ tickets: List[Ticket]
diff --git a/src/x_twitter_scraper/types/support/ticket_retrieve_response.py b/src/x_twitter_scraper/types/support/ticket_retrieve_response.py
index eaff19b..274d150 100644
--- a/src/x_twitter_scraper/types/support/ticket_retrieve_response.py
+++ b/src/x_twitter_scraper/types/support/ticket_retrieve_response.py
@@ -4,7 +4,7 @@
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
-from typing import List, Optional
+from typing import List
from datetime import datetime
from typing_extensions import Literal
@@ -39,24 +39,24 @@ class MessageAttachment(BaseModel):
class Message(BaseModel):
- attachments: Optional[List[MessageAttachment]] = None
+ attachments: List[MessageAttachment]
- body: Optional[str] = None
+ body: str
- created_at: Optional[datetime] = FieldInfo(alias="createdAt", default=None)
+ created_at: datetime = FieldInfo(alias="createdAt")
- sender: Optional[str] = None
+ sender: Literal["user", "support", "system"]
class TicketRetrieveResponse(BaseModel):
- created_at: Optional[datetime] = FieldInfo(alias="createdAt", default=None)
+ created_at: datetime = FieldInfo(alias="createdAt")
- messages: Optional[List[Message]] = None
+ messages: List[Message]
- public_id: Optional[str] = FieldInfo(alias="publicId", default=None)
+ public_id: str = FieldInfo(alias="publicId")
- status: Optional[str] = None
+ status: Literal["open", "in_progress", "resolved", "closed"]
- subject: Optional[str] = None
+ subject: str
- updated_at: Optional[datetime] = FieldInfo(alias="updatedAt", default=None)
+ updated_at: datetime = FieldInfo(alias="updatedAt")
diff --git a/src/x_twitter_scraper/types/support/ticket_update_response.py b/src/x_twitter_scraper/types/support/ticket_update_response.py
index 59d6a5a..a7ebc97 100644
--- a/src/x_twitter_scraper/types/support/ticket_update_response.py
+++ b/src/x_twitter_scraper/types/support/ticket_update_response.py
@@ -4,7 +4,7 @@
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
-from typing import Optional
+from typing_extensions import Literal
from pydantic import Field as FieldInfo
@@ -14,6 +14,6 @@
class TicketUpdateResponse(BaseModel):
- public_id: Optional[str] = FieldInfo(alias="publicId", default=None)
+ public_id: str = FieldInfo(alias="publicId")
- status: Optional[str] = None
+ status: Literal["open", "resolved", "closed"]
diff --git a/src/x_twitter_scraper/types/trend_list_response.py b/src/x_twitter_scraper/types/trend_list_response.py
index f5b4e6f..917d36a 100644
--- a/src/x_twitter_scraper/types/trend_list_response.py
+++ b/src/x_twitter_scraper/types/trend_list_response.py
@@ -6,6 +6,8 @@
from typing import List, Optional
+from pydantic import Field as FieldInfo
+
from .._models import BaseModel
__all__ = ["TrendListResponse", "Trend"]
@@ -16,10 +18,19 @@ class Trend(BaseModel):
description: Optional[str] = None
+ promoted_content: Optional[str] = FieldInfo(alias="promotedContent", default=None)
+ """Promotion identifier from X. Null for organic trends."""
+
query: Optional[str] = None
rank: Optional[int] = None
+ tweet_volume: Optional[int] = FieldInfo(alias="tweetVolume", default=None)
+ """Approximate public post volume when X supplies it."""
+
+ url: Optional[str] = None
+ """X search URL for the trend."""
+
class TrendListResponse(BaseModel):
total: int
diff --git a/src/x_twitter_scraper/types/x/__init__.py b/src/x_twitter_scraper/types/x/__init__.py
index a2de5e3..dfb924b 100644
--- a/src/x_twitter_scraper/types/x/__init__.py
+++ b/src/x_twitter_scraper/types/x/__init__.py
@@ -27,6 +27,7 @@
from .profile_update_params import ProfileUpdateParams as ProfileUpdateParams
from .tweet_create_response import TweetCreateResponse as TweetCreateResponse
from .tweet_delete_response import TweetDeleteResponse as TweetDeleteResponse
+from .account_create_response import AccountCreateResponse as AccountCreateResponse
from .account_delete_response import AccountDeleteResponse as AccountDeleteResponse
from .account_reauth_response import AccountReauthResponse as AccountReauthResponse
from .community_create_params import CommunityCreateParams as CommunityCreateParams
@@ -41,6 +42,7 @@
from .community_create_response import CommunityCreateResponse as CommunityCreateResponse
from .community_delete_response import CommunityDeleteResponse as CommunityDeleteResponse
from .dm_retrieve_history_params import DmRetrieveHistoryParams as DmRetrieveHistoryParams
+from .tweet_get_replies_response import TweetGetRepliesResponse as TweetGetRepliesResponse
from .user_retrieve_batch_params import UserRetrieveBatchParams as UserRetrieveBatchParams
from .user_retrieve_likes_params import UserRetrieveLikesParams as UserRetrieveLikesParams
from .user_retrieve_media_params import UserRetrieveMediaParams as UserRetrieveMediaParams
diff --git a/src/x_twitter_scraper/types/x/account_connection_attempt_retrieve_response.py b/src/x_twitter_scraper/types/x/account_connection_attempt_retrieve_response.py
index 7f8592f..10a169d 100644
--- a/src/x_twitter_scraper/types/x/account_connection_attempt_retrieve_response.py
+++ b/src/x_twitter_scraper/types/x/account_connection_attempt_retrieve_response.py
@@ -2,10 +2,11 @@
from typing import Union, Optional
from datetime import datetime
-from typing_extensions import Literal, TypeAlias
+from typing_extensions import Literal, Annotated, TypeAlias
from pydantic import Field as FieldInfo
+from ..._utils import PropertyInfo
from ..._models import BaseModel
__all__ = [
@@ -74,9 +75,12 @@ class XAccountConnectionChallenge(BaseModel):
username: str
-AccountConnectionAttemptRetrieveResponse: TypeAlias = Union[
- XAccountConnectionAttemptPending,
- XAccountConnectionAttemptSuccess,
- XAccountConnectionAttemptFailed,
- XAccountConnectionChallenge,
+AccountConnectionAttemptRetrieveResponse: TypeAlias = Annotated[
+ Union[
+ XAccountConnectionAttemptPending,
+ XAccountConnectionAttemptSuccess,
+ XAccountConnectionAttemptFailed,
+ XAccountConnectionChallenge,
+ ],
+ PropertyInfo(discriminator="status"),
]
diff --git a/src/x_twitter_scraper/types/x/account_connection_challenge_submit_response.py b/src/x_twitter_scraper/types/x/account_connection_challenge_submit_response.py
index d131559..695537a 100644
--- a/src/x_twitter_scraper/types/x/account_connection_challenge_submit_response.py
+++ b/src/x_twitter_scraper/types/x/account_connection_challenge_submit_response.py
@@ -23,7 +23,7 @@ class AccountConnectionChallengeSubmitResponse(BaseModel):
health: Literal["healthy", "locked", "needsReauth", "recovering", "suspended", "temporaryIssue"]
- status: str
+ status: Literal["active"]
x_user_id: str = FieldInfo(alias="xUserId")
diff --git a/src/x_twitter_scraper/types/x/account_create_response.py b/src/x_twitter_scraper/types/x/account_create_response.py
new file mode 100644
index 0000000..96651ae
--- /dev/null
+++ b/src/x_twitter_scraper/types/x/account_create_response.py
@@ -0,0 +1,68 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Union
+from datetime import datetime
+from typing_extensions import Literal, TypeAlias
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+
+__all__ = [
+ "AccountCreateResponse",
+ "SanitizedXAccount",
+ "XAccountConnectionAttemptPending",
+ "XAccountConnectionChallenge",
+]
+
+
+class SanitizedXAccount(BaseModel):
+ """Sanitized X account summary returned by connect and reauth."""
+
+ id: str
+
+ created_at: datetime = FieldInfo(alias="createdAt")
+
+ health: Literal["healthy", "locked", "needsReauth", "recovering", "suspended", "temporaryIssue"]
+
+ status: Literal["active"]
+
+ x_user_id: str = FieldInfo(alias="xUserId")
+
+ x_username: str = FieldInfo(alias="xUsername")
+
+
+class XAccountConnectionAttemptPending(BaseModel):
+ """The connection is still in progress."""
+
+ id: str
+
+ object: Literal["x_account_connection_attempt"]
+
+ poll_after_ms: int = FieldInfo(alias="pollAfterMs")
+
+ status: Literal["pending"]
+
+
+class XAccountConnectionChallenge(BaseModel):
+ """Resumable account connection challenge.
+
+ Submit the email code to finish the same connection attempt.
+ """
+
+ id: str
+
+ expires_at: datetime = FieldInfo(alias="expiresAt")
+
+ message: str
+
+ object: Literal["x_account_connection_challenge"]
+
+ status: Literal["requires_email_code"]
+
+ username: str
+
+
+AccountCreateResponse: TypeAlias = Union[
+ SanitizedXAccount, XAccountConnectionAttemptPending, XAccountConnectionChallenge
+]
diff --git a/src/x_twitter_scraper/types/x/account_reauth_response.py b/src/x_twitter_scraper/types/x/account_reauth_response.py
index 9e1d481..f52b1c5 100644
--- a/src/x_twitter_scraper/types/x/account_reauth_response.py
+++ b/src/x_twitter_scraper/types/x/account_reauth_response.py
@@ -23,7 +23,7 @@ class AccountReauthResponse(BaseModel):
health: Literal["healthy", "locked", "needsReauth", "recovering", "suspended", "temporaryIssue"]
- status: str
+ status: Literal["active"]
x_user_id: str = FieldInfo(alias="xUserId")
diff --git a/src/x_twitter_scraper/types/x/communities/tweet_list_by_community_params.py b/src/x_twitter_scraper/types/x/communities/tweet_list_by_community_params.py
index a5b683d..8bc6fe9 100644
--- a/src/x_twitter_scraper/types/x/communities/tweet_list_by_community_params.py
+++ b/src/x_twitter_scraper/types/x/communities/tweet_list_by_community_params.py
@@ -18,10 +18,8 @@ class TweetListByCommunityParams(TypedDict, total=False):
"""Pagination cursor for community tweets"""
page_size: Annotated[int, PropertyInfo(alias="pageSize")]
- """Maximum items requested from this page (1-100, default 20).
+ """Maximum page items (1-100, default 20).
- The response can contain fewer items because the source returned fewer, filters
- removed items, or remaining credits cover fewer results. Keep requesting
- next_cursor while has_next_page is true, even when a page is empty. The
- deprecated limit and count aliases remain accepted.
+ Source, filters, or credits can reduce results. Continue while has_next_page is
+ true. Deprecated limit and count aliases remain accepted.
"""
diff --git a/src/x_twitter_scraper/types/x/communities/tweet_list_params.py b/src/x_twitter_scraper/types/x/communities/tweet_list_params.py
index 8ea8138..792a790 100644
--- a/src/x_twitter_scraper/types/x/communities/tweet_list_params.py
+++ b/src/x_twitter_scraper/types/x/communities/tweet_list_params.py
@@ -24,12 +24,10 @@ class TweetListParams(TypedDict, total=False):
"""Pagination cursor for community results"""
page_size: Annotated[int, PropertyInfo(alias="pageSize")]
- """Maximum items requested from this page (1-100, default 20).
+ """Maximum page items (1-100, default 20).
- The response can contain fewer items because the source returned fewer, filters
- removed items, or remaining credits cover fewer results. Keep requesting
- next_cursor while has_next_page is true, even when a page is empty. The
- deprecated limit and count aliases remain accepted.
+ Source, filters, or credits can reduce results. Continue while has_next_page is
+ true. Deprecated limit and count aliases remain accepted.
"""
query_type: Annotated[Literal["Latest", "Top"], PropertyInfo(alias="queryType")]
diff --git a/src/x_twitter_scraper/types/x/community_retrieve_info_response.py b/src/x_twitter_scraper/types/x/community_retrieve_info_response.py
index e77004a..84afb73 100644
--- a/src/x_twitter_scraper/types/x/community_retrieve_info_response.py
+++ b/src/x_twitter_scraper/types/x/community_retrieve_info_response.py
@@ -57,9 +57,6 @@ class Community(BaseModel):
invites_policy: Optional[str] = None
"""Invitation policy"""
- is_member: Optional[bool] = None
- """Whether the authenticated viewer is a member"""
-
is_nsfw: Optional[bool] = None
"""Whether the community is marked sensitive"""
@@ -78,9 +75,6 @@ class Community(BaseModel):
primary_topic: Optional[CommunityPrimaryTopic] = None
"""Primary topic"""
- role: Optional[str] = None
- """Authenticated viewer's community role"""
-
rules: Optional[List[CommunityRule]] = None
"""Community rules"""
diff --git a/src/x_twitter_scraper/types/x/community_retrieve_search_params.py b/src/x_twitter_scraper/types/x/community_retrieve_search_params.py
index bccf151..a3edeff 100644
--- a/src/x_twitter_scraper/types/x/community_retrieve_search_params.py
+++ b/src/x_twitter_scraper/types/x/community_retrieve_search_params.py
@@ -24,12 +24,10 @@ class CommunityRetrieveSearchParams(TypedDict, total=False):
"""Pagination cursor for community search"""
page_size: Annotated[int, PropertyInfo(alias="pageSize")]
- """Maximum items requested from this page (1-100, default 20).
+ """Maximum page items (1-100, default 20).
- The response can contain fewer items because the source returned fewer, filters
- removed items, or remaining credits cover fewer results. Keep requesting
- next_cursor while has_next_page is true, even when a page is empty. The
- deprecated limit and count aliases remain accepted.
+ Source, filters, or credits can reduce results. Continue while has_next_page is
+ true. Deprecated limit and count aliases remain accepted.
"""
query_type: Annotated[Literal["Latest", "Top"], PropertyInfo(alias="queryType")]
diff --git a/src/x_twitter_scraper/types/x/list_retrieve_tweets_params.py b/src/x_twitter_scraper/types/x/list_retrieve_tweets_params.py
index b53749c..d283cd2 100644
--- a/src/x_twitter_scraper/types/x/list_retrieve_tweets_params.py
+++ b/src/x_twitter_scraper/types/x/list_retrieve_tweets_params.py
@@ -21,12 +21,10 @@ class ListRetrieveTweetsParams(TypedDict, total=False):
"""Include replies (default false)"""
page_size: Annotated[int, PropertyInfo(alias="pageSize")]
- """Maximum items requested from this page (1-100, default 20).
+ """Maximum page items (1-100, default 20).
- The response can contain fewer items because the source returned fewer, filters
- removed items, or remaining credits cover fewer results. Keep requesting
- next_cursor while has_next_page is true, even when a page is empty. The
- deprecated limit and count aliases remain accepted.
+ Source, filters, or credits can reduce results. Continue while has_next_page is
+ true. Deprecated limit and count aliases remain accepted.
"""
since_time: Annotated[str, PropertyInfo(alias="sinceTime")]
diff --git a/src/x_twitter_scraper/types/x/tweet_author.py b/src/x_twitter_scraper/types/x/tweet_author.py
index 7785a1f..4654a6f 100644
--- a/src/x_twitter_scraper/types/x/tweet_author.py
+++ b/src/x_twitter_scraper/types/x/tweet_author.py
@@ -15,4 +15,6 @@ class TweetAuthor(UserProfile):
The lookup route always includes follower count and verification state. Other profile fields appear when available.
"""
- pass
+ followers: int # type: ignore
+
+ verified: bool # type: ignore
diff --git a/src/x_twitter_scraper/types/x/tweet_detail.py b/src/x_twitter_scraper/types/x/tweet_detail.py
index 1dd674b..ba05a82 100644
--- a/src/x_twitter_scraper/types/x/tweet_detail.py
+++ b/src/x_twitter_scraper/types/x/tweet_detail.py
@@ -4,6 +4,8 @@
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+from __future__ import annotations
+
from typing import Dict, List, Optional
from pydantic import Field as FieldInfo
@@ -11,10 +13,125 @@
from ..._models import BaseModel
from .tweet_author import TweetAuthor
from ..shared.tweet_media import TweetMedia
-from ..shared.embedded_tweet import EmbeddedTweet
from ..shared.content_disclosure import ContentDisclosure
-__all__ = ["TweetDetail"]
+__all__ = [
+ "TweetDetail",
+ "Article",
+ "Card",
+ "CommunityNote",
+ "Edit",
+ "NoteTweet",
+ "NoteTweetRichtextTag",
+ "Place",
+ "PreviousCounts",
+]
+
+
+class Article(BaseModel):
+ """Article metadata attached to a tweet."""
+
+ id: Optional[str] = None
+
+ cover_media_url: Optional[str] = FieldInfo(alias="coverMediaUrl", default=None)
+
+ preview_text: Optional[str] = FieldInfo(alias="previewText", default=None)
+
+ title: Optional[str] = None
+
+
+class Card(BaseModel):
+ """Public card metadata attached to a tweet."""
+
+ id: Optional[str] = None
+
+ binding_values: Optional[Dict[str, object]] = FieldInfo(alias="bindingValues", default=None)
+
+ name: Optional[str] = None
+
+ url: Optional[str] = None
+
+
+class CommunityNote(BaseModel):
+ """Community Note presentation metadata returned by X."""
+
+ id: Optional[str] = None
+
+ destination_url: Optional[str] = FieldInfo(alias="destinationUrl", default=None)
+
+ footer: Optional[str] = None
+
+ short_title: Optional[str] = FieldInfo(alias="shortTitle", default=None)
+
+ subtitle: Optional[str] = None
+
+ title: Optional[str] = None
+
+ visual_style: Optional[str] = FieldInfo(alias="visualStyle", default=None)
+
+
+class Edit(BaseModel):
+ """Edit history metadata returned by X."""
+
+ editable_until_msecs: Optional[str] = FieldInfo(alias="editableUntilMsecs", default=None)
+
+ edit_tweet_ids: Optional[List[str]] = FieldInfo(alias="editTweetIds", default=None)
+
+
+class NoteTweetRichtextTag(BaseModel):
+ from_index: int = FieldInfo(alias="fromIndex")
+
+ to_index: int = FieldInfo(alias="toIndex")
+
+ types: List[str]
+
+
+class NoteTweet(BaseModel):
+ """Complete Note Tweet content and rich-text metadata."""
+
+ text: str
+
+ id: Optional[str] = None
+
+ entities: Optional[Dict[str, object]] = None
+
+ is_expandable: Optional[bool] = FieldInfo(alias="isExpandable", default=None)
+
+ richtext_tags: Optional[List[NoteTweetRichtextTag]] = FieldInfo(alias="richtextTags", default=None)
+
+
+class Place(BaseModel):
+ """Public place metadata attached to a tweet."""
+
+ id: Optional[str] = None
+
+ bounding_box: Optional[Dict[str, object]] = FieldInfo(alias="boundingBox", default=None)
+
+ country: Optional[str] = None
+
+ country_code: Optional[str] = FieldInfo(alias="countryCode", default=None)
+
+ full_name: Optional[str] = FieldInfo(alias="fullName", default=None)
+
+ name: Optional[str] = None
+
+ place_type: Optional[str] = FieldInfo(alias="placeType", default=None)
+
+ url: Optional[str] = None
+
+
+class PreviousCounts(BaseModel):
+ """Engagement counts retained from a prior tweet edit."""
+
+ bookmark_count: Optional[int] = FieldInfo(alias="bookmarkCount", default=None)
+
+ like_count: Optional[int] = FieldInfo(alias="likeCount", default=None)
+
+ quote_count: Optional[int] = FieldInfo(alias="quoteCount", default=None)
+
+ reply_count: Optional[int] = FieldInfo(alias="replyCount", default=None)
+
+ retweet_count: Optional[int] = FieldInfo(alias="retweetCount", default=None)
class TweetDetail(BaseModel):
@@ -39,6 +156,9 @@ class TweetDetail(BaseModel):
view_count: int = FieldInfo(alias="viewCount")
+ article: Optional[Article] = None
+ """Article metadata attached to a tweet."""
+
author: Optional[TweetAuthor] = None
"""Tweet author profile.
@@ -46,6 +166,12 @@ class TweetDetail(BaseModel):
profile fields appear when available.
"""
+ card: Optional[Card] = None
+ """Public card metadata attached to a tweet."""
+
+ community_note: Optional[CommunityNote] = FieldInfo(alias="communityNote", default=None)
+ """Community Note presentation metadata returned by X."""
+
content_disclosure: Optional[ContentDisclosure] = FieldInfo(alias="contentDisclosure", default=None)
"""
Content disclosure metadata shown by X when a tweet is labeled as paid
@@ -60,6 +186,9 @@ class TweetDetail(BaseModel):
display_text_range: Optional[List[int]] = FieldInfo(alias="displayTextRange", default=None)
"""Start and end offsets for rendered tweet text"""
+ edit: Optional[Edit] = None
+ """Edit history metadata returned by X."""
+
entities: Optional[Dict[str, object]] = None
"""Parsed entities from the tweet text (URLs, mentions, hashtags, media)"""
@@ -84,13 +213,26 @@ class TweetDetail(BaseModel):
is_reply: Optional[bool] = FieldInfo(alias="isReply", default=None)
"""Whether this tweet is a reply to another tweet"""
+ is_translatable: Optional[bool] = FieldInfo(alias="isTranslatable", default=None)
+
lang: Optional[str] = None
"""Tweet language code"""
media: Optional[List[TweetMedia]] = None
"""Attached media items, omitted when the tweet has no media"""
- quoted_tweet: Optional[EmbeddedTweet] = None
+ note_tweet: Optional[NoteTweet] = FieldInfo(alias="noteTweet", default=None)
+ """Complete Note Tweet content and rich-text metadata."""
+
+ place: Optional[Place] = None
+ """Public place metadata attached to a tweet."""
+
+ possibly_sensitive: Optional[bool] = FieldInfo(alias="possiblySensitive", default=None)
+
+ previous_counts: Optional[PreviousCounts] = FieldInfo(alias="previousCounts", default=None)
+ """Engagement counts retained from a prior tweet edit."""
+
+ quoted_tweet: Optional["EmbeddedTweet"] = None
"""Quoted or retweeted tweet context.
Every object includes id, text, and engagement metrics. A zero metric can mean X
@@ -98,7 +240,7 @@ class TweetDetail(BaseModel):
available.
"""
- retweeted_tweet: Optional[EmbeddedTweet] = None
+ retweeted_tweet: Optional["EmbeddedTweet"] = None
"""Quoted or retweeted tweet context.
Every object includes id, text, and engagement metrics. A zero metric can mean X
@@ -114,3 +256,8 @@ class TweetDetail(BaseModel):
url: Optional[str] = None
"""Tweet permalink URL"""
+
+ view_state: Optional[str] = FieldInfo(alias="viewState", default=None)
+
+
+from ..shared.embedded_tweet import EmbeddedTweet
diff --git a/src/x_twitter_scraper/types/x/tweet_get_quotes_params.py b/src/x_twitter_scraper/types/x/tweet_get_quotes_params.py
index d18489f..64babcd 100644
--- a/src/x_twitter_scraper/types/x/tweet_get_quotes_params.py
+++ b/src/x_twitter_scraper/types/x/tweet_get_quotes_params.py
@@ -73,12 +73,10 @@ class TweetGetQuotesParams(TypedDict, total=False):
"""Minimum retweets threshold."""
page_size: Annotated[int, PropertyInfo(alias="pageSize")]
- """Maximum items requested from this page (1-100, default 20).
+ """Maximum page items (1-100, default 20).
- The response can contain fewer items because the source returned fewer, filters
- removed items, or remaining credits cover fewer results. Keep requesting
- next_cursor while has_next_page is true, even when a page is empty. The
- deprecated limit and count aliases remain accepted.
+ Source, filters, or credits can reduce results. Continue while has_next_page is
+ true. Deprecated limit and count aliases remain accepted.
"""
quotes: Literal["include", "exclude", "only"]
diff --git a/src/x_twitter_scraper/types/x/tweet_get_replies_params.py b/src/x_twitter_scraper/types/x/tweet_get_replies_params.py
index 881bf6f..f505b0d 100644
--- a/src/x_twitter_scraper/types/x/tweet_get_replies_params.py
+++ b/src/x_twitter_scraper/types/x/tweet_get_replies_params.py
@@ -49,6 +49,13 @@ class TweetGetRepliesParams(TypedDict, total=False):
language: str
"""Language code filter, e.g. en or tr."""
+ limit: int
+ """With mode=complete, maximum combined direct and nested reply rows (1-25000).
+
+ Without complete mode, this is the deprecated pageSize alias and uses the normal
+ 1-100 page range.
+ """
+
media_type: Annotated[
Literal["images", "videos", "gifs", "media", "links", "none"], PropertyInfo(alias="mediaType")
]
@@ -69,13 +76,18 @@ class TweetGetRepliesParams(TypedDict, total=False):
min_retweets: Annotated[int, PropertyInfo(alias="minRetweets")]
"""Minimum retweets threshold."""
+ mode: Literal["complete"]
+ """Set complete for maximum-coverage collection.
+
+ Complete mode accepts only limit. Remove cursor, pageSize, count, time ranges,
+ and tweet filters.
+ """
+
page_size: Annotated[int, PropertyInfo(alias="pageSize")]
- """Maximum items requested from this page (1-100, default 20).
+ """Maximum page items (1-100, default 20).
- The response can contain fewer items because the source returned fewer, filters
- removed items, or remaining credits cover fewer results. Keep requesting
- next_cursor while has_next_page is true, even when a page is empty. The
- deprecated limit and count aliases remain accepted.
+ Source, filters, or credits can reduce results. Continue while has_next_page is
+ true. Deprecated limit and count aliases remain accepted.
"""
quotes: Literal["include", "exclude", "only"]
diff --git a/src/x_twitter_scraper/types/x/tweet_get_replies_response.py b/src/x_twitter_scraper/types/x/tweet_get_replies_response.py
new file mode 100644
index 0000000..5b55421
--- /dev/null
+++ b/src/x_twitter_scraper/types/x/tweet_get_replies_response.py
@@ -0,0 +1,151 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import List, Optional
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+from ..shared.paginated_tweets import PaginatedTweets
+
+__all__ = [
+ "TweetGetRepliesResponse",
+ "TweetGetRepliesResponseDiagnostic",
+ "TweetGetRepliesResponseDiagnosticRichness",
+ "TweetGetRepliesResponseDiagnosticStrategiesAttempted",
+]
+
+
+class TweetGetRepliesResponseDiagnosticRichness(BaseModel):
+ """Field-presence counts across the collected direct replies."""
+
+ article: int
+ """Replies with article content."""
+
+ author: int
+ """Replies with author details."""
+
+ card: int
+ """Replies with card metadata."""
+
+ community_note: int = FieldInfo(alias="communityNote")
+ """Replies with community-note data."""
+
+ created_at: int = FieldInfo(alias="createdAt")
+ """Replies with a creation timestamp."""
+
+ engagement_counts: int = FieldInfo(alias="engagementCounts")
+ """Replies with engagement counts."""
+
+ entities: int
+ """Replies with entity metadata."""
+
+ language: int
+ """Replies with a language value."""
+
+ media: int
+ """Replies with media metadata."""
+
+ quoted_or_reposted_tweet: int = FieldInfo(alias="quotedOrRepostedTweet")
+ """Replies with quoted or reposted tweet data."""
+
+ text: int
+ """Replies with text."""
+
+ total_replies: int = FieldInfo(alias="totalReplies")
+ """Total unique direct replies evaluated for richness."""
+
+ url: int
+ """Replies with a canonical URL."""
+
+
+class TweetGetRepliesResponseDiagnosticStrategiesAttempted(BaseModel):
+ name: str
+
+ new_direct_replies: int = FieldInfo(alias="newDirectReplies")
+
+ new_nested_replies: int = FieldInfo(alias="newNestedReplies")
+
+ pages_attempted: int = FieldInfo(alias="pagesAttempted")
+
+ stop_reason: Literal[
+ "deadline", "empty_pages", "error", "missing_cursor", "no_next_page", "page_cap", "repeated_cursor"
+ ] = FieldInfo(alias="stopReason")
+
+
+class TweetGetRepliesResponseDiagnostic(BaseModel):
+ """Evidence for direct-reply coverage and collector behavior."""
+
+ complete: bool
+ """Whether coverage met the target without truncation."""
+
+ coverage_percentage: float = FieldInfo(alias="coveragePercentage")
+ """Unique direct replies as a percentage of the reported count."""
+
+ cursor_failures: int = FieldInfo(alias="cursorFailures")
+ """Cursor requests that failed."""
+
+ duplicate_count: int = FieldInfo(alias="duplicateCount")
+ """Duplicate tweet IDs removed across pages and strategies."""
+
+ empty_false_progress_pages: int = FieldInfo(alias="emptyFalseProgressPages")
+ """Empty pages rejected because they did not make progress."""
+
+ malformed_count: int = FieldInfo(alias="malformedCount")
+ """Malformed response items rejected."""
+
+ missing_response_modules_or_fields: List[str] = FieldInfo(alias="missingResponseModulesOrFields")
+ """Expected response modules or fields missing from X."""
+
+ nested_reply_count: int = FieldInfo(alias="nestedReplyCount")
+ """Unique nested replies kept outside direct coverage."""
+
+ pages_attempted: int = FieldInfo(alias="pagesAttempted")
+ """Total pages attempted across all strategies."""
+
+ recommended_fallback: str = FieldInfo(alias="recommendedFallback")
+ """Recommended next action when coverage is incomplete."""
+
+ repeated_cursor_count: int = FieldInfo(alias="repeatedCursorCount")
+ """Repeated cursors rejected to prevent loops."""
+
+ reported_reply_count: int = FieldInfo(alias="reportedReplyCount")
+ """Reply count reported on the source post."""
+
+ response_truncated: bool = FieldInfo(alias="responseTruncated")
+ """Whether the requested row limit truncated safe results."""
+
+ richness: TweetGetRepliesResponseDiagnosticRichness
+ """Field-presence counts across the collected direct replies."""
+
+ strategies_attempted: List[TweetGetRepliesResponseDiagnosticStrategiesAttempted] = FieldInfo(
+ alias="strategiesAttempted"
+ )
+ """Per-strategy pagination and contribution evidence."""
+
+ target_direct_replies: int = FieldInfo(alias="targetDirectReplies")
+ """Minimum direct replies required for the coverage target."""
+
+ unique_direct_replies: int = FieldInfo(alias="uniqueDirectReplies")
+ """Unique replies whose parent ID equals the source post ID."""
+
+ unrelated_count: int = FieldInfo(alias="unrelatedCount")
+ """Tweets rejected because they belonged elsewhere."""
+
+
+class TweetGetRepliesResponse(PaginatedTweets):
+ """Reply rows.
+
+ Complete mode also returns nested replies and coverage diagnostics. Keep nested replies separate from direct coverage.
+ """
+
+ diagnostic: Optional[TweetGetRepliesResponseDiagnostic] = None
+ """Evidence for direct-reply coverage and collector behavior."""
+
+ nested_replies: Optional[List["SearchTweet"]] = None
+ """Nested replies. Excluded from direct coverage."""
+
+
+from ..shared.search_tweet import SearchTweet
diff --git a/src/x_twitter_scraper/types/x/tweet_get_thread_params.py b/src/x_twitter_scraper/types/x/tweet_get_thread_params.py
index e31da7c..8d9250f 100644
--- a/src/x_twitter_scraper/types/x/tweet_get_thread_params.py
+++ b/src/x_twitter_scraper/types/x/tweet_get_thread_params.py
@@ -18,10 +18,8 @@ class TweetGetThreadParams(TypedDict, total=False):
"""Pagination cursor for thread tweets"""
page_size: Annotated[int, PropertyInfo(alias="pageSize")]
- """Maximum items requested from this page (1-100, default 20).
+ """Maximum page items (1-100, default 20).
- The response can contain fewer items because the source returned fewer, filters
- removed items, or remaining credits cover fewer results. Keep requesting
- next_cursor while has_next_page is true, even when a page is empty. The
- deprecated limit and count aliases remain accepted.
+ Source, filters, or credits can reduce results. Continue while has_next_page is
+ true. Deprecated limit and count aliases remain accepted.
"""
diff --git a/src/x_twitter_scraper/types/x/tweet_retrieve_response.py b/src/x_twitter_scraper/types/x/tweet_retrieve_response.py
index 253bc89..58fdf27 100644
--- a/src/x_twitter_scraper/types/x/tweet_retrieve_response.py
+++ b/src/x_twitter_scraper/types/x/tweet_retrieve_response.py
@@ -4,17 +4,18 @@
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+from __future__ import annotations
+
from typing import Optional
from ..._models import BaseModel
from .tweet_author import TweetAuthor
-from .tweet_detail import TweetDetail
__all__ = ["TweetRetrieveResponse"]
class TweetRetrieveResponse(BaseModel):
- tweet: TweetDetail
+ tweet: "TweetDetail"
"""Full tweet with text, engagement metrics, media, and metadata.
A zero metric can mean X did not report the count.
@@ -26,3 +27,6 @@ class TweetRetrieveResponse(BaseModel):
The lookup route always includes follower count and verification state. Other
profile fields appear when available.
"""
+
+
+from .tweet_detail import TweetDetail
diff --git a/src/x_twitter_scraper/types/x/user_retrieve_likes_params.py b/src/x_twitter_scraper/types/x/user_retrieve_likes_params.py
index 0a3f94c..255f9d9 100644
--- a/src/x_twitter_scraper/types/x/user_retrieve_likes_params.py
+++ b/src/x_twitter_scraper/types/x/user_retrieve_likes_params.py
@@ -70,12 +70,10 @@ class UserRetrieveLikesParams(TypedDict, total=False):
"""Minimum retweets threshold."""
page_size: Annotated[int, PropertyInfo(alias="pageSize")]
- """Maximum items requested from this page (1-100, default 20).
+ """Maximum page items (1-100, default 20).
- The response can contain fewer items because the source returned fewer, filters
- removed items, or remaining credits cover fewer results. Keep requesting
- next_cursor while has_next_page is true, even when a page is empty. The
- deprecated limit and count aliases remain accepted.
+ Source, filters, or credits can reduce results. Continue while has_next_page is
+ true. Deprecated limit and count aliases remain accepted.
"""
quotes: Literal["include", "exclude", "only"]
diff --git a/src/x_twitter_scraper/types/x/user_retrieve_media_params.py b/src/x_twitter_scraper/types/x/user_retrieve_media_params.py
index 053cebe..3730305 100644
--- a/src/x_twitter_scraper/types/x/user_retrieve_media_params.py
+++ b/src/x_twitter_scraper/types/x/user_retrieve_media_params.py
@@ -70,12 +70,10 @@ class UserRetrieveMediaParams(TypedDict, total=False):
"""Minimum retweets threshold."""
page_size: Annotated[int, PropertyInfo(alias="pageSize")]
- """Maximum items requested from this page (1-100, default 20).
+ """Maximum page items (1-100, default 20).
- The response can contain fewer items because the source returned fewer, filters
- removed items, or remaining credits cover fewer results. Keep requesting
- next_cursor while has_next_page is true, even when a page is empty. The
- deprecated limit and count aliases remain accepted.
+ Source, filters, or credits can reduce results. Continue while has_next_page is
+ true. Deprecated limit and count aliases remain accepted.
"""
quotes: Literal["include", "exclude", "only"]
diff --git a/src/x_twitter_scraper/types/x/user_retrieve_mentions_params.py b/src/x_twitter_scraper/types/x/user_retrieve_mentions_params.py
index 58b6e01..8731903 100644
--- a/src/x_twitter_scraper/types/x/user_retrieve_mentions_params.py
+++ b/src/x_twitter_scraper/types/x/user_retrieve_mentions_params.py
@@ -70,12 +70,10 @@ class UserRetrieveMentionsParams(TypedDict, total=False):
"""Minimum retweets threshold."""
page_size: Annotated[int, PropertyInfo(alias="pageSize")]
- """Maximum items requested from this page (1-100, default 20).
+ """Maximum page items (1-100, default 20).
- The response can contain fewer items because the source returned fewer, filters
- removed items, or remaining credits cover fewer results. Keep requesting
- next_cursor while has_next_page is true, even when a page is empty. The
- deprecated limit and count aliases remain accepted.
+ Source, filters, or credits can reduce results. Continue while has_next_page is
+ true. Deprecated limit and count aliases remain accepted.
"""
quotes: Literal["include", "exclude", "only"]
diff --git a/src/x_twitter_scraper/types/x/user_retrieve_replies_params.py b/src/x_twitter_scraper/types/x/user_retrieve_replies_params.py
index 911a040..fa091ad 100644
--- a/src/x_twitter_scraper/types/x/user_retrieve_replies_params.py
+++ b/src/x_twitter_scraper/types/x/user_retrieve_replies_params.py
@@ -73,12 +73,10 @@ class UserRetrieveRepliesParams(TypedDict, total=False):
"""Minimum retweets threshold."""
page_size: Annotated[int, PropertyInfo(alias="pageSize")]
- """Maximum items requested from this page (1-100, default 20).
+ """Maximum page items (1-100, default 20).
- The response can contain fewer items because the source returned fewer, filters
- removed items, or remaining credits cover fewer results. Keep requesting
- next_cursor while has_next_page is true, even when a page is empty. The
- deprecated limit and count aliases remain accepted.
+ Source, filters, or credits can reduce results. Continue while has_next_page is
+ true. Deprecated limit and count aliases remain accepted.
"""
quotes: Literal["include", "exclude", "only"]
diff --git a/src/x_twitter_scraper/types/x/user_retrieve_tweets_params.py b/src/x_twitter_scraper/types/x/user_retrieve_tweets_params.py
index 6f30e7b..d2faa4f 100644
--- a/src/x_twitter_scraper/types/x/user_retrieve_tweets_params.py
+++ b/src/x_twitter_scraper/types/x/user_retrieve_tweets_params.py
@@ -76,12 +76,10 @@ class UserRetrieveTweetsParams(TypedDict, total=False):
"""Minimum retweets threshold."""
page_size: Annotated[int, PropertyInfo(alias="pageSize")]
- """Maximum items requested from this page (1-100, default 20).
+ """Maximum page items (1-100, default 20).
- The response can contain fewer items because the source returned fewer, filters
- removed items, or remaining credits cover fewer results. Keep requesting
- next_cursor while has_next_page is true, even when a page is empty. The
- deprecated limit and count aliases remain accepted.
+ Source, filters, or credits can reduce results. Continue while has_next_page is
+ true. Deprecated limit and count aliases remain accepted.
"""
quotes: Literal["include", "exclude", "only"]
diff --git a/src/x_twitter_scraper/types/x_get_article_response.py b/src/x_twitter_scraper/types/x_get_article_response.py
index b80cc71..83eaf73 100644
--- a/src/x_twitter_scraper/types/x_get_article_response.py
+++ b/src/x_twitter_scraper/types/x_get_article_response.py
@@ -79,8 +79,6 @@ class Author(BaseModel):
username: str
- can_dm: Optional[bool] = FieldInfo(alias="canDm", default=None)
-
created_at: Optional[str] = FieldInfo(alias="createdAt", default=None)
description: Optional[str] = None
diff --git a/src/x_twitter_scraper/types/x_get_trends_response.py b/src/x_twitter_scraper/types/x_get_trends_response.py
index 43aa53b..66befe5 100644
--- a/src/x_twitter_scraper/types/x_get_trends_response.py
+++ b/src/x_twitter_scraper/types/x_get_trends_response.py
@@ -6,6 +6,8 @@
from typing import List, Optional
+from pydantic import Field as FieldInfo
+
from .._models import BaseModel
__all__ = ["XGetTrendsResponse", "Trend"]
@@ -16,10 +18,19 @@ class Trend(BaseModel):
description: Optional[str] = None
+ promoted_content: Optional[str] = FieldInfo(alias="promotedContent", default=None)
+ """Promotion identifier from X. Null for organic trends."""
+
query: Optional[str] = None
rank: Optional[int] = None
+ tweet_volume: Optional[int] = FieldInfo(alias="tweetVolume", default=None)
+ """Approximate public post volume when X supplies it."""
+
+ url: Optional[str] = None
+ """X search URL for the trend."""
+
class XGetTrendsResponse(BaseModel):
count: int
diff --git a/tests/api_resources/x/test_accounts.py b/tests/api_resources/x/test_accounts.py
index c3d34b5..878c4c8 100644
--- a/tests/api_resources/x/test_accounts.py
+++ b/tests/api_resources/x/test_accounts.py
@@ -16,6 +16,7 @@
from x_twitter_scraper.types.x import (
XAccountDetail,
AccountListResponse,
+ AccountCreateResponse,
AccountDeleteResponse,
AccountReauthResponse,
AccountBulkRetryResponse,
@@ -36,7 +37,7 @@ def test_method_create(self, client: XTwitterScraper) -> None:
totp_secret="",
username="your_x_username",
)
- assert_matches_type(object, account, path=["response"])
+ assert_matches_type(AccountCreateResponse, account, path=["response"])
@pytest.mark.skip(reason="Mock server tests are disabled")
@parametrize
@@ -51,7 +52,7 @@ def test_raw_response_create(self, client: XTwitterScraper) -> None:
assert response.is_closed is True
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
account = response.parse()
- assert_matches_type(object, account, path=["response"])
+ assert_matches_type(AccountCreateResponse, account, path=["response"])
@pytest.mark.skip(reason="Mock server tests are disabled")
@parametrize
@@ -66,7 +67,7 @@ def test_streaming_response_create(self, client: XTwitterScraper) -> None:
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
account = response.parse()
- assert_matches_type(object, account, path=["response"])
+ assert_matches_type(AccountCreateResponse, account, path=["response"])
assert cast(Any, response.is_closed) is True
@@ -282,7 +283,7 @@ async def test_method_create(self, async_client: AsyncXTwitterScraper) -> None:
totp_secret="",
username="your_x_username",
)
- assert_matches_type(object, account, path=["response"])
+ assert_matches_type(AccountCreateResponse, account, path=["response"])
@pytest.mark.skip(reason="Mock server tests are disabled")
@parametrize
@@ -297,7 +298,7 @@ async def test_raw_response_create(self, async_client: AsyncXTwitterScraper) ->
assert response.is_closed is True
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
account = await response.parse()
- assert_matches_type(object, account, path=["response"])
+ assert_matches_type(AccountCreateResponse, account, path=["response"])
@pytest.mark.skip(reason="Mock server tests are disabled")
@parametrize
@@ -312,7 +313,7 @@ async def test_streaming_response_create(self, async_client: AsyncXTwitterScrape
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
account = await response.parse()
- assert_matches_type(object, account, path=["response"])
+ assert_matches_type(AccountCreateResponse, account, path=["response"])
assert cast(Any, response.is_closed) is True
diff --git a/tests/api_resources/x/test_tweets.py b/tests/api_resources/x/test_tweets.py
index 3a3d213..6b447d9 100644
--- a/tests/api_resources/x/test_tweets.py
+++ b/tests/api_resources/x/test_tweets.py
@@ -18,6 +18,7 @@
TweetCreateResponse,
TweetDeleteResponse,
TweetRetrieveResponse,
+ TweetGetRepliesResponse,
)
from x_twitter_scraper.types.shared import PaginatedUsers, PaginatedTweets
@@ -342,7 +343,7 @@ def test_method_get_replies(self, client: XTwitterScraper) -> None:
tweet = client.x.tweets.get_replies(
id="id",
)
- assert_matches_type(PaginatedTweets, tweet, path=["response"])
+ assert_matches_type(TweetGetRepliesResponse, tweet, path=["response"])
@pytest.mark.skip(reason="Mock server tests are disabled")
@parametrize
@@ -359,12 +360,14 @@ def test_method_get_replies_with_all_params(self, client: XTwitterScraper) -> No
hashtags="hashtags",
in_reply_to_tweet_id="inReplyToTweetId",
language="language",
+ limit=1,
media_type="images",
mentioning="mentioning",
min_faves=0,
min_quotes=0,
min_replies=0,
min_retweets=0,
+ mode="complete",
page_size=1,
quotes="include",
quotes_of_tweet_id="quotesOfTweetId",
@@ -379,7 +382,7 @@ def test_method_get_replies_with_all_params(self, client: XTwitterScraper) -> No
url="url",
verified_only=True,
)
- assert_matches_type(PaginatedTweets, tweet, path=["response"])
+ assert_matches_type(TweetGetRepliesResponse, tweet, path=["response"])
@pytest.mark.skip(reason="Mock server tests are disabled")
@parametrize
@@ -391,7 +394,7 @@ def test_raw_response_get_replies(self, client: XTwitterScraper) -> None:
assert response.is_closed is True
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
tweet = response.parse()
- assert_matches_type(PaginatedTweets, tweet, path=["response"])
+ assert_matches_type(TweetGetRepliesResponse, tweet, path=["response"])
@pytest.mark.skip(reason="Mock server tests are disabled")
@parametrize
@@ -403,7 +406,7 @@ def test_streaming_response_get_replies(self, client: XTwitterScraper) -> None:
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
tweet = response.parse()
- assert_matches_type(PaginatedTweets, tweet, path=["response"])
+ assert_matches_type(TweetGetRepliesResponse, tweet, path=["response"])
assert cast(Any, response.is_closed) is True
@@ -918,7 +921,7 @@ async def test_method_get_replies(self, async_client: AsyncXTwitterScraper) -> N
tweet = await async_client.x.tweets.get_replies(
id="id",
)
- assert_matches_type(PaginatedTweets, tweet, path=["response"])
+ assert_matches_type(TweetGetRepliesResponse, tweet, path=["response"])
@pytest.mark.skip(reason="Mock server tests are disabled")
@parametrize
@@ -935,12 +938,14 @@ async def test_method_get_replies_with_all_params(self, async_client: AsyncXTwit
hashtags="hashtags",
in_reply_to_tweet_id="inReplyToTweetId",
language="language",
+ limit=1,
media_type="images",
mentioning="mentioning",
min_faves=0,
min_quotes=0,
min_replies=0,
min_retweets=0,
+ mode="complete",
page_size=1,
quotes="include",
quotes_of_tweet_id="quotesOfTweetId",
@@ -955,7 +960,7 @@ async def test_method_get_replies_with_all_params(self, async_client: AsyncXTwit
url="url",
verified_only=True,
)
- assert_matches_type(PaginatedTweets, tweet, path=["response"])
+ assert_matches_type(TweetGetRepliesResponse, tweet, path=["response"])
@pytest.mark.skip(reason="Mock server tests are disabled")
@parametrize
@@ -967,7 +972,7 @@ async def test_raw_response_get_replies(self, async_client: AsyncXTwitterScraper
assert response.is_closed is True
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
tweet = await response.parse()
- assert_matches_type(PaginatedTweets, tweet, path=["response"])
+ assert_matches_type(TweetGetRepliesResponse, tweet, path=["response"])
@pytest.mark.skip(reason="Mock server tests are disabled")
@parametrize
@@ -979,7 +984,7 @@ async def test_streaming_response_get_replies(self, async_client: AsyncXTwitterS
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
tweet = await response.parse()
- assert_matches_type(PaginatedTweets, tweet, path=["response"])
+ assert_matches_type(TweetGetRepliesResponse, tweet, path=["response"])
assert cast(Any, response.is_closed) is True
From b9c37caf3608fb16346f946b2a2259bea945f78e Mon Sep 17 00:00:00 2001
From: kriptoburak
Date: Fri, 31 Jul 2026 17:15:10 +0300
Subject: [PATCH 2/2] test: cover generated reply response unions
Signed-off-by: kriptoburak
---
src/x_twitter_scraper/types/__init__.py | 2 ++
tests/mock_api_routes.py | 5 ++++-
2 files changed, 6 insertions(+), 1 deletion(-)
diff --git a/src/x_twitter_scraper/types/__init__.py b/src/x_twitter_scraper/types/__init__.py
index 26495ac..59d1a62 100644
--- a/src/x_twitter_scraper/types/__init__.py
+++ b/src/x_twitter_scraper/types/__init__.py
@@ -119,12 +119,14 @@
# Pydantic can resolve the necessary references.
# See: https://github.com/pydantic/pydantic/issues/11250 for more context.
if _compat.PYDANTIC_V1:
+ x.tweet_get_replies_response.TweetGetRepliesResponse.update_forward_refs() # type: ignore
x.tweet_detail.TweetDetail.update_forward_refs() # type: ignore
x.tweet_retrieve_response.TweetRetrieveResponse.update_forward_refs() # type: ignore
shared.embedded_tweet.EmbeddedTweet.update_forward_refs() # type: ignore
shared.paginated_tweets.PaginatedTweets.update_forward_refs() # type: ignore
shared.search_tweet.SearchTweet.update_forward_refs() # type: ignore
else:
+ x.tweet_get_replies_response.TweetGetRepliesResponse.model_rebuild(_parent_namespace_depth=0)
x.tweet_detail.TweetDetail.model_rebuild(_parent_namespace_depth=0)
x.tweet_retrieve_response.TweetRetrieveResponse.model_rebuild(_parent_namespace_depth=0)
shared.embedded_tweet.EmbeddedTweet.model_rebuild(_parent_namespace_depth=0)
diff --git a/tests/mock_api_routes.py b/tests/mock_api_routes.py
index fba94ce..f139787 100644
--- a/tests/mock_api_routes.py
+++ b/tests/mock_api_routes.py
@@ -82,9 +82,12 @@ def _response_type(module_name: str, name: str) -> ResponseType:
response_type = getattr(module, name, None)
if isinstance(response_type, type) and issubclass(response_type, BaseModel):
return response_type
- for variant in get_args(response_type):
+ pending = list(get_args(response_type))
+ while pending:
+ variant = pending.pop(0)
if isinstance(variant, type) and issubclass(variant, BaseModel):
return variant
+ pending.extend(get_args(variant))
raise TypeError(f"{module_name}.{name} is not a response model")