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' # optional, for future token refresh support
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
# Auto-saves to ~/.x/<username>_<timestamp>.json
xapi fetch elonmusk
# Save to a specific file
xapi fetch elonmusk -o /tmp/elon_following.json
# Verbose mode shows progress
xapi fetch elonmusk -v
Diff Two Snapshots
Compare two snapshots to detect new follows and unfollows:
# Basic diff
xapi diff snapshot_before.json snapshot_after.json
# Save diff output to file
xapi diff before.json after.json -o diff_result.json
# Also fetch the latest post for each unfollowed account
# (uses the newer snapshot's timestamp as cutoff)
xapi diff before.json after.json --fetch-posts -v
Fetch Latest Post
xapi latest-post someuser
# Get the latest post before a specific date
xapi latest-post someuser --before 2026-01-15T00:00:00Z
# Get the latest post before a specific post ID
xapi latest-post someuser --before-post 1234567890
# Save to file
xapi latest-post someuser -o latest.json
Fetch Likes
# Fetch liked tweets for the default user (HamelHusain)
xapi likes
# Fetch for a different user
xapi likes someuser
# Limit to 20 most recent likes
xapi likes --limit 20
# Save to file
xapi likes -o likes.json
Fetch Bookmarks
# Fetch bookmarked tweets for the default user (HamelHusain)
xapi bookmarks
# Fetch for a different user (must be your own account)
xapi bookmarks yourusername
# Limit and save
xapi bookmarks --limit 50 -o bookmarks.json
Screenshot a Tweet
# Screenshot a tweet (saves to tweet_<id>.png)
xapi screenshot https://x.com/user/status/1234567890
# Save to specific file
xapi screenshot https://x.com/user/status/1234567890 -o my_tweet.png
# Full page screenshot
xapi screenshot https://x.com/user/status/1234567890 --full-page
# Wait longer for slow loading tweets
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_USERNAMEto change the default for all invocations without editing code:export X_DEFAULT_USERNAME='yourusername'Add this to your
~/.bashrcor~/.zshrcto persist it.Per-invocation: Pass a username argument to override just that call:
xapi likes otherusername xapi bookmarks otherusernameEdit source code: Change
_FALLBACK_USERNAMEat the top ofhamel/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:
# 1. Check the CLI is installed
xapi --help
# 2. Test API access (lightweight call — uses X_BEARER_TOKEN)
xapi resolve github
# 3. Test latest post
xapi latest-post someuser
# 4. Test likes (requires X_ACCESS_TOKEN, defaults to HamelHusain)
xapi likes --limit 5
# 5. Test bookmarks (requires X_ACCESS_TOKEN, defaults to HamelHusain)
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 withX_BEARER_TOKENalone. You need to complete the OAuth 2.0 PKCE flow and setX_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
--beforewith 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
# ... later ...
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