
- 7.7k installs
- 18 repo stars
- Updated July 27, 2026
- starchild-ai-agent/official-skills
twitter is a script-mode skill that reads X data through thirteen twitterapi.io functions proxied via sc-proxy for tweets, users, search, threads, and trends.
About
twitter is a script-mode Starchild skill exposing thirteen read-only functions against twitterapi.io through sc-proxy, covering tweets, users, followers, replies, threads, quotes, articles, and trends. Agents invoke exports via bash and python3 rather than runtime tools, with TWITTER_API_KEY injected server-side on the proxy host. Use it for any x.com or twitter.com URL instead of web_fetch because Twitter blocks scrapers. Core flows include twitter_get_tweets for IDs extracted from status URLs, twitter_user_tweets for recent posts, twitter_search_tweets with operators like from:user, min_faves, and since_time, and twitter_get_trends for trending topics. Billing is per returned item, not per request; last_tweets always bills roughly twenty tweets per call with no page-size lever, so polling should prefer advanced_search windows that bill one item when empty. Error handling covers 402 credits exhausted, 429 rate limits, and 404 user not found without auto-retry.
- Thirteen read-only functions in exports.py for tweets, users, followers, and trends.
- Script-mode delivery: agents call python3 against /data/workspace/skills/twitter via bash.
- Start here for any x.com or twitter.com URL because Twitter blocks web_fetch scrapers.
- Advanced search polling with since_time and until_time is cheaper than last_tweets polling.
- Bills per returned item; last_tweets charges about twenty tweets even when only five are needed.
Twitter by the numbers
- 7,676 all-time installs (skills.sh)
- +64 installs in the week ending Jul 28, 2026 (Skillselion tracking)
- Ranked #52 of 2,742 Automation & Workflows skills by installs in the Skillselion catalog
- Security screen: MEDIUM risk (skills.sh audit)
- Data as of Jul 28, 2026 (Skillselion catalog sync)
twitter capabilities & compatibility
- Capabilities
- fetch tweets by id list via twitter_get_tweets · advanced search with from user cashtag lang and · user profile tweets followers and followings loo · thread replies quotes retweeters and article bod · trending topics via twitter_get_trends with opti · cursor pagination using next_cursor on paged end
- Use cases
- research · web search · data analysis
- Runs
- Remote server
- Pricing
- Bring your own API key
What twitter says it does
Twitter/X data: fetch tweets, search, user profiles, followers, replies, trends.
npx skills add https://github.com/starchild-ai-agent/official-skills --skill twitterAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 7.7k |
|---|---|
| repo stars | ★ 18 |
| Security audit | 1 / 3 scanners passed |
| Last updated | July 27, 2026 |
| Repository | starchild-ai-agent/official-skills ↗ |
How do I fetch tweets, search X, or read profiles when web_fetch is blocked and I need cost-aware twitterapi.io access?
Fetch tweets, search X, read profiles, followers, threads, quotes, articles, and trends via script-mode twitterapi.io exports through sc-proxy.
Who is it for?
Agents that need read-only X lookups, KOL monitoring, cashtag search, or tweet summarization via python3 script invocations.
Skip if: Skip for posting tweets, managed streams, webhook filters, or work that expects runtime-registered agent tools instead of bash python calls.
When should I use this skill?
User shares an x.com or twitter.com URL, asks for recent posts by a handle, advanced search, thread context, or trending topics.
What you get
JSON dict responses from exports.py functions for the requested tweets, users, search pages, or trend lists with cursor pagination when available.
- Tweet and thread JSON responses
- Trend and profile datasets
- Follower and reply lists
By the numbers
- 13 functions in exports.py
- 20 tweets billed per last_tweets page
- 100k credits equals one dollar upstream
Files
Twitter / X (script-mode)
Read-only access to twitterapi.io endpoints. 13 functions covering tweets, users, followers, replies, threads, quotes, articles, and trends.
All requests go through sc-proxy via core.http_client.proxied_get. The TWITTER_API_KEY env var is auto-injected server-side, no local key needed on the agent machine.
Script Usage
Standard invocation pattern:
python3 - <<'EOF'
import sys, json
sys.path.insert(0, "/data/workspace/skills/twitter")
from exports import twitter_user_info, twitter_user_tweets
profile = twitter_user_info(username="vitalikbuterin")
print(json.dumps(profile, indent=2))
recent = twitter_user_tweets(username="vitalikbuterin")
print(f"got {len(recent.get('tweets', []))} tweets")
EOFTweet ID extraction from URL: the last path segment of any x.com/{user}/status/{id} or twitter.com/{user}/status/{id} URL is the tweet ID. Pass it as a string (Python int will lose precision on long IDs).
Function Reference (signatures)
All 13 functions live in exports.py. Returns are dicts straight from twitterapi.io — keys vary per endpoint, inspect once before scripting.
Tweet endpoints
| Function | Description |
|---|---|
twitter_search_tweets(query, cursor=None) | Advanced search. Operators: from:user, to:user, #tag, $cashtag, lang:en, has:media, has:links, is:reply, min_faves:N, since:YYYY-MM-DD, until:YYYY-MM-DD. |
twitter_get_tweets(tweet_ids) | Fetch one or more tweets by ID. tweet_ids = list of strings (also accepts comma-string). |
twitter_tweet_replies(tweet_id, cursor=None) | Replies to a tweet. |
twitter_tweet_retweeters(tweet_id, cursor=None) | Users who retweeted. |
twitter_tweet_thread_context(tweet_id) | Full thread context (parents + direct replies). |
twitter_tweet_quote(tweet_id, cursor=None) | Quote tweets. |
twitter_get_article(tweet_id) | Long-form X article body. |
twitter_get_trends(woeid=None, country=None, category=None, limit=None) | Trending topics; all filters optional. |
User endpoints
| Function | Description |
|---|---|
twitter_user_info(username) | Profile: bio, follower/following counts, tweet count, verified. |
twitter_user_tweets(username, cursor=None) | User's recent tweets. |
twitter_user_followers(username, cursor=None) | Follower list. |
twitter_user_followings(username, cursor=None) | Accounts followed. |
twitter_search_users(query, cursor=None) | Search users by name/keyword. |
username is the handle WITHOUT @ (e.g. "elonmusk", not "@elonmusk"). Pagination: when a response includes next_cursor, pass it back as cursor on the next call.
When to use this skill
- ANY
x.com/...ortwitter.com/...URL → start here, NOTweb_fetch
(Twitter blocks scrapers).
- Single tweet detail →
twitter_get_tweets([tweet_id]). - "What's @user been posting?" →
twitter_user_tweets. - KOL discovery / cashtag mentions →
twitter_search_tweets("$SOL min_faves:50"). - Trending topics →
twitter_get_trends.
Billing & cost control (READ before bulk/scheduled use)
twitterapi.io bills per item actually returned, not per request and NOT by any "max_results" you ask for. sc-proxy charges = returned-item-count × unit (tweets 45 / profiles 54 / followers 45 credits; 100k credits = $1; 3× upstream). Min 1 item per request.
The `last_tweets` / `user_tweets` trap: the upstream /twitter/user/last_tweets endpoint has no page-size parameter — it always returns up to 20 tweets per page. There is no max_results / pageSize lever, and twitter_user_tweets() does not accept one. So "I only need 5" still fetches and bills for ~20. Slicing the result client-side does NOT save money — the charge is already counted at the proxy from the upstream response.
⭐ Polling for "new tweets from account X" → use search, NOT last_tweets
This is the biggest, most common waste. twitter_user_tweets() (upstream last_tweets) has no page-size param and always bills a full ~20-tweet page every call, even when nothing new was posted. The official twitterapi.io guide recommends the advanced_search endpoint instead, which our skill already exposes as twitter_search_tweets():
# Cheap polling pattern — bills only the tweets actually in the window.
# When NO new tweet exists, the call is billed as 1 item (not 20).
import time
since = int(last_check_unix)
until = int(time.time())
q = f"from:{handle} include:nativeretweets since_time:{since} until_time:{until}"
res = twitter_search_tweets(q) # queryType defaults to LatestOfficial pricing (upstream; our proxy bills 3×):
- tweets found → $0.00015 per returned tweet
- no tweets found → $0.00015 for the whole call (vs last_tweets' ~20× that)
Per-call cost in our billing makes the difference obvious:
last_tweets→ ~$0.009/call (20 tweets every time)advanced_searchempty window → ~$0.00045/call (1 item) — ~20× cheaper
Frequency vs monthly cost (single account, upstream): hourly $0.11 · 30min $0.22 · 15min $0.43 · 5min $1.30 · 1min $6.48.
Other cost levers
- Use `get_tweets([ids])` when IDs are known — pay only for those exact
tweets, not a 20-item page.
- Followers/followings bill per returned profile (default page 200 → 200
billed). Only paginate as far as needed. For ID-only graph work use the bulk followers-IDs endpoint (lightweight).
- Tighten search queries (min_faves, since_time/until_time, lang) so fewer
pages are needed.
Note: twitterapi.io also sells a managed stream/webhook product. **We do NOT
subscribe to it* — do not use the `/oapi/x_user_stream/` or
/oapi/tweet_filter/* endpoints. For any account-monitoring need, theadvanced_search polling pattern above is the correct and only approach here.
Error handling
402 Credits is not enough→ upstream proxy credits exhausted; tell user
to top up. Don't retry.
429→ rate limited; surface to user, don't auto-retry.404 user not found→ suggest verifying the handle spelling.
Version Policy (hard rule)
This skill is script-mode (delivery: script). It does NOT register runtime tools — agent must read_file SKILL.md and call functions via bash + python3. The legacy tools.py / __init__.py files are kept for backward compatibility but are no longer the preferred entry point.
Bump rules:
- Any signature change, env-var change, or sc-proxy contract change → MAJOR
- New function added, response schema clarified → MINOR
- Bug fix or doc-only change → PATCH
"""
Twitter/X Extension — Read-only data via twitterapi.io
Provides 13 tools for Twitter/X data:
- twitter_search_tweets: Advanced tweet search
- twitter_get_tweets: Get tweets by ID
- twitter_get_article: Get long-form article by tweet ID
- twitter_tweet_thread_context: Get complete thread context
- twitter_tweet_quote: Get quote tweets for a tweet
- twitter_get_trends: Get trends
- twitter_user_info: User profile lookup
- twitter_user_tweets: User's recent tweets
- twitter_user_followers: User's followers
- twitter_user_followings: User's followings
- twitter_tweet_replies: Replies to a tweet
- twitter_tweet_retweeters: Users who retweeted
- twitter_search_users: Search for users
Environment Variables:
- TWITTER_API_KEY: API key for twitterapi.io (required)
Usage:
This extension is auto-loaded by the ExtensionLoader.
"""
import logging
from typing import List
logger = logging.getLogger(__name__)
def register(api) -> List[str]:
"""
Extension entry point — register all Twitter tools.
Args:
api: ExtensionApi instance with registry and config
Returns:
List of registered tool names
"""
registered = []
try:
from .tools import (
TwitterSearchTweetsTool,
TwitterGetTweetsTool,
TwitterGetArticleTool,
TwitterTweetThreadContextTool,
TwitterTweetQuoteTool,
TwitterGetTrendsTool,
TwitterUserInfoTool,
TwitterUserTweetsTool,
TwitterUserFollowersTool,
TwitterUserFollowingsTool,
TwitterTweetRepliesTool,
TwitterTweetRetweetersTool,
TwitterSearchUsersTool,
)
api.register_tool(TwitterSearchTweetsTool())
api.register_tool(TwitterGetTweetsTool())
api.register_tool(TwitterGetArticleTool())
api.register_tool(TwitterTweetThreadContextTool())
api.register_tool(TwitterTweetQuoteTool())
api.register_tool(TwitterGetTrendsTool())
api.register_tool(TwitterUserInfoTool())
api.register_tool(TwitterUserTweetsTool())
api.register_tool(TwitterUserFollowersTool())
api.register_tool(TwitterUserFollowingsTool())
api.register_tool(TwitterTweetRepliesTool())
api.register_tool(TwitterTweetRetweetersTool())
api.register_tool(TwitterSearchUsersTool())
registered = [
"twitter_search_tweets",
"twitter_get_tweets",
"twitter_get_article",
"twitter_tweet_thread_context",
"twitter_tweet_quote",
"twitter_get_trends",
"twitter_user_info",
"twitter_user_tweets",
"twitter_user_followers",
"twitter_user_followings",
"twitter_tweet_replies",
"twitter_tweet_retweeters",
"twitter_search_users",
]
logger.info(f"Registered Twitter tools ({len(registered)} tools)")
except Exception as e:
logger.warning(f"Failed to load Twitter tools: {e}")
return registered
# Extension metadata
EXTENSION_INFO = {
"name": "twitter",
"version": "1.1.0",
"description": "Twitter/X data — search tweets, article, thread context, quote tweets, trends, users",
"tools": [
"twitter_search_tweets",
"twitter_get_tweets",
"twitter_get_article",
"twitter_tweet_thread_context",
"twitter_tweet_quote",
"twitter_get_trends",
"twitter_user_info",
"twitter_user_tweets",
"twitter_user_followers",
"twitter_user_followings",
"twitter_tweet_replies",
"twitter_tweet_retweeters",
"twitter_search_users",
],
"env_vars": [
"TWITTER_API_KEY",
],
}
"""
Twitter API client — wraps twitterapi.io endpoints.
Read-only: search tweets, user profiles, followers, replies, thread context,
quote tweets, article, and trends.
Auth: X-API-Key header from TWITTER_API_KEY env var.
"""
import logging
import os
from typing import Any, List
from core.http_client import proxied_get
CALLER_ID = "chat:twitter-skill"
logger = logging.getLogger(__name__)
TWITTERAPI_BASE_URL = "https://api.twitterapi.io"
class TwitterApiClient:
"""
Sync twitterapi.io client using proxied_get.
All endpoints are public GET requests authenticated via X-API-Key header.
"""
def __init__(self):
self.base_url = TWITTERAPI_BASE_URL
self.api_key = os.environ.get("TWITTER_API_KEY", "")
def _get(self, path: str, params: dict = None) -> Any:
"""GET a twitterapi.io endpoint."""
url = f"{self.base_url}{path}"
headers = {"SC-CALLER-ID": CALLER_ID}
if self.api_key:
headers["X-API-Key"] = self.api_key
response = proxied_get(url, headers=headers, params=params, timeout=15)
if response.status_code >= 400:
raise Exception(f"twitterapi.io {response.status_code}: {response.text}")
return response.json()
# ── Tweet Endpoints ──────────────────────────────────────────────────
def search_tweets(self, query: str, cursor: str = None) -> dict:
"""Advanced tweet search."""
params = {"query": query}
if cursor:
params["cursor"] = cursor
return self._get("/twitter/tweet/advanced_search", params)
def get_tweets(self, tweet_ids: List[str]) -> dict:
"""Get tweets by IDs."""
params = {"tweet_ids": ",".join(tweet_ids)}
return self._get("/twitter/tweets", params)
def get_tweet_replies(self, tweet_id: str, cursor: str = None) -> dict:
"""Get replies to a tweet."""
params = {"tweetId": tweet_id}
if cursor:
params["cursor"] = cursor
return self._get("/twitter/tweet/replies", params)
def get_tweet_retweeters(self, tweet_id: str, cursor: str = None) -> dict:
"""Get users who retweeted a tweet."""
params = {"tweetId": tweet_id}
if cursor:
params["cursor"] = cursor
return self._get("/twitter/tweet/retweeters", params)
def get_tweet_thread_context(self, tweet_id: str) -> dict:
"""Get complete thread context for a tweet (parents + direct replies)."""
return self._get("/twitter/tweet/thread_context", {"tweetId": tweet_id})
def get_tweet_quote(self, tweet_id: str, cursor: str = None) -> dict:
"""Get quote tweets for a tweet."""
params = {"tweetId": tweet_id}
if cursor:
params["cursor"] = cursor
return self._get("/twitter/tweet/quotes", params)
def get_article(self, tweet_id: str) -> dict:
"""Get article content by tweet ID (long-form X article)."""
return self._get("/twitter/article", {"tweet_id": tweet_id})
def get_trends(self, woeid: str = None, country: str = None, category: str = None, limit: int = None) -> dict:
"""Get trending topics."""
params = {}
if woeid:
params["woeid"] = woeid
if country:
params["country"] = country
if category:
params["category"] = category
if limit is not None:
params["limit"] = limit
return self._get("/twitter/trends", params)
# ── User Endpoints ───────────────────────────────────────────────────
def get_user_info(self, username: str) -> dict:
"""Get user profile info."""
return self._get("/twitter/user/info", {"userName": username})
def get_user_tweets(self, username: str, cursor: str = None) -> dict:
"""Get user's recent tweets."""
params = {"userName": username}
if cursor:
params["cursor"] = cursor
return self._get("/twitter/user/last_tweets", params)
def get_user_followers(self, username: str, cursor: str = None) -> dict:
"""Get user's followers."""
params = {"userName": username}
if cursor:
params["cursor"] = cursor
return self._get("/twitter/user/followers", params)
def get_user_followings(self, username: str, cursor: str = None) -> dict:
"""Get accounts the user follows."""
params = {"userName": username}
if cursor:
params["cursor"] = cursor
return self._get("/twitter/user/followings", params)
def search_users(self, query: str, cursor: str = None) -> dict:
"""Search for users."""
params = {"query": query}
if cursor:
params["cursor"] = cursor
return self._get("/twitter/user/search", params)
"""
Twitter/X skill exports — script-mode wrapper around twitterapi.io.
Usage from a bash block:
python3 - <<'EOF'
import sys
sys.path.insert(0, "/data/workspace/skills/twitter")
from exports import twitter_user_info, twitter_search_tweets
print(twitter_user_info(username="elonmusk"))
EOF
All 13 functions are flat, sync wrappers around TwitterApiClient methods.
HTTP traffic goes through sc-proxy via core.http_client — credentials
injected server-side, no TWITTER_API_KEY needed locally.
"""
from __future__ import annotations
import os
import sys
from typing import List, Optional
# Make local client.py importable regardless of caller cwd.
_THIS_DIR = os.path.dirname(os.path.abspath(__file__))
if _THIS_DIR not in sys.path:
sys.path.insert(0, _THIS_DIR)
from client import TwitterApiClient # noqa: E402
_client_singleton: Optional[TwitterApiClient] = None
def _client() -> TwitterApiClient:
global _client_singleton
if _client_singleton is None:
_client_singleton = TwitterApiClient()
return _client_singleton
# ── Tweet endpoints ──────────────────────────────────────────────────────────
def twitter_search_tweets(query: str, cursor: Optional[str] = None) -> dict:
"""Search Twitter/X tweets using advanced query syntax.
Operators: from:user, to:user, #hashtag, $cashtag, lang:en, has:media,
has:links, is:reply, min_faves:100, since:YYYY-MM-DD, until:YYYY-MM-DD.
Returns dict with `tweets` array and `next_cursor` for pagination.
"""
return _client().search_tweets(query, cursor=cursor)
def twitter_get_tweets(tweet_ids: List[str]) -> dict:
"""Get one or more tweets by their tweet IDs.
`tweet_ids` is a list of stringified tweet IDs.
Returns dict with `tweets` array containing full tweet data.
"""
if isinstance(tweet_ids, str):
# Be forgiving — accept comma string too.
tweet_ids = [t.strip() for t in tweet_ids.split(",") if t.strip()]
return _client().get_tweets(tweet_ids)
def twitter_tweet_replies(tweet_id: str, cursor: Optional[str] = None) -> dict:
"""Get replies to a specific tweet (by tweet ID)."""
return _client().get_tweet_replies(tweet_id, cursor=cursor)
def twitter_tweet_retweeters(tweet_id: str, cursor: Optional[str] = None) -> dict:
"""Get users who retweeted a specific tweet."""
return _client().get_tweet_retweeters(tweet_id, cursor=cursor)
def twitter_tweet_thread_context(tweet_id: str) -> dict:
"""Get full thread context for a tweet (parent chain + direct replies)."""
return _client().get_tweet_thread_context(tweet_id)
def twitter_tweet_quote(tweet_id: str, cursor: Optional[str] = None) -> dict:
"""Get quote tweets that quote a specific tweet."""
return _client().get_tweet_quote(tweet_id, cursor=cursor)
def twitter_get_article(tweet_id: str) -> dict:
"""Get long-form X article content by the article's tweet ID."""
return _client().get_article(tweet_id)
def twitter_get_trends(
woeid: Optional[str] = None,
country: Optional[str] = None,
category: Optional[str] = None,
limit: Optional[int] = None,
) -> dict:
"""Get Twitter/X trending topics.
All filters optional. `woeid` = Where On Earth ID (numeric region code).
"""
return _client().get_trends(woeid=woeid, country=country, category=category, limit=limit)
# ── User endpoints ───────────────────────────────────────────────────────────
def twitter_user_info(username: str) -> dict:
"""Get a user's profile: bio, follower/following count, tweet count, verified."""
return _client().get_user_info(username)
def twitter_user_tweets(username: str, cursor: Optional[str] = None) -> dict:
"""Get a user's recent tweets (paginated)."""
return _client().get_user_tweets(username, cursor=cursor)
def twitter_user_followers(username: str, cursor: Optional[str] = None) -> dict:
"""Get a user's followers (paginated)."""
return _client().get_user_followers(username, cursor=cursor)
def twitter_user_followings(username: str, cursor: Optional[str] = None) -> dict:
"""Get accounts a user follows (paginated)."""
return _client().get_user_followings(username, cursor=cursor)
def twitter_search_users(query: str, cursor: Optional[str] = None) -> dict:
"""Search for users by name or keyword."""
return _client().search_users(query, cursor=cursor)
__all__ = [
"twitter_search_tweets",
"twitter_get_tweets",
"twitter_tweet_replies",
"twitter_tweet_retweeters",
"twitter_tweet_thread_context",
"twitter_tweet_quote",
"twitter_get_article",
"twitter_get_trends",
"twitter_user_info",
"twitter_user_tweets",
"twitter_user_followers",
"twitter_user_followings",
"twitter_search_users",
]
"""
Twitter/X Tools — BaseTool subclasses for agent use.
13 read-only tools: search tweets, get tweets, user info, user tweets,
user followers, user followings, tweet replies, tweet retweeters, search users,
article, thread context, quote tweets, trends.
"""
import asyncio
import logging
from core.tool import BaseTool, ToolContext, ToolResult
from .client import TwitterApiClient
logger = logging.getLogger(__name__)
# Module-level shared client instance
_client: TwitterApiClient = None
def _get_client() -> TwitterApiClient:
global _client
if _client is None:
_client = TwitterApiClient()
return _client
# ── Tweet Tools ──────────────────────────────────────────────────────────────
class TwitterSearchTweetsTool(BaseTool):
"""Search tweets with advanced query syntax."""
@property
def name(self) -> str:
return "twitter_search_tweets"
@property
def description(self) -> str:
return """Search Twitter/X tweets using advanced query syntax.
Supports operators: keyword matching, from:user, to:user, #hashtag, $cashtag,
lang:en, has:media, has:links, is:reply, min_faves:100, since:2024-01-01, until:2024-12-31.
Parameters:
- query: Search query (required). Examples: "bitcoin", "from:elonmusk crypto", "$SOL min_faves:50"
- cursor: Pagination cursor from previous response (optional)
Returns: tweets array with text, author, metrics, and next cursor for pagination"""
@property
def parameters(self) -> dict:
return {
"type": "object",
"properties": {
"query": {
"type": "string",
"description": "Search query (supports advanced operators)",
},
"cursor": {
"type": "string",
"description": "Pagination cursor from previous response",
},
},
"required": ["query"],
}
async def execute(self, ctx: ToolContext, query: str = "", cursor: str = None, **kwargs) -> ToolResult:
if not query:
return ToolResult(success=False, error="'query' is required")
try:
client = _get_client()
data = await asyncio.to_thread(client.search_tweets, query, cursor=cursor)
return ToolResult(success=True, output=data)
except Exception as e:
return ToolResult(success=False, error=str(e))
class TwitterGetTweetsTool(BaseTool):
"""Get tweets by their IDs."""
@property
def name(self) -> str:
return "twitter_get_tweets"
@property
def description(self) -> str:
return """Get one or more tweets by their tweet IDs.
Parameters:
- tweet_ids: Array of tweet ID strings (required, e.g. ["1234567890", "9876543210"])
Returns: tweets array with full tweet data"""
@property
def parameters(self) -> dict:
return {
"type": "object",
"properties": {
"tweet_ids": {
"type": "array",
"items": {"type": "string"},
"description": "Array of tweet ID strings",
},
},
"required": ["tweet_ids"],
}
async def execute(self, ctx: ToolContext, tweet_ids: list = None, **kwargs) -> ToolResult:
if not tweet_ids:
return ToolResult(success=False, error="'tweet_ids' is required")
try:
client = _get_client()
data = await asyncio.to_thread(client.get_tweets, tweet_ids)
return ToolResult(success=True, output=data)
except Exception as e:
return ToolResult(success=False, error=str(e))
class TwitterTweetRepliesTool(BaseTool):
"""Get replies to a specific tweet."""
@property
def name(self) -> str:
return "twitter_tweet_replies"
@property
def description(self) -> str:
return """Get replies to a specific tweet.
Parameters:
- tweet_id: Tweet ID to get replies for (required)
- cursor: Pagination cursor from previous response (optional)
Returns: replies array with tweet data and next cursor"""
@property
def parameters(self) -> dict:
return {
"type": "object",
"properties": {
"tweet_id": {
"type": "string",
"description": "Tweet ID to get replies for",
},
"cursor": {
"type": "string",
"description": "Pagination cursor from previous response",
},
},
"required": ["tweet_id"],
}
async def execute(self, ctx: ToolContext, tweet_id: str = "", cursor: str = None, **kwargs) -> ToolResult:
if not tweet_id:
return ToolResult(success=False, error="'tweet_id' is required")
try:
client = _get_client()
data = await asyncio.to_thread(client.get_tweet_replies, tweet_id, cursor=cursor)
return ToolResult(success=True, output=data)
except Exception as e:
return ToolResult(success=False, error=str(e))
class TwitterTweetRetweetersTool(BaseTool):
"""Get users who retweeted a tweet."""
@property
def name(self) -> str:
return "twitter_tweet_retweeters"
@property
def description(self) -> str:
return """Get users who retweeted a specific tweet.
Parameters:
- tweet_id: Tweet ID to get retweeters for (required)
- cursor: Pagination cursor from previous response (optional)
Returns: users array with profile data and next cursor"""
@property
def parameters(self) -> dict:
return {
"type": "object",
"properties": {
"tweet_id": {
"type": "string",
"description": "Tweet ID to get retweeters for",
},
"cursor": {
"type": "string",
"description": "Pagination cursor from previous response",
},
},
"required": ["tweet_id"],
}
async def execute(self, ctx: ToolContext, tweet_id: str = "", cursor: str = None, **kwargs) -> ToolResult:
if not tweet_id:
return ToolResult(success=False, error="'tweet_id' is required")
try:
client = _get_client()
data = await asyncio.to_thread(client.get_tweet_retweeters, tweet_id, cursor=cursor)
return ToolResult(success=True, output=data)
except Exception as e:
return ToolResult(success=False, error=str(e))
# ── User Tools ───────────────────────────────────────────────────────────────
class TwitterGetArticleTool(BaseTool):
"""Get long-form article for a tweet."""
@property
def name(self) -> str:
return "twitter_get_article"
@property
def description(self) -> str:
return """Get X/Twitter long-form article by tweet ID.
Parameters:
- tweet_id: Tweet ID of the article post (required)
Returns: article object with title, preview text, cover media URL, and content blocks"""
@property
def parameters(self) -> dict:
return {
"type": "object",
"properties": {
"tweet_id": {
"type": "string",
"description": "Tweet ID of the article post",
},
},
"required": ["tweet_id"],
}
async def execute(self, ctx: ToolContext, tweet_id: str = "", **kwargs) -> ToolResult:
if not tweet_id:
return ToolResult(success=False, error="'tweet_id' is required")
try:
client = _get_client()
data = await asyncio.to_thread(client.get_article, tweet_id)
return ToolResult(success=True, output=data)
except Exception as e:
return ToolResult(success=False, error=str(e))
class TwitterTweetThreadContextTool(BaseTool):
"""Get complete thread context for a tweet."""
@property
def name(self) -> str:
return "twitter_tweet_thread_context"
@property
def description(self) -> str:
return """Get complete thread context for a tweet.
Parameters:
- tweet_id: Tweet ID to get thread context for (required)
Returns: parent tweets + direct replies in a single response"""
@property
def parameters(self) -> dict:
return {
"type": "object",
"properties": {
"tweet_id": {
"type": "string",
"description": "Tweet ID to get thread context for",
},
},
"required": ["tweet_id"],
}
async def execute(self, ctx: ToolContext, tweet_id: str = "", **kwargs) -> ToolResult:
if not tweet_id:
return ToolResult(success=False, error="'tweet_id' is required")
try:
client = _get_client()
data = await asyncio.to_thread(client.get_tweet_thread_context, tweet_id)
return ToolResult(success=True, output=data)
except Exception as e:
return ToolResult(success=False, error=str(e))
class TwitterTweetQuoteTool(BaseTool):
"""Get quote tweets for a tweet."""
@property
def name(self) -> str:
return "twitter_tweet_quote"
@property
def description(self) -> str:
return """Get quote tweets for a specific tweet.
Parameters:
- tweet_id: Tweet ID to get quote tweets for (required)
- cursor: Pagination cursor from previous response (optional)
Returns: quote tweets list and next cursor"""
@property
def parameters(self) -> dict:
return {
"type": "object",
"properties": {
"tweet_id": {
"type": "string",
"description": "Tweet ID to get quote tweets for",
},
"cursor": {
"type": "string",
"description": "Pagination cursor from previous response",
},
},
"required": ["tweet_id"],
}
async def execute(self, ctx: ToolContext, tweet_id: str = "", cursor: str = None, **kwargs) -> ToolResult:
if not tweet_id:
return ToolResult(success=False, error="'tweet_id' is required")
try:
client = _get_client()
data = await asyncio.to_thread(client.get_tweet_quote, tweet_id, cursor=cursor)
return ToolResult(success=True, output=data)
except Exception as e:
return ToolResult(success=False, error=str(e))
class TwitterGetTrendsTool(BaseTool):
"""Get Twitter/X trends."""
@property
def name(self) -> str:
return "twitter_get_trends"
@property
def description(self) -> str:
return """Get Twitter/X trends.
Parameters:
- woeid: Where On Earth ID (optional)
- country: Country code/name (optional)
- category: Trend category (optional)
- limit: Number of trends to return (optional)
Returns: trends list"""
@property
def parameters(self) -> dict:
return {
"type": "object",
"properties": {
"woeid": {
"type": "string",
"description": "Where On Earth ID",
},
"country": {
"type": "string",
"description": "Country code/name",
},
"category": {
"type": "string",
"description": "Trend category",
},
"limit": {
"type": "integer",
"description": "Number of trends to return",
},
},
"required": [],
}
async def execute(
self,
ctx: ToolContext,
woeid: str = None,
country: str = None,
category: str = None,
limit: int = None,
**kwargs,
) -> ToolResult:
try:
client = _get_client()
data = await asyncio.to_thread(
client.get_trends,
woeid=woeid,
country=country,
category=category,
limit=limit,
)
return ToolResult(success=True, output=data)
except Exception as e:
return ToolResult(success=False, error=str(e))
class TwitterUserInfoTool(BaseTool):
"""Get a Twitter user's profile information."""
@property
def name(self) -> str:
return "twitter_user_info"
@property
def description(self) -> str:
return """Get a Twitter/X user's profile information: bio, follower count, following count, tweet count, verification status.
Parameters:
- username: Twitter handle without @ (required, e.g. "elonmusk")
Returns: user profile with name, bio, followers_count, following_count, tweet_count, verified"""
@property
def parameters(self) -> dict:
return {
"type": "object",
"properties": {
"username": {
"type": "string",
"description": "Twitter handle without @ (e.g. 'elonmusk')",
},
},
"required": ["username"],
}
async def execute(self, ctx: ToolContext, username: str = "", **kwargs) -> ToolResult:
if not username:
return ToolResult(success=False, error="'username' is required")
try:
client = _get_client()
data = await asyncio.to_thread(client.get_user_info, username)
return ToolResult(success=True, output=data)
except Exception as e:
return ToolResult(success=False, error=str(e))
class TwitterUserTweetsTool(BaseTool):
"""Get a user's recent tweets."""
@property
def name(self) -> str:
return "twitter_user_tweets"
@property
def description(self) -> str:
return """Get a Twitter/X user's recent tweets.
Parameters:
- username: Twitter handle without @ (required, e.g. "elonmusk")
- cursor: Pagination cursor from previous response (optional)
Returns: tweets array with text, metrics, timestamps, and next cursor"""
@property
def parameters(self) -> dict:
return {
"type": "object",
"properties": {
"username": {
"type": "string",
"description": "Twitter handle without @ (e.g. 'elonmusk')",
},
"cursor": {
"type": "string",
"description": "Pagination cursor from previous response",
},
},
"required": ["username"],
}
async def execute(self, ctx: ToolContext, username: str = "", cursor: str = None, **kwargs) -> ToolResult:
if not username:
return ToolResult(success=False, error="'username' is required")
try:
client = _get_client()
data = await asyncio.to_thread(client.get_user_tweets, username, cursor=cursor)
return ToolResult(success=True, output=data)
except Exception as e:
return ToolResult(success=False, error=str(e))
class TwitterUserFollowersTool(BaseTool):
"""Get a user's followers."""
@property
def name(self) -> str:
return "twitter_user_followers"
@property
def description(self) -> str:
return """Get a Twitter/X user's followers.
Parameters:
- username: Twitter handle without @ (required, e.g. "elonmusk")
- cursor: Pagination cursor from previous response (optional)
Returns: users array with profile data and next cursor"""
@property
def parameters(self) -> dict:
return {
"type": "object",
"properties": {
"username": {
"type": "string",
"description": "Twitter handle without @ (e.g. 'elonmusk')",
},
"cursor": {
"type": "string",
"description": "Pagination cursor from previous response",
},
},
"required": ["username"],
}
async def execute(self, ctx: ToolContext, username: str = "", cursor: str = None, **kwargs) -> ToolResult:
if not username:
return ToolResult(success=False, error="'username' is required")
try:
client = _get_client()
data = await asyncio.to_thread(client.get_user_followers, username, cursor=cursor)
return ToolResult(success=True, output=data)
except Exception as e:
return ToolResult(success=False, error=str(e))
class TwitterUserFollowingsTool(BaseTool):
"""Get accounts a user follows."""
@property
def name(self) -> str:
return "twitter_user_followings"
@property
def description(self) -> str:
return """Get accounts that a Twitter/X user follows.
Parameters:
- username: Twitter handle without @ (required, e.g. "elonmusk")
- cursor: Pagination cursor from previous response (optional)
Returns: users array with profile data and next cursor"""
@property
def parameters(self) -> dict:
return {
"type": "object",
"properties": {
"username": {
"type": "string",
"description": "Twitter handle without @ (e.g. 'elonmusk')",
},
"cursor": {
"type": "string",
"description": "Pagination cursor from previous response",
},
},
"required": ["username"],
}
async def execute(self, ctx: ToolContext, username: str = "", cursor: str = None, **kwargs) -> ToolResult:
if not username:
return ToolResult(success=False, error="'username' is required")
try:
client = _get_client()
data = await asyncio.to_thread(client.get_user_followings, username, cursor=cursor)
return ToolResult(success=True, output=data)
except Exception as e:
return ToolResult(success=False, error=str(e))
class TwitterSearchUsersTool(BaseTool):
"""Search for Twitter users."""
@property
def name(self) -> str:
return "twitter_search_users"
@property
def description(self) -> str:
return """Search for Twitter/X users by name or keyword.
Parameters:
- query: Search query (required, e.g. "crypto analyst", "bitcoin")
- cursor: Pagination cursor from previous response (optional)
Returns: users array with profile data and next cursor"""
@property
def parameters(self) -> dict:
return {
"type": "object",
"properties": {
"query": {
"type": "string",
"description": "Search query for users",
},
"cursor": {
"type": "string",
"description": "Pagination cursor from previous response",
},
},
"required": ["query"],
}
async def execute(self, ctx: ToolContext, query: str = "", cursor: str = None, **kwargs) -> ToolResult:
if not query:
return ToolResult(success=False, error="'query' is required")
try:
client = _get_client()
data = await asyncio.to_thread(client.search_users, query, cursor=cursor)
return ToolResult(success=True, output=data)
except Exception as e:
return ToolResult(success=False, error=str(e))
Related skills
How it compares
Use twitter for read-only social research inside agents; prefer dedicated posting integrations when publishing or engagement automation is required.
FAQ
How do agents call the twitter skill?
Read SKILL.md and run python3 via bash importing from /data/workspace/skills/twitter exports; it does not register runtime tools.
Why avoid twitter_user_tweets for polling?
last_tweets has no page-size parameter and bills about twenty tweets per call even when the window is empty; advanced_search is cheaper.
What env var does twitter require?
TWITTER_API_KEY is listed in metadata and auto-injected server-side on sc-proxy; no local key is needed on the agent machine.
Is Twitter safe to install?
skills.sh reports 1 of 3 security scanners passed. Review the Security Audits panel on this page before installing in production.