Install any skill in seconds. Free to start, no credit card required.
Get Started Free →X/Twitter API integration for posting tweets, threads, reading timelines, search, and analytics. Covers OAuth auth patterns, rate limits, and platform-native content posting. Use when the user wants to interact with X programmatically.
.claude/skills/loulanyue-x-api/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-01 | ✗→✓ | ▲ Improved | 44% | 0% |
| case-02 | ✗→✓ | ▲ Improved | 27% | 0% |
| case-03 | ✗→✓ | ▲ Improved | 29% | 0% |
| case-05 | ✗→✓ | ▲ Improved | 61% | 0% |
| case-06 | ✗→✓ | ▲ Improved | 46% | 0% |
Programmatic interaction with X (Twitter) for posting, reading, searching, and analytics.
Best for: read-heavy operations, search, public data.
bash# Environment setup export X_BEARER_TOKEN="your-bearer-token"
pythonimport os import requests bearer = os.environ["X_BEARER_TOKEN"] headers = {"Authorization": f"Bearer {bearer}"} # Search recent tweets resp = requests.get( "https://api.x.com/2/tweets/search/recent", headers=headers, params={"query": "claude code", "max_results": 10} ) tweets = resp.json()
Required for: posting tweets, managing account, DMs.
bash# Environment setup — source before use export X_API_KEY="your-api-key" export X_API_SECRET="your-api-secret" export X_ACCESS_TOKEN="your-access-token" export X_ACCESS_SECRET="your-access-secret"
pythonimport os from requests_oauthlib import OAuth1Session oauth = OAuth1Session( os.environ["X_API_KEY"], client_secret=os.environ["X_API_SECRET"], resource_owner_key=os.environ["X_ACCESS_TOKEN"], resource_owner_secret=os.environ["X_ACCESS_SECRET"], )
pythonresp = oauth.post( "https://api.x.com/2/tweets", json={"text": "Hello from Claude Code"} ) resp.raise_for_status() tweet_id = resp.json()["data"]["id"]
pythondef post_thread(oauth, tweets: list[str]) -> list[str]: ids = [] reply_to = None for text in tweets: payload = {"text": text} if reply_to: payload["reply"] = {"in_reply_to_tweet_id": reply_to} resp = oauth.post("https://api.x.com/2/tweets", json=payload) tweet_id = resp.json()["data"]["id"] ids.append(tweet_id) reply_to = tweet_id return ids
pythonresp = requests.get( f"https://api.x.com/2/users/{user_id}/tweets", headers=headers, params={ "max_results": 10, "tweet.fields": "created_at,public_metrics", } )
pythonresp = requests.get( "https://api.x.com/2/tweets/search/recent", headers=headers, params={ "query": "from:affaanmustafa -is:retweet", "max_results": 10, "tweet.fields": "public_metrics,created_at", } )
pythonresp = requests.get( "https://api.x.com/2/users/by/username/affaanmustafa", headers=headers, params={"user.fields": "public_metrics,description,created_at"} )
python# Media upload uses v1.1 endpoint # Step 1: Upload media media_resp = oauth.post( "https://upload.twitter.com/1.1/media/upload.json", files={"media": open("image.png", "rb")} ) media_id = media_resp.json()["media_id_string"] # Step 2: Post with media resp = oauth.post( "https://api.x.com/2/tweets", json={"text": "Check this out", "media": {"media_ids": [media_id]}} )
X API rate limits vary by endpoint, auth method, and account tier, and they change over time. Always:
x-rate-limit-remaining and x-rate-limit-reset headers at runtimepythonimport time remaining = int(resp.headers.get("x-rate-limit-remaining", 0)) if remaining < 5: reset = int(resp.headers.get("x-rate-limit-reset", 0)) wait = max(0, reset - int(time.time())) print(f"Rate limit approaching. Resets in {wait}s")
pythonresp = oauth.post("https://api.x.com/2/tweets", json={"text": content}) if resp.status_code == 201: return resp.json()["data"]["id"] elif resp.status_code == 429: reset = int(resp.headers["x-rate-limit-reset"]) raise Exception(f"Rate limited. Resets at {reset}") elif resp.status_code == 403: raise Exception(f"Forbidden: {resp.json().get('detail', 'check permissions')}") else: raise Exception(f"X API error {resp.status_code}: {resp.text}")
.env files..env files. Add to .gitignore.Use content-engine skill to generate platform-native content, then post via X API:
content-engine — Generate platform-native content for Xcrosspost — Distribute content across X, LinkedIn, and other platforms| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-01 | fail→pass | 12,420 | 8,047 | -35% | 1 | 1 | 0% | 2,420 | 3,481 | +44% | 0 | 0 | — |
case-02 | fail→pass | 11,689 | 6,992 | -40% | 1 | 1 | 0% | 2,445 | 3,109 | +27% | 0 | 0 | — |
case-03 | fail→pass | 11,901 | 7,199 | -40% | 1 | 1 | 0% | 2,446 | 3,164 | +29% | 0 | 0 | — |
case-04 | pass→pass | 11,572 | 8,138 | -30% | 1 | 1 | 0% | 2,208 | 3,196 | +45% | 0 | 0 | — |
case-05 | fail→pass | 9,814 | 7,315 | -25% | 1 | 1 | 0% | 2,007 | 3,236 | +61% | 0 | 0 | — |
case-06 | fail→pass | 10,558 | 6,669 | -37% | 1 | 1 | 0% | 2,112 | 3,077 | +46% | 0 | 0 | — |
case-07 | pass→pass | 13,586 | 9,288 | -32% | 1 | 1 | 0% | 2,882 | 3,522 | +22% | 0 | 0 | — |
case-08 | pass→pass | 6,322 | 6,733 | +7% | 1 | 1 | 0% | 1,182 | 2,892 | +145% | 0 | 0 | — |
case-09 | fail→pass | 6,489 | 4,943 | -24% | 1 | 1 | 0% | 1,322 | 2,632 | +99% | 0 | 0 | — |
case-10 | fail→pass | 9,045 | 5,705 | -37% | 1 | 1 | 0% | 1,880 | 2,870 | +53% | 0 | 0 | — |
case-11 | pass→pass | 11,170 | 5,546 | -50% | 1 | 1 | 0% | 2,069 | 2,751 | +33% | 0 | 0 | — |
case-12 | pass→pass | 3,291 | 2,454 | -25% | 1 | 1 | 0% | 615 | 2,083 | +239% | 0 | 0 | — |
case-13 | pass→pass | 8,461 | 4,460 | -47% | 1 | 1 | 0% | 1,839 | 2,606 | +42% | 0 | 0 | — |
case-14 | pass→pass | 9,158 | 3,178 | -65% | 1 | 1 | 0% | 1,514 | 2,161 | +43% | 0 | 0 | — |
case-15 | pass→pass | 7,228 | 5,034 | -30% | 1 | 1 | 0% | 1,202 | 2,452 | +104% | 0 | 0 | — |
case-16 | pass→pass | 16,975 | 10,112 | -40% | 1 | 1 | 0% | 2,897 | 3,399 | +17% | 0 | 0 | — |
case-17 | pass→pass | 9,043 | 6,368 | -30% | 1 | 1 | 0% | 1,639 | 2,975 | +82% | 0 | 0 | — |
case-18 | pass→pass | 9,293 | 4,194 | -55% | 1 | 1 | 0% | 1,480 | 2,375 | +60% | 0 | 0 | — |
case-19 | fail→pass | 6,468 | 2,066 | -68% | 1 | 1 | 0% | 1,040 | 1,913 | +84% | 0 | 0 | — |
case-20 | fail→fail | 13,300 | 13,682 | +3% | 1 | 1 | 0% | 2,253 | 4,493 | +99% | 0 | 0 | — |
case-21 | fail→fail | 20,717 | 20,022 | -3% | 1 | 1 | 0% | 4,029 | 6,059 | +50% | 0 | 0 | — |
case-22 | fail→fail | 9,685 | 8,923 | -8% | 1 | 1 | 0% | 1,812 | 3,542 | +95% | 0 | 0 | — |
case-23 | fail→fail | 18,916 | 15,920 | -16% | 1 | 1 | 0% | 3,892 | 4,853 | +25% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 23 cases were attempted. The headline lift of +35 percentage points is the difference between those two pass rates over the 23 comparable cases. 1 case got worse with the skill loaded, and it is included in that figure.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.