# x-api-rs Python Full AI Reference This file is the Python-specific AI reference for `x-api-rs`. Use it when generating Python code. The source of truth for public signatures is `python/x_api_rs/x_api_rs.pyi`; this file mirrors that contract in a compact, task-oriented format. ## Package And Import Model Install package: ```bash pip install x-api-rs ``` Build locally: ```bash maturin develop --manifest-path src/py/Cargo.toml --features "dm upload inbox user posts communities settings search xchat" pytest tests/python/ -v ``` Current public imports: ```python from x_api_rs.web import ( Client, WebProfile, ApiResult, AuthTokenResult, DMClient, UploadClient, InboxClient, UserClient, PostsClient, CommunitiesClient, SettingsClient, SearchClient, XChatClient, ) from x_api_rs import ( XApiError, InvalidCookiesError, InvalidArgumentError, NetworkError, JsonError, RuntimeXApiError, XChatError, ) ``` The Python package name is `x-api-rs`; the import module is `x_api_rs`. Public Web APIs live under `x_api_rs.web`. Removed compatibility entries are intentionally absent: - root-level `Client` - root feature modules - `Twitter` facade classes - legacy `src/python` package paths ## Async Pattern All network operations are async. ```python import asyncio import os from x_api_rs.web import Client, WebProfile async def main() -> None: cookies = os.environ["TWITTER_COOKIES"] client = await Client.create( cookies, proxy_url=None, profile=WebProfile.default_latest(), ) result = await client.dm.send_message("123", "hello") print(result.to_dict()) asyncio.run(main()) ``` Cookies must be authenticated X Web cookies. Typical required cookies are `ct0`, `auth_token`, and `twid`. ## Result Model Most public API calls return `ApiResult`. ```python result = await client.user.get_profile("x") data: dict = result.to_dict() json_text: str = result.to_json() screen_name = result.screen_name ``` `ApiResult` wraps JSON-like Rust results. Stable helper methods: - `to_dict() -> dict[str, Any]` - `to_json() -> str` - attribute access maps object keys to Python attributes Do not assume every result has the same fields. Prefer `to_dict()` for robust code and only use attribute access when a field is known for that API response. ## Errors Typed Python exceptions: - `XApiError` - `InvalidCookiesError` - `InvalidArgumentError` - `NetworkError` - `JsonError` - `RuntimeXApiError` - `XChatError` Recommended pattern: ```python from x_api_rs import XApiError, InvalidCookiesError try: client = await Client.create(cookies) except InvalidCookiesError: raise except XApiError as exc: print(str(exc)) ``` Error messages should preserve semantics but must not be used to log cookies, auth tokens, PINs, keystore material, or environment snapshots. ## WebProfile `WebProfile` binds TLS/HTTP fingerprint behavior and HTTP header identity into one selector. Signatures: ```python WebProfile.default_latest() -> WebProfile WebProfile.from_spec(spec: str) -> WebProfile WebProfile.chrome(os: str, arch: str, version: int | None = None) -> WebProfile WebProfile.safari_mac(version: str) -> WebProfile WebProfile.safari_ios(version: str) -> WebProfile WebProfile.safari_ipad() -> WebProfile WebProfile.firefox(version: str) -> WebProfile WebProfile.edge(version: str) -> WebProfile WebProfile.okhttp(version: str) -> WebProfile WebProfile.none() -> WebProfile profile.spec() -> str ``` `chrome()` 的 `version` 仅允许当前依赖 `wreq-util` 已提供 TLS 模板、且出现在产品表中的大版本(当前 120–149,排除 121/122/125);默认基线 149。规范字符串:`chrome:{os}:{arch}[:{version}]`。 Examples: ```python profile = WebProfile.default_latest() profile = WebProfile.from_spec("chrome:windows:x64") profile = WebProfile.chrome("windows", "x64") # 默认基线 149 profile = WebProfile.chrome("windows", "x64", version=140) # 显式版本 profile = WebProfile.safari_ios("18.1.1") profile = WebProfile.okhttp("5") profile = WebProfile.none() selector = profile.spec() ``` Persist `profile.spec()`, not Python object repr output. ## Client Construction and restoration: ```python await Client.create(cookies: str, proxy_url: str | None = None, profile: WebProfile | None = None) -> Client await Client.auth_token_to_cookies(auth_token: str, proxy_url: str | None = None) -> AuthTokenResult Client.restore_environment_json(snapshot_json: str) -> Client Client.restore_environment(snapshot: dict[str, Any] | str) -> Client ``` `auth_token_to_cookies` is for accounts that only have an `auth_token` and no full cookies yet. It loads `x.com/home` with the token, collects server-issued `ct0`/`twid`, and returns `AuthTokenResult`: ```python res = await Client.auth_token_to_cookies(auth_token) # raises InvalidCookiesError if token invalid/expired res.cookies # full cookie string: "auth_token=...; ct0=...; twid=u%3D..." res.ct0 # CSRF token res.user_id # numeric user id res.auth_token # original auth_token client = await Client.create(res.cookies) ``` Helpers: ```python client.validate_cookies() -> bool client.get_cookies() -> str client.fingerprint_info() -> ApiResult client.export_environment() -> ApiResult client.export_environment_json() -> str await client.raw_get(url: str, params: list[tuple[str, str]] | None = None) -> ApiResult await client.raw_post(url: str, params: list[tuple[str, str]] | None = None, body: str | None = None) -> ApiResult client.xchat(keystore_path: str) -> XChatClient client.xchat_from_environment() -> XChatClient ``` Subclients: ```python client.dm -> DMClient client.upload -> UploadClient client.inbox -> InboxClient client.user -> UserClient client.posts -> PostsClient client.communities -> CommunitiesClient client.settings -> SettingsClient client.search -> SearchClient ``` ## DMClient ```python await client.dm.send_message(user_id: str, text: str, media_id: str | None = None, api_version: str = "v2") -> ApiResult await client.dm.send_batch(user_ids: list[str], text: str, media_ids: list[str | None] | None = None, api_version: str = "v2") -> ApiResult await client.dm.send_batch_with_custom_texts(user_ids: list[str], texts: list[str], media_ids: list[str | None] | None = None, api_version: str = "v2") -> ApiResult await client.dm.send_message_v2(user_id: str, text: str, media_id: str | None = None) -> ApiResult await client.dm.send_batch_v2(user_ids: list[str], text: str, media_ids: list[str | None] | None = None) -> ApiResult await client.dm.send_batch_with_custom_texts_v2(user_ids: list[str], texts: list[str], media_ids: list[str | None] | None = None) -> ApiResult ``` Example: ```python await client.dm.send_message("123", "hello") await client.dm.send_batch(["123", "456"], "hello everyone") ``` ## UploadClient ```python await client.upload.upload_from_bytes(data: bytes, category: str = "tweet_image", proxy_url: str | None = None, disable_proxy: bool = False) -> ApiResult await client.upload.upload_file(path: str, category: str = "tweet_image", proxy_url: str | None = None, disable_proxy: bool = False) -> ApiResult await client.upload.upload_multiple_times(data: bytes, category: str, count: int, proxy_url: str | None = None, disable_proxy: bool = False) -> ApiResult await client.upload.set_media_metadata(media_id: str, warnings: list[str] | None = None, ai_generated: bool = False, block_grok_edit: bool = False, allow_download: bool | None = None) -> ApiResult ``` Example: ```python upload = await client.upload.upload_file("image.jpg", category="tweet_image") await client.upload.set_media_metadata(upload.media_id_string, warnings=["other"], ai_generated=True) ``` ## InboxClient ```python await client.inbox.get_user_updates(active_conversation_id: str | None = None, cursor: str | None = None) -> ApiResult await client.inbox.get_conversations(max_count: int = 20) -> ApiResult await client.inbox.get_conversation_count() -> ApiResult ``` ## UserClient ```python await client.user.get_about_account(screen_name: str) -> ApiResult await client.user.get_profile(screen_name: str) -> ApiResult await client.user.get_profile_by_id(rest_id: str) -> ApiResult await client.user.edit_profile(name: str | None = None, description: str | None = None, location: str | None = None, url: str | None = None) -> ApiResult await client.user.change_profile_image(media_id: str) -> ApiResult await client.user.change_background_image(media_id: str) -> ApiResult await client.user.get_profile_public(screen_name: str, guest_token: str) -> ApiResult await client.user.get_followers(user_id: str, cursor: str | None = None) -> ApiResult await client.user.get_following(user_id: str, cursor: str | None = None) -> ApiResult await client.user.follow(user_id: str) -> ApiResult await client.user.unfollow(user_id: str) -> ApiResult await client.user.block(user_id: str) -> ApiResult await client.user.unblock(user_id: str) -> ApiResult await client.user.remove_profile_image() -> ApiResult await client.user.remove_profile_banner() -> ApiResult await client.user.get_user_lists(count: int = 100) -> ApiResult await client.user.unsubscribe_list(list_id: str) -> ApiResult ``` ## PostsClient ```python await client.posts.create_tweet(text: str = "", media_ids: list[str] | None = None, reply_to: str | None = None, quote_tweet_id: str | None = None, community_id: str | None = None, made_with_ai: bool = False) -> ApiResult await client.posts.delete_tweet(tweet_id: str) -> ApiResult await client.posts.favorite_tweet(tweet_id: str) -> ApiResult await client.posts.unfavorite_tweet(tweet_id: str) -> ApiResult await client.posts.create_retweet(tweet_id: str) -> ApiResult # pure Repost (CreateRetweet). Body only variables.tweet_id. # Does NOT send promoted-tweet engagement_request/impression_id. # Quote retweet => create_tweet(..., quote_tweet_id=...) not create_retweet. # ApiResult fields: success, source_tweet_id, retweet_id, error_msg, http_status await client.posts.delete_retweet(tweet_id: str) -> ApiResult # Undo repost (DeleteRetweet). Pass ORIGINAL tweet_id, not retweet_id. # Success requires data.unretweet.source_tweet_results.result (HTTP 200 alone is not enough). # GraphQL errors (even with HTTP 200) => success=False, error_msg set. # ApiResult fields: success, tweet_id, error_msg, http_status await client.posts.get_tweets(user_id: str, cursor: str | None = None) -> ApiResult await client.posts.get_likes(user_id: str | None = None, cursor: str | None = None) -> ApiResult await client.posts.clear_bookmarks() -> ApiResult await client.posts.check_account_write_status() -> ApiResult # 只读判发帖能力: status=Writable/ReadOnlySuspended/Locked/LoginInvalid/HardSuspended/Unknown, writable: bool ``` ## CommunitiesClient ```python await client.communities.search_communities(query: str, cursor: str | None = None) -> ApiResult await client.communities.join_community(community_id: str) -> ApiResult ``` ## SettingsClient ```python await client.settings.get_account_settings() -> ApiResult await client.settings.update_account_settings(**kwargs: Any) -> ApiResult await client.settings.get_search_safety() -> ApiResult await client.settings.set_search_safety(filtering: bool, blocking: bool) -> ApiResult await client.settings.get_audience_settings() -> ApiResult await client.settings.set_protect_videos(enabled: bool) -> ApiResult await client.settings.get_explore_settings() -> ApiResult await client.settings.set_explore_settings(personalized_trends: bool | None = None, current_location: bool | None = None) -> ApiResult ``` ## SearchClient ```python await client.search.search_top(query: str, cursor: str | None = None) -> ApiResult await client.search.search_latest(query: str, cursor: str | None = None) -> ApiResult await client.search.search_people(query: str, cursor: str | None = None) -> ApiResult await client.search.search_media(query: str, cursor: str | None = None) -> ApiResult await client.search.search_lists(query: str, cursor: str | None = None) -> ApiResult ``` ## Raw HTTP Raw HTTP helpers reuse authenticated Web client cookies, CSRF/auth material, WebProfile, request headers, and ClientTransaction behavior. ```python result = await client.raw_get( "https://x.com/i/api/graphql/...", params=[("variables", "{}"), ("features", "{}")], ) result = await client.raw_post( "https://x.com/i/api/graphql/...", params=[("variables", "{}")], body='{"key":"value"}', ) ``` `raw_post` parses non-empty `body` as JSON. ## Environment Snapshot Export and restore: ```python snapshot_json = client.export_environment_json() restored = Client.restore_environment_json(snapshot_json) snapshot = client.export_environment().to_dict() restored = Client.restore_environment(snapshot) ``` Snapshot content can include cookies, CSRF/auth tokens, user id, proxy URL, WebProfile selector, resolved UA/profile info, request header policy, POST UA override, HTTP/2 retry count, ClientTransaction material, and optional XChat key material. Security rule: a snapshot is plaintext sensitive data. Treat it as an account credential blob, encrypt it before database storage, and never print it in logs, exceptions, snapshots, or CI output. ## XChatClient Create: ```python xchat = client.xchat("/path/to/keystore.json") ``` Restore from environment snapshot with XChat key material: ```python xchat = client.xchat_from_environment() ``` Methods: ```python await xchat.enroll(pin: str) -> ApiResult await xchat.reset_pin(old_pin: str, new_pin: str) -> ApiResult await xchat.delete_account(pin: str) -> ApiResult await xchat.is_enrolled() -> ApiResult await xchat.status() -> ApiResult await xchat.send_message(recipient_user_id: str, text: str) -> ApiResult await xchat.send_batch(user_ids: list[str], text: str) -> ApiResult await xchat.send_batch_with_custom_texts(user_ids: list[str], texts: list[str]) -> ApiResult await xchat.receive_messages(conversation_id: str, limit: int) -> ApiResult await xchat.recover_keystore(pin: str, keystore_path: str) -> ApiResult ``` Security rule: never log PINs, private keys, keystore JSON, backup blobs, cookies, tokens, or full environment snapshots. ## Common Recipes Send a DM: ```python client = await Client.create(cookies) result = await client.dm.send_message("123", "hello", media_id=None) print(result.to_dict()) ``` Upload and create a tweet: ```python upload = await client.upload.upload_file("image.jpg", category="tweet_image") tweet = await client.posts.create_tweet( text="hello", media_ids=[upload.media_id_string], ) ``` Search and page: ```python result = await client.search.search_latest("open source") data = result.to_dict() cursor = data.get("cursor") if cursor: next_page = await client.search.search_latest("open source", cursor=cursor) ``` Create a browser profile explicitly: ```python profile = WebProfile.chrome("windows", "x64") client = await Client.create(cookies, proxy_url="http://127.0.0.1:7890", profile=profile) ``` Restore a persisted environment: ```python snapshot_json = load_from_encrypted_store() client = Client.restore_environment_json(snapshot_json) ``` ## Validation Commands Use these when changing Python public API: ```bash maturin build --manifest-path src/py/Cargo.toml --features "dm upload inbox user posts communities settings search xchat" pytest tests/python/ -v ``` When changing Python public API, also check: - `src/py/src/**` - `python/x_api_rs/x_api_rs.pyi` - `python/x_api_rs/__init__.py` - `python/x_api_rs/web/__init__.py` - `docs/python/llms.txt` - `docs/python/llms-full.txt` - root `docs/llms.txt` and `docs/llms-full.txt` - examples and tests under `examples/python/` and `tests/python/`