| name | x |
| description | Unified X (Twitter) CLI — fetch follows, diff snapshots, get likes/bookmarks, fetch latest posts, and take screenshots. Uses the official X API v2 with Bearer Token and OAuth 2.0 user-context auth. |
X CLI — Unified X (Twitter) Tool
Use the xapi CLI tool to interact with X (Twitter) via the official API v2. No cookies or scraping required.
Backward-compatible alias: twitter also points to the same CLI.
Setup
Bearer Token (required for most commands)
Get one from the X Developer Portal:
- Create or select a project/app
- Under "Keys and Tokens", find your Bearer Token
- Set the environment variable:
export X_BEARER_TOKEN='your_token_here'
OAuth 2.0 User Access Token (required for likes/bookmarks)
The likes and bookmarks endpoints require an OAuth 2.0 user access token — app-only Bearer Tokens are not supported for these.
- In the Developer Portal, go to your app's Settings → User authentication settings → Set up
- Enable OAuth 2.0 and select Web App (confidential client)
- Set a callback URL (e.g.
http://localhost:3000/callback)
- Complete the OAuth 2.0 PKCE flow to get an access token
- Request scopes:
like.read, bookmark.read, tweet.read, users.read, offline.access
- Set the environment variables:
export X_ACCESS_TOKEN='your_user_access_token_here'
export X_REFRESH_TOKEN='your_refresh_token_here'
Commands
| Command | Description |
|---|
xapi resolve <username> | Resolve username to user ID + profile info |
xapi fetch <username> | Fetch and snapshot the full following list |
xapi diff <snap_a> <snap_b> | Compare two snapshots for follows/unfollows |
xapi latest-post <username> | Fetch most recent original post for an account |
xapi likes [username] | Fetch liked tweets (default: HamelHusain, requires X_ACCESS_TOKEN) |
xapi bookmarks [username] | Fetch bookmarked tweets (default: HamelHusain, requires X_ACCESS_TOKEN) |
xapi screenshot <url> | Take a screenshot of a tweet (requires Playwright) |
Usage
Resolve a Username
xapi resolve elonmusk
Fetch a Following Snapshot
xapi fetch elonmusk
xapi fetch elonmusk -o /tmp/elon_following.json
xapi fetch elonmusk -v
Diff Two Snapshots
Compare two snapshots to detect new follows and unfollows:
xapi diff snapshot_before.json snapshot_after.json
xapi diff before.json after.json -o diff_result.json
xapi diff before.json after.json --fetch-posts -v
Fetch Latest Post
xapi latest-post someuser
xapi latest-post someuser --before 2026-01-15T00:00:00Z
xapi latest-post someuser --before-post 1234567890
xapi latest-post someuser -o latest.json
Fetch Likes
xapi likes
xapi likes someuser
xapi likes --limit 20
xapi likes -o likes.json
Fetch Bookmarks
xapi bookmarks
xapi bookmarks yourusername
xapi bookmarks --limit 50 -o bookmarks.json
Screenshot a Tweet
xapi screenshot https://x.com/user/status/1234567890
xapi screenshot https://x.com/user/status/1234567890 -o my_tweet.png
xapi screenshot https://x.com/user/status/1234567890 --full-page
xapi screenshot https://x.com/user/status/1234567890 --wait 5 -v
Options
| Option | Short | Description |
|---|
--output | -o | Output file path (default: stdout or auto-generated) |
--dir | -d | Snapshot directory for fetch (default: ~/.x/) |
--verbose | -v | Show progress information |
--fetch-posts | -p | Fetch latest post for unfollowed accounts (diff only) |
--before | | ISO 8601 datetime cutoff for latest-post |
--before-post | | Post ID cutoff for latest-post (returns posts older than this ID) |
--limit | -n | Maximum number of tweets to fetch (likes/bookmarks) |
--full-page | -f | Capture full page (screenshot only) |
--wait | -w | Seconds to wait for page load (screenshot only) |
Output Formats
Snapshot (fetch)
{
"watched_username": "elonmusk",
"watched_user_id": "44196397",
"fetch_timestamp": "2026-01-01T00:00:00+00:00",
"following_count": 500,
"following": [
{
"id": "12345",
"username": "someuser",
"name": "Some User",
"description": "Bio text",
"public_metrics": { "followers_count": 1000, "following_count": 200, "tweet_count": 5000 }
}
]
}
Diff Output
{
"snapshot_before": { "file": "...", "watched_username": "...", "fetch_timestamp": "...", "following_count": 500 },
"snapshot_after": { "file": "...", "watched_username": "...", "fetch_timestamp": "...", "following_count": 502 },
"new_follows_count": 3,
"unfollows_count": 1,
"new_follows": [ { "id": "...", "username": "...", "name": "..." } ],
"unfollows": [ { "id": "...", "username": "...", "name": "...", "latest_post": { "post_id": "...", "text": "...", "url": "..." } } ]
}
Likes / Bookmarks Output
[
{
"id": "1234567890",
"text": "Tweet content here...",
"created_at": "2026-01-01T00:00:00Z",
"author": { "id": "987654321", "username": "example_user", "name": "Example User" },
"public_metrics": { "like_count": 100, "retweet_count": 10 },
"url": "https://x.com/example_user/status/1234567890"
}
]
Environment Variables
X_BEARER_TOKEN — X API Bearer Token (required for resolve, fetch, diff, latest-post; also used by likes/bookmarks to resolve usernames)
X_ACCESS_TOKEN — OAuth 2.0 user access token (required for likes and bookmarks)
X_REFRESH_TOKEN — OAuth 2.0 refresh token (optional, for future token refresh support)
X_DEFAULT_USERNAME — Override the default username for likes/bookmarks (optional, default: HamelHusain)
If a required variable is missing, the CLI exits with a clear error explaining what is needed and how to set it up.
Default Username
The likes and bookmarks commands default to HamelHusain if no username is provided.
Three ways to override (pick one):
-
Environment variable (recommended for skill installers): Set X_DEFAULT_USERNAME to change the default for all invocations without editing code:
export X_DEFAULT_USERNAME='yourusername'
Add this to your ~/.bashrc or ~/.zshrc to persist it.
-
Per-invocation: Pass a username argument to override just that call:
xapi likes otherusername
xapi bookmarks otherusername
-
Edit source code: Change _FALLBACK_USERNAME at the top of hamel/x_cli.py:
_FALLBACK_USERNAME = "YourUsername"
Requirements
The hamel package must be installed: pip install hamel
For xapi screenshot: Playwright is included as a dependency, but you need to install browser binaries:
playwright install chromium
Verify Setup
After installing and setting the environment variables, run these commands to confirm everything works:
xapi --help
xapi resolve github
xapi latest-post someuser
xapi likes --limit 5
xapi bookmarks --limit 5
If xapi resolve github returns a JSON object with "username": "github", your Bearer Token is working.
If xapi likes returns JSON, your OAuth 2.0 user access token is working.
Known Limitations
- Likes and bookmarks require
X_ACCESS_TOKEN. These endpoints use OAuth 2.0 user-context auth and do not work with X_BEARER_TOKEN alone. You need to complete the OAuth 2.0 PKCE flow and set X_ACCESS_TOKEN. Required scopes: like.read (likes), bookmark.read (bookmarks), tweet.read, users.read.
- Timeline window. The user timeline endpoint only returns posts from approximately the last 7 days with app-only Bearer Token auth. Using
--before with older dates will return no results.
- Protected accounts. App-only auth cannot access following lists or tweets for protected accounts.
Rate Limits
The X API has rate limits per 15-minute window. The tool automatically handles rate limiting by waiting and retrying. For large following lists (>15k), fetches may take several minutes due to pagination and rate limits.
Troubleshooting
"X_BEARER_TOKEN environment variable is not set": Get a Bearer Token from the X Developer Portal and export it.
"API error 401": Your Bearer Token may be invalid or expired. Regenerate it in the Developer Portal.
"X_ACCESS_TOKEN environment variable is not set": Complete the OAuth 2.0 PKCE flow in the Developer Portal and set the token. See Setup section above.
"API error 403": For likes/bookmarks, ensure X_ACCESS_TOKEN is set (app-only Bearer Tokens are not supported). For other endpoints, check your app's access level in the Developer Portal.
"API error 429": Rate limited. The tool waits automatically, but if it persists, wait 15 minutes.
Empty following list: The account may be protected. App-only auth cannot access protected accounts' following lists.
playwright not installed: For screenshots, install with pip install playwright && playwright install chromium.
Examples
Monitor follows over time:
xapi fetch someuser -o ~/snapshots/day1.json
xapi fetch someuser -o ~/snapshots/day2.json
xapi diff ~/snapshots/day1.json ~/snapshots/day2.json --fetch-posts -v
Download likes and analyze with AI:
xapi likes -o /tmp/likes.json
ai-gem "What topics am I most interested in based on my likes?" /tmp/likes.json
Export bookmarks:
xapi bookmarks -o ~/bookmarks-backup.json
Get the last post before a specific tweet:
xapi latest-post someuser --before-post 1234567890