CtrlK
BlogDocsLog inGet started
Tessl Logo

x-api

Read and post to X/Twitter via API. Check mentions, post tweets, search. Uses OAuth 2.0 user context with token refresh.

Install with Tessl CLI

npx tessl i github:joelhooks/joelclaw --skill x-api
What are skills?

81

Does it follow best practices?

Validation for skill structure

SKILL.md
Review
Evals

X/Twitter API Skill

Basic X (Twitter) API access for the @joelclaw account until a proper CLI is built (ADR-0119).

Authentication

OAuth 1.0a User Context. Tokens do not expire — no refresh dance needed.

Secrets (in agent-secrets)

  • x_consumer_key — API Key (OAuth 1.0a consumer key)
  • x_consumer_secret — API Key Secret (OAuth 1.0a consumer secret)
  • x_access_token — Access Token (OAuth 1.0a, format: numeric-alphanumeric)
  • x_access_token_secret — Access Token Secret (OAuth 1.0a)

Signing requests

OAuth 1.0a requires cryptographic signing. Use requests-oauthlib (Python) or equivalent:

export CK=$(secrets lease x_consumer_key)
export CS=$(secrets lease x_consumer_secret)
export AT=$(secrets lease x_access_token)
export ATS=$(secrets lease x_access_token_secret)

uv run --with requests-oauthlib python3 << 'PYEOF'
import os
from requests_oauthlib import OAuth1Session

client = OAuth1Session(
    os.environ['CK'],
    client_secret=os.environ['CS'],
    resource_owner_key=os.environ['AT'],
    resource_owner_secret=os.environ['ATS'],
)

r = client.get("https://api.twitter.com/2/users/me")
print(r.json())
PYEOF

Revoke leases after use

secrets revoke --all

Account Info

  • Account: @joelclaw
  • User ID: 2022779096049311744
  • Scopes: Read and write (OAuth 1.0a app permissions)

Common Operations

All operations require OAuth 1.0a signing. Use the Python pattern from Authentication section above, then:

Common API calls (inside OAuth1Session)

# Check mentions
r = client.get("https://api.twitter.com/2/users/2022779096049311744/mentions",
    params={"max_results": 10, "tweet.fields": "created_at,author_id,text",
            "expansions": "author_id", "user.fields": "username,name"})

# Get my timeline
r = client.get("https://api.twitter.com/2/users/2022779096049311744/tweets",
    params={"max_results": 10, "tweet.fields": "created_at,public_metrics"})

# Post a tweet
r = client.post("https://api.twitter.com/2/tweets", json={"text": "your tweet"})

# Reply to a tweet
r = client.post("https://api.twitter.com/2/tweets",
    json={"text": "@user reply", "reply": {"in_reply_to_tweet_id": "TWEET_ID"}})

# Search recent tweets
r = client.get("https://api.twitter.com/2/tweets/search/recent",
    params={"query": "joelclaw", "max_results": 10, "tweet.fields": "created_at,author_id,text"})

# Get user by username
r = client.get("https://api.twitter.com/2/users/by/username/USERNAME",
    params={"user.fields": "description,public_metrics"})

# Follow a user
my_id = "2022779096049311744"
r = client.post(f"https://api.twitter.com/2/users/{my_id}/following",
    json={"target_user_id": "TARGET_USER_ID"})

# Delete a tweet
r = client.delete("https://api.twitter.com/2/tweets/TWEET_ID")

Rate Limits

  • Mentions: 180 requests / 15 min
  • Post tweet: 200 tweets / 15 min (app-level)
  • Search: 180 requests / 15 min
  • User lookup: 300 requests / 15 min

Rules

  • Never engage with shitcoin/scam mentions. Ignore them entirely.
  • Never post financial advice or token endorsements.
  • Joel approves all tweets before posting unless explicitly told otherwise.
  • Always revoke leases after use — don't leave secrets in memory.
  • OAuth 1.0a tokens don't expire — no refresh needed. If auth fails, tokens were regenerated in the developer portal.
Repository
joelhooks/joelclaw
Last updated
Created

Is this your skill?

If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.