PluginBench
Skill
Review
Audit score 70

x-api

affaan-m/ecc

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 and user data, searching content, and tracking engagement metrics. Use when you need to interact with X programmatically—posting, reading, or analyzing tweets and accounts.

  • Post individual tweets and threaded content programmatically
  • Read user timelines, mentions, and account data
  • Search X for tweets, trends, and conversations by query
  • Upload media (images) and post with attachments
  • Track engagement metrics and public analytics
  • Handle OAuth 1.0a (write operations) and OAuth 2.0 Bearer (read/search)

How to install x-api

npx skills add null --skill x-api
Prerequisites
  • X Developer account with API access and approved credentials
  • OAuth 1.0a credentials (consumer key/secret, access token/secret) for posting
  • OAuth 2.0 Bearer token for read-only operations
  • 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 API credentials (consumer key/secret and access tokens)
  2. 2.Choose authentication method: OAuth 1.0a for posting/writes, OAuth 2.0 Bearer for read-only operations
  3. 3.For posting: use oauth.post() to the /tweets endpoint with text payload
  4. 4.For reading timelines: use requests.get() to /users/{user_id}/tweets with desired fields
  5. 5.For searching: query the /tweets/search/recent endpoint with your search terms and max_results
  6. 6.Monitor rate limit headers (x-rate-limit-remaining, x-rate-limit-reset) and implement backoff logic
  7. 7.For media posts: upload to upload.twitter.com/1.1/media/upload.json first, then reference media_id in tweet payload

Use cases

Good for
  • Building a bot that posts updates or curated content to X automatically
  • Searching X for mentions of a brand or topic and analyzing sentiment
  • Pulling a user's recent tweets to extract voice samples for content modeling
  • Posting long-form threads programmatically after content generation
  • Monitoring timeline and engagement metrics for account analytics
Who it's for
  • Social media managers automating posting workflows
  • Content creators building X bots or integrations
  • Developers building brand voice or content generation pipelines
  • Analytics teams tracking X engagement and metrics
  • Automation engineers connecting X to larger workflows

x-api FAQ

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

OAuth 1.0a (user context) is required for posting tweets, managing accounts, and write operations. OAuth 2.0 Bearer (app-only) is simpler and sufficient for read-heavy operations like search and timeline reading.

How do I post a thread instead of a single tweet?

Post tweets sequentially, using the reply_to_tweet_id field to chain them together. Each tweet's ID becomes the in_reply_to_tweet_id for the next tweet in the thread.

What should I do if I hit a rate limit?

Read the x-rate-limit-reset header to find when the limit resets, wait that duration, then retry. Implement automatic backoff instead of hardcoding static rate limits, as X changes them frequently.

Can I post images or media with tweets?

Yes. Upload media first to upload.twitter.com/1.1/media/upload.json, capture the media_id_string, then include it in the media.media_ids array when posting the tweet.

Should I hardcode my API tokens in my code?

Never. Always use environment variables or .env files, add .env to .gitignore, and rotate tokens immediately if exposed.

Full instructions (SKILL.md)

Source of truth, from affaan-m/ecc.


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