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.
- {tweetapi-2.2.0 → tweetapi-2.3.0}/PKG-INFO +55 -58
- {tweetapi-2.2.0 → tweetapi-2.3.0}/README.md +53 -56
- {tweetapi-2.2.0 → tweetapi-2.3.0}/pyproject.toml +2 -2
- {tweetapi-2.2.0 → tweetapi-2.3.0}/tweetapi/__init__.py +1 -1
- {tweetapi-2.2.0 → tweetapi-2.3.0}/tweetapi/client.py +15 -7
- {tweetapi-2.2.0 → tweetapi-2.3.0}/tweetapi/resources/profile.py +15 -1
- {tweetapi-2.2.0 → tweetapi-2.3.0}/tweetapi/types.py +8 -0
- {tweetapi-2.2.0 → tweetapi-2.3.0}/.gitignore +0 -0
- {tweetapi-2.2.0 → tweetapi-2.3.0}/tweetapi/errors.py +0 -0
- {tweetapi-2.2.0 → tweetapi-2.3.0}/tweetapi/pagination.py +0 -0
- {tweetapi-2.2.0 → tweetapi-2.3.0}/tweetapi/py.typed +0 -0
- {tweetapi-2.2.0 → tweetapi-2.3.0}/tweetapi/resources/__init__.py +0 -0
- {tweetapi-2.2.0 → tweetapi-2.3.0}/tweetapi/resources/auth.py +0 -0
- {tweetapi-2.2.0 → tweetapi-2.3.0}/tweetapi/resources/community.py +0 -0
- {tweetapi-2.2.0 → tweetapi-2.3.0}/tweetapi/resources/explore.py +0 -0
- {tweetapi-2.2.0 → tweetapi-2.3.0}/tweetapi/resources/interaction.py +0 -0
- {tweetapi-2.2.0 → tweetapi-2.3.0}/tweetapi/resources/list_.py +0 -0
- {tweetapi-2.2.0 → tweetapi-2.3.0}/tweetapi/resources/post.py +0 -0
- {tweetapi-2.2.0 → tweetapi-2.3.0}/tweetapi/resources/space.py +0 -0
- {tweetapi-2.2.0 → tweetapi-2.3.0}/tweetapi/resources/tweet.py +0 -0
- {tweetapi-2.2.0 → tweetapi-2.3.0}/tweetapi/resources/unencrypted_dm.py +0 -0
- {tweetapi-2.2.0 → tweetapi-2.3.0}/tweetapi/resources/user.py +0 -0
- {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.
|
|
4
|
-
Summary:
|
|
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
|
-
|
|
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
|
|
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"])
|
|
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
|
-
|
|
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
|
-
##
|
|
65
|
+
## SDK behavior
|
|
68
66
|
|
|
69
|
-
-
|
|
70
|
-
-
|
|
71
|
-
-
|
|
72
|
-
-
|
|
73
|
-
-
|
|
74
|
-
-
|
|
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
|
|
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
|
|
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
|
|
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 (
|
|
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=...)` |
|
|
218
|
-
| `client.dm.get_inbox_untrusted(auth_token=..., cursor=...)` |
|
|
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
|
|
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
|
-
|
|
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
|
-
##
|
|
297
|
+
## Pagination
|
|
297
298
|
|
|
298
|
-
|
|
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
|
-
|
|
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
|
-
##
|
|
323
|
+
## Retries
|
|
323
324
|
|
|
324
|
-
The
|
|
325
|
+
The client retries these transient failures:
|
|
325
326
|
|
|
326
|
-
-
|
|
327
|
-
-
|
|
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
|
-
|
|
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
|
|
346
|
+
### Rate-limit state
|
|
348
347
|
|
|
349
|
-
After a 429 response, the
|
|
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
|
|
354
|
+
## Error handling
|
|
357
355
|
|
|
358
|
-
The
|
|
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
|
-
|
|
394
|
-
- `
|
|
395
|
-
- `
|
|
396
|
-
- `
|
|
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
|
|
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
|
-
- [
|
|
425
|
-
- [
|
|
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
|
-
|
|
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
|
|
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"])
|
|
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
|
-
|
|
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
|
-
##
|
|
35
|
+
## SDK behavior
|
|
38
36
|
|
|
39
|
-
-
|
|
40
|
-
-
|
|
41
|
-
-
|
|
42
|
-
-
|
|
43
|
-
-
|
|
44
|
-
-
|
|
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
|
|
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
|
|
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
|
|
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 (
|
|
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=...)` |
|
|
188
|
-
| `client.dm.get_inbox_untrusted(auth_token=..., cursor=...)` |
|
|
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
|
|
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
|
-
|
|
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
|
-
##
|
|
267
|
+
## Pagination
|
|
267
268
|
|
|
268
|
-
|
|
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
|
-
|
|
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
|
-
##
|
|
293
|
+
## Retries
|
|
293
294
|
|
|
294
|
-
The
|
|
295
|
+
The client retries these transient failures:
|
|
295
296
|
|
|
296
|
-
-
|
|
297
|
-
-
|
|
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
|
-
|
|
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
|
|
316
|
+
### Rate-limit state
|
|
318
317
|
|
|
319
|
-
After a 429 response, the
|
|
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
|
|
324
|
+
## Error handling
|
|
327
325
|
|
|
328
|
-
The
|
|
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
|
-
|
|
364
|
-
- `
|
|
365
|
-
- `
|
|
366
|
-
- `
|
|
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
|
|
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
|
-
- [
|
|
395
|
-
- [
|
|
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.
|
|
8
|
-
description = "
|
|
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"
|
|
@@ -108,12 +108,18 @@ class TweetAPI:
|
|
|
108
108
|
}
|
|
109
109
|
return self._request("GET", path, params=clean_params)
|
|
110
110
|
|
|
111
|
-
def _post(
|
|
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(
|
|
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 <
|
|
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 <
|
|
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 <
|
|
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 <
|
|
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
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|