SKILL.md
twitterapi.io
Official skill maintained by the twitterapi.io team. Paths, parameters, and body fields in this skill are verified against the live backend.
When to use this skill
Trigger when the user wants any of:
- Fetch tweets, user profiles, followers/following, trends, replies, quote-tweets, retweeters, articles
- Advanced tweet search (X operators, date ranges, engagement filters)
- Monitor users or filter rules in near real time
- Post / delete / like / retweet / bookmark / quote-tweet / reply / follow / DM / schedule tweets
- Update profile / avatar / banner, upload media
- Create/join/leave communities, manage lists, report content
- Anything involving
twitterapi.io,api.twitterapi.io,x-api-key,login_cookies, or the X API without OAuth
Core facts
Base URL
https://api.twitterapi.io
Prefix
/twitter/... most endpoints; /oapi/x_user_stream/... and /oapi/tweet_filter/... for real-time monitoring/webhooks; /oapi/my/info for balance
Auth header
x-api-key: YOUR_KEY
Dashboard
https://twitterapi.io/dashboard
Docs
Rate limit
~200 req/s per client
Pricing
~$0.15/1k tweets, ~$0.18/1k profiles, $0.00015 minimum
⚠️ Two rules that catch people
Rule 1 — Parameter naming is per-endpoint
No universal snake_case or camelCase rule. Examples (all correct):
/twitter/user/followers?userName=(camel)
/twitter/user/verifiedFollowers?user_id=(snake)
/twitter/user/articles?username=(all-lowercase)
/twitter/tweets?tweet_ids=(snake) but/twitter/tweet/replies?tweetId=(camel)
/twitter/list/tweets_timeline?listId=(camel) but/twitter/list/members?list_id=(snake)
Copy the exact parameter name from references/endpoints.md. Don't normalize.
Rule 2 — Writes need three things in body, not two
Every write endpoint requires:
login_cookies(plural, notlogin_cookie) — base64-encoded JSON from/twitter/user_login_v2
proxy(HTTP/SOCKS proxy URL — configure in dashboard)
- Action-specific fields, almost always snake_case (
tweet_id,user_id)
Plus key field-name traps:
create_tweet_v2text field istweet_text(nottext); reply field isreply_to_tweet_id(notin_reply_to_tweet_id)
update_profile_v2usesdescription(notbio)
bookmarks_v2usescount(notpageSize)
send_dm_to_user:user_id+text; optionalmedia_id(singular)
- Monitoring add uses
x_user_name; remove usesid_for_user(different fields per endpoint!)
See references/write-operations.md for full body shapes.
Rule 3 — Response shape varies per endpoint
data-wrapped:user/info,user_about,last_tweets,tweet_timeline,trends,check_follow_relationship→r["data"]["..."]
- Flat with envelope:
followers,followings,replies,mentions,community/tweets→r["followers"],r["tweets"], etc.
- Flat without envelope:
advanced_search,thread_context,user/search,get_tweets_from_all_community→ just{tweets[], has_next_page, next_cursor}
- Named top-level field:
community/info→r["community_info"];batch_info_by_ids→r["users"]
oapi/my/info→{recharge_credits, total_bonus_credits}(no status wrapper)
Prefer defensive access: r.get("tweets", r.get("data", {}).get("tweets", [])).
Security
Never hardcode the API key. Read from env TWITTERAPI_IO_KEY. If missing, ask the user.
Minimal working example
curl -s "https://api.twitterapi.io/twitter/user/info?userName=elonmusk" \
-H "x-api-key: $TWITTERAPI_IO_KEY"
import os, requests
BASE = "https://api.twitterapi.io"
HEADERS = {"x-api-key": os.environ["TWITTERAPI_IO_KEY"]}
r = requests.get(f"{BASE}/twitter/user/info",
headers=HEADERS,
params={"userName": "elonmusk"},
timeout=30)
r.raise_for_status()
d = r.json()["data"]
print(f"{d['userName']} — {d['followers']:,} followers")
Endpoint quick reference
Reads (API key only). Exact param names — copy as shown.
Capability
Method
Path & key param
User by screen name
GET
/twitter/user/info?userName=
Extended bio
GET
/twitter/user_about?userName=
Batch users by IDs
GET
/twitter/user/batch_info_by_ids?userIds=
Search users
GET
/twitter/user/search?query= (not keyword)
Recent tweets
GET
/twitter/user/last_tweets?userName= (or userId=)
Timeline by ID
GET
/twitter/user/tweet_timeline?userId=
User's articles
GET
/twitter/user/articles?username= (all-lowercase)
Mentions of user
GET
/twitter/user/mentions?userName=
Followers
GET
/twitter/user/followers?userName=&pageSize=200
Verified followers
GET
/twitter/user/verifiedFollowers?user_id= (snake)
Followings
GET
/twitter/user/followings?userName=
Check follow
GET
/twitter/user/check_follow_relationship?source_user_name=&target_user_name=
Tweets by IDs
GET
/twitter/tweets?tweet_ids= (snake)
Tweet replies
GET
/twitter/tweet/replies?tweetId=
Replies (sortable)
GET
/twitter/tweet/replies/v2?tweetId=&queryType=Latest
Quote tweets
GET
/twitter/tweet/quotes?tweetId=
Retweeters
GET
/twitter/tweet/retweeters?tweetId=
Thread context
GET
/twitter/tweet/thread_context?tweetId=
Article content
GET
/twitter/article?tweet_id= (snake)
Advanced search
GET
/twitter/tweet/advanced_search?query=
Bulk advanced search
POST
/twitter/tweet/bulk_advanced_search body {queries:[{query,queryType?,cursor?}]}
Trends
GET
/twitter/trends?woeid=1&count=30
Space detail
GET
/twitter/spaces/detail?space_id= (snake)
List tweets (with filters)
GET
/twitter/list/tweets?listId=
List tweets_timeline
GET
/twitter/list/tweets_timeline?listId=
List members / followers
GET
/twitter/list/{members,followers}?list_id= (snake)
Community info
GET
/twitter/community/info?community_id=
Community tweets/members/mods
GET
/twitter/community/{tweets,members,moderators}?community_id=
All-community firehose
GET
/twitter/community/get_tweets_from_all_community?query= (required!)
Account balance
GET
/oapi/my/info
Real-time monitoring (/oapi/x_user_stream/*). Field names differ per endpoint!
Capability
Method
Path & body
Add monitor
POST
/oapi/x_user_stream/add_user_to_monitor_tweet body {x_user_name} ← **not user_id**
List monitors
GET
/oapi/x_user_stream/get_user_to_monitor_tweet (+ query_type 0/1/2)
Remove monitor
POST
/oapi/x_user_stream/remove_user_to_monitor_tweet body {id_for_user} ← different field from add
Webhook filter rules (/oapi/tweet_filter/*):
Capability
Method
Path & body
Add rule
POST
/oapi/tweet_filter/add_rule body {tag, value, interval_seconds?=60} (range 0.05–86400)
List rules
GET
/oapi/tweet_filter/get_rules
Update rule
POST
/oapi/tweet_filter/update_rule body {rule_id, tag, value, interval_seconds?, is_effect?}
Delete rule
DELETE
/oapi/tweet_filter/delete_rule body {rule_id} (body JSON, not query)
Writes — require login_cookies + proxy in body. login_cookies is base64(json(cookie_dict)) from /twitter/user_login_v2.
Capability
Method
Path
Key body fields (beyond login_cookies+proxy)
Log in → cookies
POST
/twitter/user_login_v2
{user_name, email, password, totp_secret?, proxy}
Create tweet
POST
/twitter/create_tweet_v2
tweet_text (req); optional reply_to_tweet_id, quote_tweet_id, community_id, media_ids[], schedule_for
Delete
POST
/twitter/delete_tweet_v2
tweet_id
Like / Unlike
POST
/twitter/{like,unlike}_tweet_v2
tweet_id
Retweet
POST
/twitter/retweet_tweet_v2
tweet_id
Bookmark / Unbookmark
POST
/twitter/{bookmark,unbookmark}_tweet_v2
tweet_id
List bookmarks
POST
/twitter/bookmarks_v2
optional count (def 20, **not pageSize**), cursor
Follow / Unfollow
POST
/twitter/{follow,unfollow}_user_v2
user_id
Send DM
POST
/twitter/send_dm_to_user
user_id, text, optional media_id (singular), reply_to_message_id
Report
POST
/twitter/report_v2
(tweet_id OR user_id), reason (enum, see endpoints.md)
Upload media
POST
/twitter/upload_media_v2
multipart: file, optional media_category, is_long_video
Update profile
PATCH
/twitter/update_profile_v2
JSON: at least one of name, **description** (not bio!), location, url
Update avatar/banner
PATCH
/twitter/update_{avatar,banner}_v2
multipart: file
Create community
POST
/twitter/create_community_v2
name, description (both required)
Join / Leave community
POST
/twitter/{join,leave}_community_v2
community_id
Delete community
POST
/twitter/delete_community_v2
community_id, **community_name** (both required)
Add list member
POST
/twitter/list/add_member_v2
list_id, user_id
For exact param lists, response shapes, and edge cases see references/endpoints.md.
Recurring patterns
Cursor pagination — use has_next_page
def iter_followers(user_name):
cursor = ""
while True:
r = requests.get(f"{BASE}/twitter/user/followers",
headers=HEADERS,
params={"userName": user_name, "cursor": cursor, "pageSize": 200},
timeout=30).json()
yield from r.get("followers", [])
if not r.get("has_next_page"):
break
cursor = r.get("next_cursor") or ""
Error handling
# FastAPI-style 4xx/5xx
{ "detail": "tweet_id is required" }
{ "detail": [{"type":"missing","loc":["body","file"]}] } # 422 on multipart
# Semantic 200 errors
{ "status": "error", "msg": "No active monitoring subscription" }
401→ badx-api-keyor expiredlogin_cookies
402→ top up balance at dashboard
429→ rate-limited; exponential backoff
400/422→ readdetail— it names the missing/wrong field
500→ transient; retry with jitter. If repeated, the param name is wrong
Cost awareness
Before a loop that could paginate for a long time (followers of a mega-account, years of advanced search), estimate cost up front and confirm with the user if it could exceed a few dollars. A 100M-follower crawl is $15k+ at $0.15/1k.
Anti-patterns
- Do not assume a universal param-casing rule — check endpoints.md
- Do not use
login_cookie(singular) — it'slogin_cookiesplural
- Do not forget
proxyin write bodies — every write requires it
- Do not send
textforcreate_tweet_v2— usetweet_text
- Do not use
bioinupdate_profile_v2— usedescription
- Do not use
in_reply_to_tweet_idforcreate_tweet_v2— it'sreply_to_tweet_id
- Do not send JSON to
upload_media_v2/update_avatar_v2/update_banner_v2— they're multipart/form-data
- Do not use
pageSizeforbookmarks_v2— it'scount
- Do not send
user_idto monitoring add — it'sx_user_name; remove usesid_for_user
- Do not log or commit API keys,
login_cookies, proxies, ortotp_secret
- Do not use
developer.twitter.com/ official X OAuth — this API replaces them
- Do not poll
/user/last_tweetsfor "real-time" — use/oapi/tweet_filter/add_rule+ webhook