PluginBench
Skill
Review
Audit score 70

x-api

affaan-m/everything-claude-code

Post tweets, read timelines, search, and track analytics on X/Twitter via OAuth.

What is x-api?

Programmatic X/Twitter API integration for posting tweets and threads, reading timelines, searching content, and pulling analytics. Use when the user wants to interact with X programmatically—posting, reading user data, searching conversations, or building X bots.

  • Post individual tweets and threaded content via OAuth 1.0a
  • Read user timelines and mentions with configurable fields
  • Search recent tweets with query operators and filters
  • Upload media and post tweets with images or video
  • Pull user data by username with public metrics
  • Track rate limits and handle backoff automatically

How to install x-api

npx skills add https://github.com/affaan-m/everything-claude-code --skill x-api
Prerequisites
  • X Developer Account with API access and appropriate tier
  • OAuth 1.0a credentials (consumer key/secret, access token/secret) for write operations
  • Bearer token for read-only operations (search, timeline, user data)
  • Environment variables configured: X_CONSUMER_KEY, X_CONSUMER_SECRET, X_ACCESS_TOKEN, X_ACCESS_TOKEN_SECRET
Claude Code
Cursor
Windsurf
Cline

How to use x-api

  1. 1.Set up environment variables with your X OAuth credentials
  2. 2.For read operations, use Bearer token with requests.get() to search or read timelines
  3. 3.For write operations, initialize OAuth1Session with consumer and access credentials
  4. 4.Post a tweet by calling oauth.post() to https://api.x.com/2/tweets with text payload
  5. 5.For threads, loop through tweet texts, capturing each tweet_id and passing it as in_reply_to_tweet_id for the next
  6. 6.Upload media via the v1.1 endpoint, then include media_ids in your tweet payload
  7. 7.Monitor x-rate-limit-remaining and x-rate-limit-reset headers to avoid hitting rate limits
  8. 8.Handle 429 (rate limited), 403 (forbidden), and other errors with appropriate backoff or user feedback

Use cases

Good for
  • Post generated content or threads to X after approval
  • Search X for trends, conversations, or specific user posts
  • Build voice profiles by pulling recent original posts from a user
  • Integrate X posting into content workflows with brand-voice and content-engine
  • Monitor engagement metrics and timeline activity programmatically
Who it's for
  • Content creators and marketers automating X posting
  • Developers building X integrations or bots
  • Teams using voice modeling to match posting style
  • Anyone building cross-platform content distribution

x-api FAQ

What's the difference between OAuth 2.0 Bearer Token and OAuth 1.0a?

Bearer Token (OAuth 2.0) is for read-heavy operations like search and public data. OAuth 1.0a (User Context) is required for posting tweets, managing your account, and any write operations.

How do I post a thread?

Loop through your tweet texts, post each one, capture the tweet_id, and pass it as in_reply_to_tweet_id in the next tweet's payload. The skill includes a post_thread() example function.

How do I handle rate limits?

Check the x-rate-limit-remaining and x-rate-limit-reset headers after each request. If remaining is low, calculate the wait time and back off automatically instead of relying on hardcoded limits.

Can I post images or videos?

Yes. Upload media via the v1.1 upload endpoint, get the media_id_string, then include it in your tweet payload under media.media_ids.

What should I do if I get a 403 Forbidden error?

Check that your OAuth credentials have write permissions, your account tier supports the operation, and the endpoint is available in your region. Verify permissions at developer.x.com.

Full instructions (SKILL.md)

Source of truth, from affaan-m/everything-claude-code.


name: x-api description: 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. metadata: origin: ECC

X API

Drift-prone skill. X API endpoints, access tiers, quotas, and write permissions change frequently. Verify current developer docs and account access before quoting rate limits or implementing a posting/search flow.

Programmatic interaction with X (Twitter) for posting, reading, searching, and analytics.

