tweetapi 2.2.0__tar.gz → 2.3.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (23) hide show
  1. {tweetapi-2.2.0 → tweetapi-2.3.0}/PKG-INFO +55 -58
  2. {tweetapi-2.2.0 → tweetapi-2.3.0}/README.md +53 -56
  3. {tweetapi-2.2.0 → tweetapi-2.3.0}/pyproject.toml +2 -2
  4. {tweetapi-2.2.0 → tweetapi-2.3.0}/tweetapi/__init__.py +1 -1
  5. {tweetapi-2.2.0 → tweetapi-2.3.0}/tweetapi/client.py +15 -7
  6. {tweetapi-2.2.0 → tweetapi-2.3.0}/tweetapi/resources/profile.py +15 -1
  7. {tweetapi-2.2.0 → tweetapi-2.3.0}/tweetapi/types.py +8 -0
  8. {tweetapi-2.2.0 → tweetapi-2.3.0}/.gitignore +0 -0
  9. {tweetapi-2.2.0 → tweetapi-2.3.0}/tweetapi/errors.py +0 -0
  10. {tweetapi-2.2.0 → tweetapi-2.3.0}/tweetapi/pagination.py +0 -0
  11. {tweetapi-2.2.0 → tweetapi-2.3.0}/tweetapi/py.typed +0 -0
  12. {tweetapi-2.2.0 → tweetapi-2.3.0}/tweetapi/resources/__init__.py +0 -0
  13. {tweetapi-2.2.0 → tweetapi-2.3.0}/tweetapi/resources/auth.py +0 -0
  14. {tweetapi-2.2.0 → tweetapi-2.3.0}/tweetapi/resources/community.py +0 -0
  15. {tweetapi-2.2.0 → tweetapi-2.3.0}/tweetapi/resources/explore.py +0 -0
  16. {tweetapi-2.2.0 → tweetapi-2.3.0}/tweetapi/resources/interaction.py +0 -0
  17. {tweetapi-2.2.0 → tweetapi-2.3.0}/tweetapi/resources/list_.py +0 -0
  18. {tweetapi-2.2.0 → tweetapi-2.3.0}/tweetapi/resources/post.py +0 -0
  19. {tweetapi-2.2.0 → tweetapi-2.3.0}/tweetapi/resources/space.py +0 -0
  20. {tweetapi-2.2.0 → tweetapi-2.3.0}/tweetapi/resources/tweet.py +0 -0
  21. {tweetapi-2.2.0 → tweetapi-2.3.0}/tweetapi/resources/unencrypted_dm.py +0 -0
  22. {tweetapi-2.2.0 → tweetapi-2.3.0}/tweetapi/resources/user.py +0 -0
  23. {tweetapi-2.2.0 → tweetapi-2.3.0}/tweetapi/resources/xchat.py +0 -0
@@ -1,7 +1,7 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: tweetapi
3
- Version: 2.2.0
4
- Summary: Official Python SDK for TweetAPI — Twitter/X Data API for developers and researchers
3
+ Version: 2.3.0
4
+ Summary: Python SDK for TweetAPI's Twitter/X data and account API
5
5
  Project-URL: Homepage, https://tweetapi.com?utm_source=pypi&utm_medium=readme&utm_campaign=python-sdk
6
6
  Project-URL: Documentation, https://tweetapi.com/docs?utm_source=pypi&utm_medium=readme&utm_campaign=python-sdk
7
7
  Project-URL: Repository, https://github.com/tweetapi/python
@@ -30,9 +30,7 @@ Description-Content-Type: text/markdown
30
30
 
31
31
  # TweetAPI Python SDK
32
32
 
