--- name: twitterapi-io description: Official skill for twitterapi.io — query Twitter/X data (tweets, profiles, followers, advanced search, trends, spaces, communities, lists) and perform authenticated actions (post, reply, like, retweet, follow, DM) via the twitterapi.io REST API using a single `x-api-key` header — no OAuth. Use when the user needs to scrape, analyze, monitor, or automate X/Twitter without going through the official developer portal. --- # twitterapi.io **Official skill** maintained by the [twitterapi.io](https://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 | https://docs.twitterapi.io | | 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](references/endpoints.md). Don't normalize.** ### Rule 2 — Writes need three things in body, not two Every write endpoint requires: 1. `login_cookies` (**plural**, not `login_cookie`) — base64-encoded JSON from `/twitter/user_login_v2` 2. `proxy` (HTTP/SOCKS proxy URL — configure in dashboard) 3. Action-specific fields, almost always **snake_case** (`tweet_id`, `user_id`) Plus key field-name traps: - `create_tweet_v2` text field is `tweet_text` (not `text`); reply field is `reply_to_tweet_id` (not `in_reply_to_tweet_id`) - `update_profile_v2` uses `description` (not `bio`) - `bookmarks_v2` uses `count` (not `pageSize`) - `send_dm_to_user`: `user_id` + `text`; optional `media_id` (singular) - Monitoring add uses `x_user_name`; remove uses `id_for_user` (different fields per endpoint!) See [references/write-operations.md](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 ```bash curl -s "https://api.twitterapi.io/twitter/user/info?userName=elonmusk" \ -H "x-api-key: $TWITTERAPI_IO_KEY" ``` ```python 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](references/endpoints.md). ## Recurring patterns ### Cursor pagination — use `has_next_page` ```python 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 ```python # 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` → bad `x-api-key` or expired `login_cookies` - `402` → top up balance at dashboard - `429` → rate-limited; exponential backoff - `400/422` → read `detail` — 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's `login_cookies` plural - Do **not** forget `proxy` in write bodies — every write requires it - Do **not** send `text` for `create_tweet_v2` — use `tweet_text` - Do **not** use `bio` in `update_profile_v2` — use `description` - Do **not** use `in_reply_to_tweet_id` for `create_tweet_v2` — it's `reply_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 `pageSize` for `bookmarks_v2` — it's `count` - Do **not** send `user_id` to monitoring add — it's `x_user_name`; remove uses `id_for_user` - Do **not** log or commit API keys, `login_cookies`, proxies, or `totp_secret` - Do **not** use `developer.twitter.com` / official X OAuth — this API replaces them - Do **not** poll `/user/last_tweets` for "real-time" — use `/oapi/tweet_filter/add_rule` + webhook