When to Activate

  • User wants to post tweets or threads programmatically
  • Reading timeline, mentions, or user data from X
  • Searching X for content, trends, or conversations
  • Building X integrations or bots
  • Analytics and engagement tracking
  • User says "post to X", "tweet", "X API", or "Twitter API"

Authentication

OAuth 2.0 Bearer Token (App-Only)

Best for: read-heavy operations, search, public data.

# Environment setup
export X_BEARER_TOKEN="your-bearer-token"
import 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()

OAuth 1.0a (User Context)

Required for: posting tweets, managing account, DMs, and any write flow.

# Environment setup — source before use
export X_CONSUMER_KEY="your-consumer-key"
export X_CONSUMER_SECRET="your-consumer-secret"
export X_ACCESS_TOKEN="your-access-token"
export X_ACCESS_TOKEN_SECRET="your-access-token-secret"

Legacy aliases such as X_API_KEY, X_API_SECRET, and X_ACCESS_SECRET may exist in older setups. Prefer the X_CONSUMER_* and X_ACCESS_TOKEN_SECRET names when documenting or wiring new flows.

import os
from requests_oauthlib import OAuth1Session

oauth = OAuth1Session(
    os.environ["X_CONSUMER_KEY"],
    client_secret=os.environ["X_CONSUMER_SECRET"],
    resource_owner_key=os.environ["X_ACCESS_TOKEN"],
    resource_owner_secret=os.environ["X_ACCESS_TOKEN_SECRET"],
)

Core Operations

Post a Tweet

resp = oauth.post(
    "https://api.x.com/2/tweets",
    json={"text": "Hello from Claude Code"}
)
resp.raise_for_status()
tweet_id = resp.json()["data"]["id"]

Post a Thread

def 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

Read User Timeline

resp = requests.get(
    f"https://api.x.com/2/users/{user_id}/tweets",
    headers=headers,
    params={
        "max_results": 10,
        "tweet.fields": "created_at,public_metrics",
    }
)

Search Tweets

resp = 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",
    }
)

Pull Recent Original Posts for Voice Modeling

resp = requests.get(
    "https://api.x.com/2/tweets/search/recent",
    headers=headers,
    params={
        "query": "from:affaanmustafa -is:retweet -is:reply",
        "max_results": 25,
        "tweet.fields": "created_at,public_metrics",
    }
)
voice_samples = resp.json()

Get User by Username

resp = requests.get(
    "https://api.x.com/2/users/by/username/affaanmustafa",
    headers=headers,
    params={"user.fields": "public_metrics,description,created_at"}
)

Upload Media and Post

# 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]}}
)

Rate Limits

X API rate limits vary by endpoint, auth method, and account tier, and they change over time. Always:

  • Check the current X developer docs before hardcoding assumptions
  • Read x-rate-limit-remaining and x-rate-limit-reset headers at runtime
  • Back off automatically instead of relying on static tables in code
import 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")

Error Handling

resp = 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}")

Security

  • Never hardcode tokens. Use environment variables or .env files.
  • Never commit .env files. Add to .gitignore.
  • Rotate tokens if exposed. Regenerate at developer.x.com.
  • Use read-only tokens when write access is not needed.
  • Store OAuth secrets securely — not in source code or logs.

Integration with Content Engine

Use brand-voice plus content-engine to generate platform-native content, then post via X API:

  1. Pull recent original posts when voice matching matters
  2. Build or reuse a VOICE PROFILE
  3. Generate content with content-engine in X-native format
  4. Validate length and thread structure
  5. Return the draft for approval unless the user explicitly asked to post now
  6. Post via X API only after approval
  7. Track engagement via public_metrics

Related Skills

  • brand-voice — Build a reusable voice profile from real X and site/source material
  • content-engine — Generate platform-native content for X
  • crosspost — Distribute content across X, LinkedIn, and other platforms
  • connections-optimizer — Reorganize the X graph before drafting network-driven outreach