33
- Official Python SDK for [TweetAPI](https://tweetapi.com?utm_source=github&utm_medium=readme&utm_campaign=python-sdk) — the Twitter/X Data API for developers and researchers.
34
-
35
- Access tweets, user profiles, followers, analytics, and full interaction capabilities. 70+ endpoints with built-in error handling and type hints.
33
+ `tweetapi` is the Python SDK for [TweetAPI](https://tweetapi.com?utm_source=github&utm_medium=readme&utm_campaign=python-sdk). It provides methods for users, tweets, posts, profiles, interactions, lists, communities, Spaces, search, authentication, and direct messages.
36
34
 
37
35
  ## Install
38
36
 
@@ -40,7 +38,7 @@ Access tweets, user profiles, followers, analytics, and full interaction capabil
40
38
  pip install tweetapi
41
39
  ```
42
40
 
43
- ## Quick Start
41
+ ## Quick start
44
42
 
45
43
  ```python
46
44
  from tweetapi import TweetAPI
@@ -49,7 +47,7 @@ client = TweetAPI(api_key="YOUR_API_KEY")
49
47
 
50
48
  # Get a user profile
51
49
  user = client.user.get_by_username(username="elonmusk")
52
- print(user["data"]["followerCount"]) # 180000000
50
+ print(user["data"]["followerCount"])
53
51
 
54
52
  # Search tweets
55
53
  results = client.explore.search(query="bitcoin", type="Latest")
@@ -62,21 +60,18 @@ next_page = client.user.get_followers(
62
60
  )
63
61
  ```
64
62
 
65
- > **Get your free API key** — [100 requests, no credit card required](https://tweetapi.com?utm_source=github&utm_medium=readme&utm_campaign=python-sdk)
63
+ [Create an API key](https://tweetapi.com?utm_source=github&utm_medium=readme&utm_campaign=python-sdk) with 100 included requests. No credit card is required.
66
64
 
67
- ## Features
65
+ ## SDK behavior
68
66
 
69
- - **70+ endpoints** covering users, tweets, posts, interactions, DMs, communities, spaces, and search
70
- - **Full type hints** with TypedDict response types for IDE autocomplete
71
- - **Automatic retry with backoff** on rate limits (429) and server errors (5xx)
72
- - **Auto-pagination helpers** — iterate all pages with a simple `for` loop
73
- - **Solid error handling** with typed exceptions (`RateLimitError`, `NotFoundError`, etc.)
74
- - **Rate limit awareness** — `retry_after` respected automatically, state exposed via `client.rate_limit_info`
75
- - **Split timeouts** — separate connect and read timeouts
76
- - **Single dependency** — `requests` only
77
- - **Python 3.9+** compatible
67
+ - Public methods have type annotations, and response models use `TypedDict`.
68
+ - The client retries rate limits, server errors, timeouts, and connection failures by default; username changes disable automatic retries because the mutation is non-idempotent.
69
+ - `paginate()` yields items from cursor-based responses; `paginate_pages()` yields full response pages.
70
+ - HTTP errors map to exception classes such as `RateLimitError` and `NotFoundError`.
71
+ - Timeouts can use one value or separate connect and read values.
72
+ - The package supports Python 3.9+ and depends on `requests`.
78
73
 
79
- ## API Reference
74
+ ## API reference
80
75
 
81
76
  ### User
82
77
 
@@ -105,7 +100,7 @@ next_page = client.user.get_followers(
105
100
  |--------|-------------|
106
101
  | `client.tweet.get_details_and_conversation(tweet_id=...)` | Get tweet details and replies |
107
102
  | `client.tweet.get_details_by_ids(ids=...)` | Get multiple tweets (max 200) |
108
- | `client.tweet.get_retweets(tweet_id=...)` | Get who retweeted |
103
+ | `client.tweet.get_retweets(tweet_id=...)` | Get users who retweeted |
109
104
  | `client.tweet.get_quotes(tweet_id=...)` | Get quote tweets |
110
105
  | `client.tweet.translate(tweet_id=..., dst_lang=...)` | Translate a tweet |
111
106
 
@@ -129,6 +124,7 @@ next_page = client.user.get_followers(
129
124
  | `client.profile.banner(auth_token=..., media=..., proxy=...)` | Update profile banner |
130
125
  | `client.profile.remove_banner(auth_token=..., proxy=...)` | Remove the profile banner |
131
126
  | `client.profile.set_privacy(auth_token=..., is_private=..., proxy=...)` | Make the account public or private |
127
+ | `client.profile.update_username(auth_token=..., password=..., username=..., proxy=...)` | Update the account username |
132
128
 
133
129
  ### Interaction
134
130
 
@@ -173,8 +169,8 @@ next_page = client.user.get_followers(
173
169
  | `client.community.create_quote_with_media(...)` | Quote post with media |
174
170
  | `client.community.reply_post(...)` | Reply to community post |
175
171
  | `client.community.reply_post_with_media(...)` | Reply with media |
176
- | `client.community.join(auth_token=..., community_id=...)` | Join |
177
- | `client.community.leave(auth_token=..., community_id=...)` | Leave |
172
+ | `client.community.join(auth_token=..., community_id=...)` | Join a community |
173
+ | `client.community.leave(auth_token=..., community_id=...)` | Leave a community |
178
174
 
179
175
  ### Space
180
176
 
@@ -187,24 +183,24 @@ next_page = client.user.get_followers(
187
183
 
188
184
  | Method | Description |
189
185
  |--------|-------------|
190
- | `client.explore.search(query=..., type=...)` | Search tweets/users/photos/videos |
186
+ | `client.explore.search(query=..., type=...)` | Search tweets, users, photos, or videos |
191
187
 
192
188
  ### Auth
193
189
 
194
190
  | Method | Description |
195
191
  |--------|-------------|
196
- | `client.auth.login(username=..., password=..., proxy=..., country=...)` | Log in, get auth tokens |
192
+ | `client.auth.login(username=..., password=..., proxy=..., country=...)` | Log in and return auth tokens |
197
193
 
198
194
  `country` is the ISO 3166-1 alpha-2 code for the proxy's public egress IP (for example, `"US"`). It must match the IP used for the complete login attempt. Pass `two_factor_secret` when the account uses TOTP-based 2FA.
199
195
 
200
- ### X Chat (Encrypted DMs)
196
+ ### X Chat (encrypted DMs)
201
197
 
202
198
  | Method | Description |
203
199
  |--------|-------------|
204
200
  | `client.xchat.setup(auth_token=..., user_id=..., pin=...)` | Initialize encrypted DMs |
205
201
  | `client.xchat.get_conversations(auth_token=...)` | List conversations |
206
202
  | `client.xchat.send(auth_token=..., recipient_id=..., message=...)` | Send message |
207
- | `client.xchat.get_history(auth_token=..., conversation_id=...)` | Get history |
203
+ | `client.xchat.get_history(auth_token=..., conversation_id=...)` | Get conversation history |
208
204
  | `client.xchat.can_dm(auth_token=..., user_ids=...)` | Check DM availability |
209
205
 
210
206
  ### Unencrypted DMs
@@ -214,13 +210,13 @@ next_page = client.user.get_followers(
214
210
  | `client.dm.send_dm(auth_token=..., conversation_id=..., text=..., proxy=...)` | Send DM |
215
211
  | `client.dm.get_dm_permissions(auth_token=..., recipient_ids=...)` | Check permissions |
216
212
  | `client.dm.get_inbox_initial_state(auth_token=...)` | Get inbox state |
217
- | `client.dm.get_inbox_trusted(auth_token=..., cursor=...)` | Trusted inbox |
218
- | `client.dm.get_inbox_untrusted(auth_token=..., cursor=...)` | Message requests |
213
+ | `client.dm.get_inbox_trusted(auth_token=..., cursor=...)` | Get the trusted inbox |
214
+ | `client.dm.get_inbox_untrusted(auth_token=..., cursor=...)` | Get message requests |
219
215
  | `client.dm.get_conversation(auth_token=..., conversation_id=...)` | Get messages |
220
- | `client.dm.get_dm_user_updates(auth_token=..., cursor=...)` | DM user updates |
221
- | `client.dm.accept_conversation(auth_token=..., conversation_id=...)` | Accept request |
216
+ | `client.dm.get_dm_user_updates(auth_token=..., cursor=...)` | Get DM user updates |
217
+ | `client.dm.accept_conversation(auth_token=..., conversation_id=...)` | Accept a conversation request |
222
218
 
223
- ## Posting and Profile Media
219
+ ## Posting and profile media
224
220
 
225
221
  Tweet media accepts an existing TweetAPI media ID, a URL, or inline base64 data:
226
222
 
@@ -258,9 +254,14 @@ client.profile.banner(
258
254
  )
259
255
  client.profile.remove_banner(auth_token="AUTH_TOKEN")
260
256
  client.profile.set_privacy(auth_token="AUTH_TOKEN", is_private=True)
257
+ client.profile.update_username(
258
+ auth_token="AUTH_TOKEN",
259
+ password="ACCOUNT_PASSWORD",
260
+ username="NEW_USERNAME",
261
+ )
261
262
  ```
262
263
 
263
- Canonical list mutations live under `client.list`; the legacy interaction list helpers remain available:
264
+ Use `client.list` for list mutations. The older helpers under `client.interaction` remain available:
264
265
 
265
266
  ```python
266
267
  created = client.list.create(
@@ -293,9 +294,9 @@ client.community.create_quote_with_media(
293
294
  )
294
295
  ```
295
296
 
296
- ## Auto-Pagination
297
+ ## Pagination
297
298
 
298
- Use the `paginate()` and `paginate_pages()` helpers to iterate through all pages automatically:
299
+ `paginate()` yields individual items. `paginate_pages()` yields each full response page:
299
300
 
300
301
  ```python
301
302
  from tweetapi import TweetAPI, paginate, paginate_pages
@@ -317,18 +318,16 @@ for page in paginate_pages(
317
318
  print(f"Next cursor: {page['pagination']['nextCursor']}")
318
319
  ```
319
320
 
320
- Works with any paginated endpoint — followers, tweets, search results, list members, community posts, etc.
321
+ Each helper accepts a callable that takes a cursor and returns a response with `data` and `pagination.nextCursor`. Use `max_pages` to limit the number of fetched pages.
321
322
 
322
- ## Automatic Retry with Backoff
323
+ ## Retries
323
324
 
324
- The SDK automatically retries on transient errors with exponential backoff:
325
+ The client retries these transient failures:
325
326
 
326
- - **429 (Rate Limit)** — waits the `retry_after` duration from the API, then retries
327
- - **5xx (Server Error)** — retries with exponential backoff + jitter
328
- - **Network errors** — retries on timeouts and connection failures
329
- - **4xx (Client Error)** — never retried (400, 401, 403, 404 fail immediately)
327
+ - For a 429 response, it waits for `RateLimitError.retry_after` seconds, up to `max_retry_delay`.
328
+ - For a 5xx response, timeout, or connection failure, it uses exponential backoff with up to 25% jitter.
330
329
 
331
- Default: 3 retries, 2x backoff, 1s initial delay, 30s max delay.
330
+ By default, the client makes up to 3 retries. Its base delay starts at 1 second, doubles after each attempt, and is capped at 30 seconds. Other 4xx responses are not retried.
332
331
 
333
332
  ```python
334
333
  # Customize retry behavior
@@ -344,18 +343,17 @@ client = TweetAPI(
344
343
  client = TweetAPI(api_key="YOUR_API_KEY", max_retries=0)
345
344
  ```
346
345
 
347
- ### Rate Limit Awareness
346
+ ### Rate-limit state
348
347
 
349
- After a 429 response, the SDK exposes the last known rate limit state:
348
+ After a 429 response, `client.rate_limit_info` records the retry delay and the time the response was received:
350
349
 
351
350
  ```python
352
351
  print(client.rate_limit_info)
353
- # {"retry_after": 30, "timestamp": 1712345678.0} — or None if no 429 encountered
354
352
  ```
355
353
 
356
- ## Error Handling
354
+ ## Error handling
357
355
 
358
- The SDK raises typed exceptions you can catch and handle. With automatic retries enabled (default), you'll only see these after all retry attempts are exhausted:
356
+ The client raises retryable errors after it exhausts the configured retries. It raises other errors from the first response:
359
357
 
360
358
  ```python
361
359
  from tweetapi import (
@@ -389,11 +387,12 @@ except TweetAPIError as e:
389
387
  print(f"Error [{e.code}]: {e.message}")
390
388
  ```
391
389
 
392
- Every error includes:
393
- - `code` — API error code (e.g., `"ACCOUNT_SUSPENDED"`, `"RATE_LIMIT"`)
394
- - `status_code` — HTTP status code
395
- - `message` — Human-readable error message
396
- - `details` — Additional context (field, reason, retry_after, etc.)
390
+ Every error includes these attributes:
391
+
392
+ - `code`: API error code, such as `"ACCOUNT_SUSPENDED"` or `"RATE_LIMIT"`
393
+ - `status_code`: HTTP status code
394
+ - `message`: human-readable error message
395
+ - `details`: response context such as a field, reason, or retry delay
397
396
 
398
397
  ## Configuration
399
398
 
@@ -401,7 +400,7 @@ Every error includes:
401
400
  client = TweetAPI(
402
401
  api_key="YOUR_API_KEY", # Required
403
402
  base_url="https://...", # Optional (default: https://api.tweetapi.com)
404
- timeout=30, # Optional — single value for both connect + read
403
+ timeout=30, # Optional; one value for connect and read
405
404
  connect_timeout=10.0, # Optional (default: 10s)
406
405
  read_timeout=30.0, # Optional (default: 30s)
407
406
  max_retries=3, # Optional (default: 3, set 0 to disable)
@@ -421,8 +420,8 @@ client = TweetAPI(api_key="YOUR_API_KEY", timeout=(5, 30)) # (connect, read)
421
420
 
422
421
  ## Links
423
422
 
424
- - [Full Documentation](https://tweetapi.com/docs?utm_source=github&utm_medium=readme&utm_campaign=python-sdk)
425
- - [Get API Key (Free)](https://tweetapi.com?utm_source=github&utm_medium=readme&utm_campaign=python-sdk)
423
+ - [Documentation](https://tweetapi.com/docs?utm_source=github&utm_medium=readme&utm_campaign=python-sdk)
424
+ - [Create an API key](https://tweetapi.com?utm_source=github&utm_medium=readme&utm_campaign=python-sdk)
426
425
  - [Dashboard](https://tweetapi.com/dashboard?utm_source=github&utm_medium=readme&utm_campaign=python-sdk)
427
426
  - [Node.js SDK](https://github.com/tweetapi/node)
428
427
 
@@ -430,6 +429,4 @@ client = TweetAPI(api_key="YOUR_API_KEY", timeout=(5, 30)) # (connect, read)
430
429
 
431
430
  MIT
432
431
 
433
- ---
434
-
435
- *TweetAPI is a third-party service and is not affiliated with X Corp.*
432
+ TweetAPI is a third-party service and is not affiliated with X Corp.
@@ -1,8 +1,6 @@
1
1
  # TweetAPI Python SDK
2
2
 
3
- Official Python SDK for [TweetAPI](https://tweetapi.com?utm_source=github&utm_medium=readme&utm_campaign=python-sdk) — the Twitter/X Data API for developers and researchers.
4
-
5
- Access tweets, user profiles, followers, analytics, and full interaction capabilities. 70+ endpoints with built-in error handling and type hints.
3
+ `tweetapi` is the Python SDK for [TweetAPI](https://tweetapi.com?utm_source=github&utm_medium=readme&utm_campaign=python-sdk). It provides methods for users, tweets, posts, profiles, interactions, lists, communities, Spaces, search, authentication, and direct messages.
6
4
 
7
5
  ## Install
8
6
 
@@ -10,7 +8,7 @@ Access tweets, user profiles, followers, analytics, and full interaction capabil
10
8
  pip install tweetapi
11
9
  ```
12
10
 
13
- ## Quick Start
11
+ ## Quick start
14
12
 
15
13
  ```python
16
14
  from tweetapi import TweetAPI
@@ -19,7 +17,7 @@ client = TweetAPI(api_key="YOUR_API_KEY")
19
17
 
20
18
  # Get a user profile
21
19
  user = client.user.get_by_username(username="elonmusk")
22
- print(user["data"]["followerCount"]) # 180000000
20
+ print(user["data"]["followerCount"])
23
21
 
24
22
  # Search tweets
25
23
  results = client.explore.search(query="bitcoin", type="Latest")
@@ -32,21 +30,18 @@ next_page = client.user.get_followers(
32
30
  )
33
31
  ```
34
32
 
35
- > **Get your free API key** — [100 requests, no credit card required](https://tweetapi.com?utm_source=github&utm_medium=readme&utm_campaign=python-sdk)
33
+ [Create an API key](https://tweetapi.com?utm_source=github&utm_medium=readme&utm_campaign=python-sdk) with 100 included requests. No credit card is required.
36
34
 
37
- ## Features
35
+ ## SDK behavior
38
36
 
39
- - **70+ endpoints** covering users, tweets, posts, interactions, DMs, communities, spaces, and search
40
- - **Full type hints** with TypedDict response types for IDE autocomplete
41
- - **Automatic retry with backoff** on rate limits (429) and server errors (5xx)
42
- - **Auto-pagination helpers** — iterate all pages with a simple `for` loop
43
- - **Solid error handling** with typed exceptions (`RateLimitError`, `NotFoundError`, etc.)
44
- - **Rate limit awareness** — `retry_after` respected automatically, state exposed via `client.rate_limit_info`
45
- - **Split timeouts** — separate connect and read timeouts
46
- - **Single dependency** — `requests` only
47
- - **Python 3.9+** compatible
37
+ - Public methods have type annotations, and response models use `TypedDict`.
38
+ - The client retries rate limits, server errors, timeouts, and connection failures by default; username changes disable automatic retries because the mutation is non-idempotent.
39
+ - `paginate()` yields items from cursor-based responses; `paginate_pages()` yields full response pages.
40
+ - HTTP errors map to exception classes such as `RateLimitError` and `NotFoundError`.
41
+ - Timeouts can use one value or separate connect and read values.
42
+ - The package supports Python 3.9+ and depends on `requests`.
48
43
 
49
- ## API Reference
44
+ ## API reference
50
45
 
51
46
  ### User
52
47
 
@@ -75,7 +70,7 @@ next_page = client.user.get_followers(
75
70
  |--------|-------------|
76
71
  | `client.tweet.get_details_and_conversation(tweet_id=...)` | Get tweet details and replies |
77
72
  | `client.tweet.get_details_by_ids(ids=...)` | Get multiple tweets (max 200) |
78
- | `client.tweet.get_retweets(tweet_id=...)` | Get who retweeted |
73
+ | `client.tweet.get_retweets(tweet_id=...)` | Get users who retweeted |
79
74
  | `client.tweet.get_quotes(tweet_id=...)` | Get quote tweets |
80
75
  | `client.tweet.translate(tweet_id=..., dst_lang=...)` | Translate a tweet |
81
76
 
@@ -99,6 +94,7 @@ next_page = client.user.get_followers(
99
94
  | `client.profile.banner(auth_token=..., media=..., proxy=...)` | Update profile banner |
100
95
  | `client.profile.remove_banner(auth_token=..., proxy=...)` | Remove the profile banner |
101
96
  | `client.profile.set_privacy(auth_token=..., is_private=..., proxy=...)` | Make the account public or private |
97
+ | `client.profile.update_username(auth_token=..., password=..., username=..., proxy=...)` | Update the account username |
102
98
 
103
99
  ### Interaction
104
100
 
@@ -143,8 +139,8 @@ next_page = client.user.get_followers(
143
139
  | `client.community.create_quote_with_media(...)` | Quote post with media |
144
140
  | `client.community.reply_post(...)` | Reply to community post |
145
141
  | `client.community.reply_post_with_media(...)` | Reply with media |
146
- | `client.community.join(auth_token=..., community_id=...)` | Join |
147
- | `client.community.leave(auth_token=..., community_id=...)` | Leave |
142
+ | `client.community.join(auth_token=..., community_id=...)` | Join a community |
143
+ | `client.community.leave(auth_token=..., community_id=...)` | Leave a community |
148
144
 
149
145
  ### Space
150
146
 
@@ -157,24 +153,24 @@ next_page = client.user.get_followers(
157
153
 
158
154
  | Method | Description |
159
155
  |--------|-------------|
160
- | `client.explore.search(query=..., type=...)` | Search tweets/users/photos/videos |
156
+ | `client.explore.search(query=..., type=...)` | Search tweets, users, photos, or videos |
161
157
 
162
158
  ### Auth
163
159
 
164
160
  | Method | Description |
165
161
  |--------|-------------|
166
- | `client.auth.login(username=..., password=..., proxy=..., country=...)` | Log in, get auth tokens |
162
+ | `client.auth.login(username=..., password=..., proxy=..., country=...)` | Log in and return auth tokens |
167
163
 
168
164
  `country` is the ISO 3166-1 alpha-2 code for the proxy's public egress IP (for example, `"US"`). It must match the IP used for the complete login attempt. Pass `two_factor_secret` when the account uses TOTP-based 2FA.
169
165
 
170
- ### X Chat (Encrypted DMs)
166
+ ### X Chat (encrypted DMs)
171
167
 
172
168
  | Method | Description |
173
169
  |--------|-------------|
174
170
  | `client.xchat.setup(auth_token=..., user_id=..., pin=...)` | Initialize encrypted DMs |
175
171
  | `client.xchat.get_conversations(auth_token=...)` | List conversations |
176
172
  | `client.xchat.send(auth_token=..., recipient_id=..., message=...)` | Send message |
177
- | `client.xchat.get_history(auth_token=..., conversation_id=...)` | Get history |
173
+ | `client.xchat.get_history(auth_token=..., conversation_id=...)` | Get conversation history |
178
174
  | `client.xchat.can_dm(auth_token=..., user_ids=...)` | Check DM availability |
179
175
 
180
176
  ### Unencrypted DMs
@@ -184,13 +180,13 @@ next_page = client.user.get_followers(
184
180
  | `client.dm.send_dm(auth_token=..., conversation_id=..., text=..., proxy=...)` | Send DM |
185
181
  | `client.dm.get_dm_permissions(auth_token=..., recipient_ids=...)` | Check permissions |
186
182
  | `client.dm.get_inbox_initial_state(auth_token=...)` | Get inbox state |
187
- | `client.dm.get_inbox_trusted(auth_token=..., cursor=...)` | Trusted inbox |
188
- | `client.dm.get_inbox_untrusted(auth_token=..., cursor=...)` | Message requests |
183
+ | `client.dm.get_inbox_trusted(auth_token=..., cursor=...)` | Get the trusted inbox |
184
+ | `client.dm.get_inbox_untrusted(auth_token=..., cursor=...)` | Get message requests |
189
185
  | `client.dm.get_conversation(auth_token=..., conversation_id=...)` | Get messages |
190
- | `client.dm.get_dm_user_updates(auth_token=..., cursor=...)` | DM user updates |
191
- | `client.dm.accept_conversation(auth_token=..., conversation_id=...)` | Accept request |
186
+ | `client.dm.get_dm_user_updates(auth_token=..., cursor=...)` | Get DM user updates |
187
+ | `client.dm.accept_conversation(auth_token=..., conversation_id=...)` | Accept a conversation request |
192
188
 
193
- ## Posting and Profile Media
189
+ ## Posting and profile media
194
190
 
195
191
  Tweet media accepts an existing TweetAPI media ID, a URL, or inline base64 data:
196
192
 
@@ -228,9 +224,14 @@ client.profile.banner(
228
224
  )
229
225
  client.profile.remove_banner(auth_token="AUTH_TOKEN")
230
226
  client.profile.set_privacy(auth_token="AUTH_TOKEN", is_private=True)
227
+ client.profile.update_username(
228
+ auth_token="AUTH_TOKEN",
229
+ password="ACCOUNT_PASSWORD",
230
+ username="NEW_USERNAME",
231
+ )
231
232
  ```
232
233
 
233
- Canonical list mutations live under `client.list`; the legacy interaction list helpers remain available:
234
+ Use `client.list` for list mutations. The older helpers under `client.interaction` remain available:
234
235
 
235
236
  ```python
236
237
  created = client.list.create(
@@ -263,9 +264,9 @@ client.community.create_quote_with_media(
263
264
  )
264
265
  ```
265
266
 
266
- ## Auto-Pagination
267
+ ## Pagination
267
268
 
268
- Use the `paginate()` and `paginate_pages()` helpers to iterate through all pages automatically:
269
+ `paginate()` yields individual items. `paginate_pages()` yields each full response page:
269
270
 
270
271
  ```python
271
272
  from tweetapi import TweetAPI, paginate, paginate_pages
@@ -287,18 +288,16 @@ for page in paginate_pages(
287
288
  print(f"Next cursor: {page['pagination']['nextCursor']}")
288
289
  ```
289
290
 
290
- Works with any paginated endpoint — followers, tweets, search results, list members, community posts, etc.
291
+ Each helper accepts a callable that takes a cursor and returns a response with `data` and `pagination.nextCursor`. Use `max_pages` to limit the number of fetched pages.
291
292
 
292
- ## Automatic Retry with Backoff
293
+ ## Retries
293
294
 
294
- The SDK automatically retries on transient errors with exponential backoff:
295
+ The client retries these transient failures:
295
296
 
296
- - **429 (Rate Limit)** — waits the `retry_after` duration from the API, then retries
297
- - **5xx (Server Error)** — retries with exponential backoff + jitter
298
- - **Network errors** — retries on timeouts and connection failures
299
- - **4xx (Client Error)** — never retried (400, 401, 403, 404 fail immediately)
297
+ - For a 429 response, it waits for `RateLimitError.retry_after` seconds, up to `max_retry_delay`.
298
+ - For a 5xx response, timeout, or connection failure, it uses exponential backoff with up to 25% jitter.
300
299
 
301
- Default: 3 retries, 2x backoff, 1s initial delay, 30s max delay.
300
+ By default, the client makes up to 3 retries. Its base delay starts at 1 second, doubles after each attempt, and is capped at 30 seconds. Other 4xx responses are not retried.
302
301
 
303
302
  ```python
304
303
  # Customize retry behavior
@@ -314,18 +313,17 @@ client = TweetAPI(
314
313
  client = TweetAPI(api_key="YOUR_API_KEY", max_retries=0)
315
314
  ```
316
315
 
317
- ### Rate Limit Awareness
316
+ ### Rate-limit state
318
317
 
319
- After a 429 response, the SDK exposes the last known rate limit state:
318
+ After a 429 response, `client.rate_limit_info` records the retry delay and the time the response was received:
320
319
 
321
320
  ```python
322
321
  print(client.rate_limit_info)
323
- # {"retry_after": 30, "timestamp": 1712345678.0} — or None if no 429 encountered
324
322
  ```
325
323
 
326
- ## Error Handling
324
+ ## Error handling
327
325
 
328
- The SDK raises typed exceptions you can catch and handle. With automatic retries enabled (default), you'll only see these after all retry attempts are exhausted:
326
+ The client raises retryable errors after it exhausts the configured retries. It raises other errors from the first response:
329
327
 
330
328
  ```python
331
329
  from tweetapi import (
@@ -359,11 +357,12 @@ except TweetAPIError as e:
359
357
  print(f"Error [{e.code}]: {e.message}")
360
358
  ```
361
359
 
362
- Every error includes:
363
- - `code` — API error code (e.g., `"ACCOUNT_SUSPENDED"`, `"RATE_LIMIT"`)
364
- - `status_code` — HTTP status code
365
- - `message` — Human-readable error message
366
- - `details` — Additional context (field, reason, retry_after, etc.)
360
+ Every error includes these attributes:
361
+
362
+ - `code`: API error code, such as `"ACCOUNT_SUSPENDED"` or `"RATE_LIMIT"`
363
+ - `status_code`: HTTP status code
364
+ - `message`: human-readable error message
365
+ - `details`: response context such as a field, reason, or retry delay
367
366
 
368
367
  ## Configuration
369
368
 
@@ -371,7 +370,7 @@ Every error includes:
371
370
  client = TweetAPI(
372
371
  api_key="YOUR_API_KEY", # Required
373
372
  base_url="https://...", # Optional (default: https://api.tweetapi.com)
374
- timeout=30, # Optional — single value for both connect + read
373
+ timeout=30, # Optional; one value for connect and read
375
374
  connect_timeout=10.0, # Optional (default: 10s)
376
375
  read_timeout=30.0, # Optional (default: 30s)
377
376
  max_retries=3, # Optional (default: 3, set 0 to disable)
@@ -391,8 +390,8 @@ client = TweetAPI(api_key="YOUR_API_KEY", timeout=(5, 30)) # (connect, read)
391
390
 
392
391
  ## Links
393
392
 
394
- - [Full Documentation](https://tweetapi.com/docs?utm_source=github&utm_medium=readme&utm_campaign=python-sdk)
395
- - [Get API Key (Free)](https://tweetapi.com?utm_source=github&utm_medium=readme&utm_campaign=python-sdk)
393
+ - [Documentation](https://tweetapi.com/docs?utm_source=github&utm_medium=readme&utm_campaign=python-sdk)
394
+ - [Create an API key](https://tweetapi.com?utm_source=github&utm_medium=readme&utm_campaign=python-sdk)
396
395
  - [Dashboard](https://tweetapi.com/dashboard?utm_source=github&utm_medium=readme&utm_campaign=python-sdk)
397
396
  - [Node.js SDK](https://github.com/tweetapi/node)
398
397
 
@@ -400,6 +399,4 @@ client = TweetAPI(api_key="YOUR_API_KEY", timeout=(5, 30)) # (connect, read)
400
399
 
401
400
  MIT
402
401
 
403
- ---
404
-
405
- *TweetAPI is a third-party service and is not affiliated with X Corp.*
402
+ TweetAPI is a third-party service and is not affiliated with X Corp.
@@ -4,8 +4,8 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "tweetapi"
7
- version = "2.2.0"
8
- description = "Official Python SDK for TweetAPI — Twitter/X Data API for developers and researchers"
7
+ version = "2.3.0"
8
+ description = "Python SDK for TweetAPI's Twitter/X data and account API"
9
9
  readme = "README.md"
10
10
  license = "MIT"
11
11
  requires-python = ">=3.9"
@@ -37,4 +37,4 @@ __all__ = [
37
37
  "paginate_pages",
38
38
  ]
39
39
 
40
- __version__ = "2.2.0"
40
+ __version__ = "2.3.0"
@@ -108,12 +108,18 @@ class TweetAPI:
108
108
  }
109
109
  return self._request("GET", path, params=clean_params)
110
110
 
111
- def _post(self, path: str, body: Optional[dict[str, Any]] = None) -> Any:
111
+ def _post(
112
+ self,
113
+ path: str,
114
+ body: Optional[dict[str, Any]] = None,
115
+ *,
116
+ retry: bool = True,
117
+ ) -> Any:
112
118
  """Send a POST request to the API."""
113
119
  clean_body = None
114
120
  if body:
115
121
  clean_body = {k: v for k, v in body.items() if v is not None}
116
- return self._request("POST", path, json=clean_body)
122
+ return self._request("POST", path, json=clean_body, retry=retry)
117
123
 
118
124
  @property
119
125
  def rate_limit_info(self) -> Optional[dict[str, Any]]:
@@ -146,11 +152,13 @@ class TweetAPI:
146
152
  path: str,
147
153
  params: Optional[dict[str, Any]] = None,
148
154
  json: Optional[dict[str, Any]] = None,
155
+ retry: bool = True,
149
156
  ) -> Any:
150
157
  url = f"{self._base_url}{path}"
151
158
  last_error: Optional[TweetAPIError] = None
159
+ max_retries = self._max_retries if retry else 0
152
160
 
153
- for attempt in range(self._max_retries + 1):
161
+ for attempt in range(max_retries + 1):
154
162
  try:
155
163
  response = self._session.request(
156
164
  method,
@@ -163,19 +171,19 @@ class TweetAPI:
163
171
  last_error = ConnectionError_(
164
172
  f"Request timed out ({self._timeout_desc})", e
165
173
  )
166
- if attempt < self._max_retries:
174
+ if attempt < max_retries:
167
175
  time.sleep(self._calculate_retry_delay(last_error, attempt))
168
176
  continue
169
177
  raise last_error from e
170
178
  except requests.exceptions.ConnectionError as e:
171
179
  last_error = ConnectionError_(f"Network error: {e}", e)
172
- if attempt < self._max_retries:
180
+ if attempt < max_retries:
173
181
  time.sleep(self._calculate_retry_delay(last_error, attempt))
174
182
  continue
175
183
  raise last_error from e
176
184
  except requests.exceptions.RequestException as e:
177
185
  last_error = ConnectionError_(f"Request failed: {e}", e)
178
- if attempt < self._max_retries:
186
+ if attempt < max_retries:
179
187
  time.sleep(self._calculate_retry_delay(last_error, attempt))
180
188
  continue
181
189
  raise last_error from e
@@ -190,7 +198,7 @@ class TweetAPI:
190
198
  "retry_after": err.retry_after,
191
199
  "timestamp": time.time(),
192
200
  }
193
- if attempt < self._max_retries and self._is_retryable(err):
201
+ if attempt < max_retries and self._is_retryable(err):
194
202
  time.sleep(self._calculate_retry_delay(err, attempt))
195
203
  continue
196
204
  raise
@@ -4,7 +4,12 @@ from typing import Optional, TYPE_CHECKING
4
4
 
5
5
  if TYPE_CHECKING:
6
6
  from ..client import TweetAPI
7
- from ..types import ProfileMediaInput, ProfilePrivacyResponse, UserResponse
7
+ from ..types import (
8
+ ProfileMediaInput,
9
+ ProfilePrivacyResponse,
10
+ ProfileUsernameResponse,
11
+ UserResponse,
12
+ )
8
13
 
9
14
 
10
15
  class ProfileResource:
@@ -41,3 +46,12 @@ class ProfileResource:
41
46
  return self._client._post("/tw-v2/profile/privacy", {
42
47
  "authToken": auth_token, "isPrivate": is_private, "proxy": proxy,
43
48
  })
49
+
50
+ def update_username(self, *, auth_token: str, password: str, username: str, proxy: Optional[str] = None) -> ProfileUsernameResponse:
51
+ """Update the authenticated account's username."""
52
+ return self._client._post("/tw-v2/profile/username", {
53
+ "authToken": auth_token,
54
+ "password": password,
55
+ "username": username,
56
+ "proxy": proxy,
57
+ }, retry=False)
@@ -568,6 +568,14 @@ class ProfilePrivacyResponse(TypedDict):
568
568
  data: ProfilePrivacy
569
569
 
570
570
 
571
+ class ProfileUsername(TypedDict):
572
+ username: str
573
+
574
+
575
+ class ProfileUsernameResponse(TypedDict):
576
+ data: ProfileUsername
577
+
578
+
571
579
  class UsersResponse(TypedDict):
572
580
  data: list[User]
573
581
 
File without changes
File without changes
File without changes