Xurl: Official X/Twitter API CLI for Hermes Agent
X/Twitter via xurl CLI: raw post search, posting, DM, media.
Written by Neura Market from the official Hermes Agent documentation for Xurl. Commands, paths, and version numbers are reproduced from the source unchanged.
Read the official documentationXurl is the X developer platform's official command-line interface for the X API, bundled as a skill in Hermes Agent. It gives you direct, authenticated access to nearly every X API v2 endpoint, from posting and searching to direct messages and media uploads. You would reach for this when your agent needs to interact with X as a real user: posting replies, searching for raw post objects to engage with, managing your social graph, or automating any workflow that the official API supports.
What it does
Xurl replaces the older xitter skill (which wrapped a third-party Python CLI) with a tool maintained by the X developer platform team. It supports OAuth 2.0 PKCE with auto-refresh and covers a substantially larger API surface. Every command returns JSON to stdout, so parsing results in an agent workflow is straightforward.
With xurl you can:
- Post, reply, quote, and delete posts
- Search for raw posts (actual post JSON with IDs you can engage with) and read timelines and mentions
- Like, repost, and bookmark
- Follow, unfollow, block, and mute
- Send and list direct messages
- Upload images and video
- Access any X API v2 endpoint in raw curl-style mode
- Work with multiple apps and multiple accounts
Before you start
Prerequisites
- Platform: Linux or macOS. The shell script or
go installare the easiest on Linux. - X Developer Account: You need an app registered at https://developer.x.com/en/portal/dashboard with a redirect URI set to
http://localhost:8080/callback. The app type must be "Web app, automated app or bot" in User Authentication Settings, not "Native App". - Credentials: You will need the app's Client ID and Client Secret from the X Developer Portal.
- Cost: X API access is typically paid for meaningful usage. Many failures are plan or permission problems, not code problems.
Installation
Pick ONE method. On Linux, the shell script or go install are the easiest.
# Shell script (installs to ~/.local/bin, no sudo, works on Linux + macOS)
curl -fsSL https://raw.githubusercontent.com/xdevplatform/xurl/main/install.sh | bash
# Homebrew (macOS)
brew install --cask xdevplatform/tap/xurl
# npm
npm install -g @xdevplatform/xurl
# Go
go install github.com/xdevplatform/xurl@latest
Verify:
xurl --help
xurl auth status
If xurl is installed but auth status shows no apps or tokens, the user needs to complete auth manually.
One-Time User Setup (user runs these outside the agent)
These steps must be performed by the user directly, NOT by the agent, because they involve pasting secrets. Direct the user to this block; do not execute it for them.
- Create or open an app at https://developer.x.com/en/portal/dashboard
- Set the redirect URI to
http://localhost:8080/callback - Copy the app's Client ID and Client Secret
- Register the app locally (user runs this):
xurl auth apps add my-app --client-id YOUR_CLIENT_ID --client-secret YOUR_CLIENT_SECRET
- Authenticate (specify
--appto bind the token to your app):
xurl auth oauth2 --app my-app
(This opens a browser for the OAuth 2.0 PKCE flow.)
If X returns a UsernameNotFound error or 403 on the post-OAuth /2/users/me lookup, pass your handle explicitly (xurl v1.1.0+):
xurl auth oauth2 --app my-app YOUR_USERNAME
This binds the token to your handle and skips the broken /2/users/me call.
6. Set the app as default so all commands use it:
xurl auth default my-app
- Verify:
xurl auth status
xurl whoami
After this, the agent can use any command below without further setup. OAuth 2.0 tokens auto-refresh.
Common pitfall: If you omit
--app my-appfromxurl auth oauth2, the OAuth token is saved to the built-indefaultapp profile which has no client-id or client-secret. Commands will fail with auth errors even though the OAuth flow appeared to succeed. If you hit this, re-runxurl auth oauth2 --app my-appandxurl auth default my-app.
Docker HOME pitfall: In the official Hermes Docker layout,
/opt/dataisHERMES_HOME, but Hermes tool subprocesses use/opt/data/homeasHOME. That means~/.xurlresolves to/opt/data/home/.xurlfor Hermes-runxurlcommands, not/opt/data/.xurl. Run the user setup with the same HOME:
HOME=/opt/data/home xurl auth apps add my-app --client-id YOUR_CLIENT_ID --client-secret YOUR_CLIENT_SECRET
HOME=/opt/data/home xurl auth oauth2 --app my-app YOUR_USERNAME
HOME=/opt/data/home xurl auth default my-app YOUR_USERNAME
HOME=/opt/data/home xurl auth status
If
HOME=/opt/data xurl auth statussucceeds butHOME=/opt/data/home xurl auth statusshows no apps or tokens, Hermes tool calls will not see the credentials.
Quick Reference
| Action | Command |
|---|---|
| Post | xurl post "Hello world!" |
| Reply | xurl reply POST_ID "Nice post!" |
| Quote | xurl quote POST_ID "My take" |
| Delete a post | xurl delete POST_ID |
| Read a post | xurl read POST_ID |
| Search posts | xurl search "QUERY" -n 10 |
| Who am I | xurl whoami |
| Look up a user | xurl user @handle |
| Home timeline | xurl timeline -n 20 |
| Mentions | xurl mentions -n 10 |
| Like / Unlike | xurl like POST_ID / xurl unlike POST_ID |
| Repost / Undo | xurl repost POST_ID / xurl unrepost POST_ID |
| Bookmark / Remove | xurl bookmark POST_ID / xurl unbookmark POST_ID |
| List bookmarks / likes | xurl bookmarks -n 10 / xurl likes -n 10 |
| Follow / Unfollow | xurl follow @handle / xurl unfollow @handle |
| Following / Followers | xurl following -n 20 / xurl followers -n 20 |
| Block / Unblock | xurl block @handle / xurl unblock @handle |
| Mute / Unmute | xurl mute @handle / xurl unmute @handle |
| Send DM | xurl dm @handle "message" |
| List DMs | xurl dms -n 10 |
| Upload media | xurl media upload path/to/file.mp4 |
| Media status | xurl media status MEDIA_ID |
| List apps | xurl auth apps list |
| Remove app | xurl auth apps remove NAME |
| Set default app | xurl auth default APP_NAME [USERNAME] |
| Per-request app | xurl --app NAME /2/users/me |
| Auth status | xurl auth status |
Notes:
POST_IDaccepts full URLs too (e.g.https://x.com/user/status/1234567890) xurl extracts the ID.- Usernames work with or without a leading
@.
Command Details
Posting
xurl post "Hello world!"
xurl post "Check this out" --media-id MEDIA_ID
xurl post "Thread pics" --media-id 111 --media-id 222
xurl reply 1234567890 "Great point!"
xurl reply https://x.com/user/status/1234567890 "Agreed!"
xurl reply 1234567890 "Look at this" --media-id MEDIA_ID
xurl quote 1234567890 "Adding my thoughts"
xurl delete 1234567890
Reading & Search
xurl search queries the X index as your authenticated account and returns raw post objects IDs, authors, full text so results can be immediately engaged with (reply, like, repost, quote). Use it when you need the actual posts rather than a summarized answer about a topic.
xurl read 1234567890
xurl read https://x.com/user/status/1234567890
xurl search "golang"
xurl search "from:elonmusk" -n 20
xurl search "#buildinpublic lang:en" -n 15
For X Articles, use raw API mode instead of the read shortcut. xurl read expects a post ID or post URL; do not put read before a /2/tweets/... endpoint. Request the article tweet field and ingest data.article.plain_text from the JSON response:
xurl --app APP_NAME '/2/tweets/2057909493250539891?expansions=author_id,attachments.media_keys,referenced_tweets.id&tweet.fields=created_at,lang,public_metrics,context_annotations,entities,possibly_sensitive,conversation_id,in_reply_to_user_id,referenced_tweets,article'
Users, Timeline, Mentions
xurl whoami
xurl user elonmusk
xurl user @XDevelopers
xurl timeline -n 25
xurl mentions -n 20
Engagement
xurl like 1234567890
xurl unlike 1234567890
xurl repost 1234567890
xurl unrepost 1234567890
xurl bookmark 1234567890
xurl unbookmark 1234567890
xurl bookmarks -n 20
xurl likes -n 20
Social Graph
xurl follow @XDevelopers
xurl unfollow @XDevelopers
xurl following -n 50
xurl followers -n 50
# Another user's graph
xurl following --of elonmusk -n 20
xurl followers --of elonmusk -n 20
xurl block @spammer
xurl unblock @spammer
xurl mute @annoying
xurl unmute @annoying
Direct Messages
xurl dm @someuser "Hey, saw your post!"
xurl dms -n 25
Media Upload
# Auto-detect type
xurl media upload photo.jpg
xurl media upload video.mp4
# Explicit type/category
xurl media upload --media-type image/jpeg --category tweet_image photo.jpg
# Videos need server-side processing — check status (or poll)
xurl media status MEDIA_ID
xurl media status --wait MEDIA_ID
# Full workflow
xurl media upload meme.png # returns media id
xurl post "lol" --media-id MEDIA_ID
Raw API Access
The shortcuts cover common operations. For anything else, use raw curl-style mode against any X API v2 endpoint:
# GET
xurl /2/users/me
# POST with JSON body
xurl -X POST /2/tweets -d '{"text":"Hello world!"}'
# DELETE / PUT / PATCH
xurl -X DELETE /2/tweets/1234567890
# Custom headers
xurl -H "Content-Type: application/json" /2/some/endpoint
# Force streaming
xurl -s /2/tweets/search/stream
# Full URLs also work
xurl https://api.x.com/2/users/me
Global Flags
| Flag | Short | Description |
|---|---|---|
--app | Use a specific registered app (overrides default) | |
--auth | Force auth type: oauth1, oauth2, or app | |
--username | -u | Which OAuth2 account to use (if multiple exist) |
--verbose | -v | Forbidden in agent sessions leaks auth headers |
--trace | -t | Add X-B3-Flags: 1 trace header |
Streaming
Streaming endpoints are auto-detected. Known ones include:
/2/tweets/search/stream/2/tweets/sample/stream/2/tweets/sample10/stream
Force streaming on any endpoint with -s.
Output Format
All commands return JSON to stdout. Structure mirrors X API v2:
{ "data": { "id": "1234567890", "text": "Hello world!" } }
Errors are also JSON:
{ "errors": [ { "message": "Not authorized", "code": 403 } ] }
Common Workflows
Post with an image
xurl media upload photo.jpg
xurl post "Check out this photo!" --media-id MEDIA_ID
Reply to a conversation
xurl read https://x.com/user/status/1234567890
xurl reply 1234567890 "Here are my thoughts..."
Search and engage
xurl search "topic of interest" -n 10
xurl like POST_ID_FROM_RESULTS
xurl reply POST_ID_FROM_RESULTS "Great point!"
Check your activity
xurl whoami
xurl mentions -n 20
xurl timeline -n 20
Multiple apps (credentials pre-configured manually)
xurl auth default prod alice # prod app, alice user
xurl --app staging /2/users/me # one-off against staging
Error Handling
- Non-zero exit code on any error.
- API errors are still printed as JSON to stdout, so you can parse them.
- Auth errors have the user re-run
xurl auth oauth2outside the agent session. - Commands that need the caller's user ID (like, repost, bookmark, follow, etc.) will auto-fetch it via
/2/users/me. An auth failure there surfaces as an auth error.
Agent Workflow
- Verify prerequisites:
xurl --helpandxurl auth status. - Before using
xurl search, check intent. Reach for it when the task needs actual post objects, authenticated account context, or leads into an X write action it is the right surface when the user wants posts they can engage with, not just a summary of a topic. - Check default app has credentials. Parse the
auth statusoutput. The default app is marked with▸. If the default app showsoauth2: (none)but another app has a valid oauth2 user, tell the user to runxurl auth defaultto fix it. This is the most common setup mistake the user added an app with a custom name but never set it as default, so xurl keeps trying the emptydefaultprofile. - If auth is missing entirely, stop and direct the user to the "One-Time User Setup" section do NOT attempt to register apps or pass secrets yourself.
- Start with a cheap read (
xurl whoami,xurl user @handle,xurl search ... -n 3) to confirm reachability. - Confirm the target post/user and the user's intent before any write action (post, reply, like, repost, DM, follow, block, delete).
- Only the
xurlcommand output (or the raw X API response) proves that a state-changing X action happened. Never report a write as done based on any other source search results, summaries, or prior context. - Use JSON output directly every response is already structured.
- Never paste
~/.xurlcontents back into the conversation.
Troubleshooting
| Symptom | Cause | Fix |
|---|---|---|
| Auth errors after successful OAuth flow | Token saved to default app (no client-id/secret) instead of your named app | xurl auth oauth2 --app my-app then xurl auth default my-app |
unauthorized_client during OAuth | App type set to "Native App" in X dashboard | Change to "Web app, automated app or bot" in User Authentication Settings |
UsernameNotFound or 403 on /2/users/me right after OAuth | X not returning username reliably from /2/users/me | Re-run xurl auth oauth2 --app my-app YOUR_USERNAME (xurl v1.1.0+) to pass the handle explicitly |
| 401 on every request | Token expired or wrong default app | Check xurl auth status verify ▸ points to an app with oauth2 tokens |
client-forbidden / client-not-enrolled | X platform enrollment issue | Dashboard Apps Manage Move to "Pay-per-use" package Production environment |
CreditsDepleted | $0 balance on X API | Buy credits (min $5) in Developer Console Billing |
media processing failed on image upload | Default category is amplify_video | Add --category tweet_image --media-type image/png |
| Two "Client Secret" values in X dashboard | UI bug first is actually Client ID | Confirm on the "Keys and tokens" page; ID ends in MTpjaQ |
Notes
- Rate limits: X enforces per-endpoint rate limits. A 429 means wait and retry. Write endpoints (post, reply, like, repost) have tighter limits than reads.
- Scopes: OAuth 2.0 tokens use broad scopes. A 403 on a specific action usually means the token is missing a scope have the user re-run
xurl auth oauth2. - Token refresh: OAuth 2.0 tokens auto-refresh. Nothing to do.
- Multiple apps: Each app has isolated credentials/tokens. Switch with
xurl auth defaultor--app. - Multiple accounts per app: Select with
-u / --username, or set a default withxurl auth default APP USER. - Token storage:
~/.xurlis YAML. In Docker, use the Hermes subprocess HOME (/opt/data/homein the official image) so tokens land under/opt/data/home/.xurl. Never read or send this file to LLM context. - Cost: X API access is typically paid for meaningful usage. Many failures are plan/permission problems, not code problems.
When not to use it
Do not use xurl when you only need a summarized answer about a topic on X. The xurl search command returns raw post objects, which is the right tool when you need to engage with posts (like, reply, repost) or need the authenticated account's context. If the user just wants to know what people are saying about a subject, a different skill that summarizes search results may be more appropriate.
Limits and gotchas
- Secret safety is mandatory. Never read, print, parse, summarize, upload, or send
~/.xurlto LLM context. Never ask the user to paste credentials or tokens into chat. Never use--verbose/-vin agent sessions it can expose auth headers or tokens. Forbidden flags in agent commands (they accept inline secrets):--bearer-token,--consumer-key,--consumer-secret,--access-token,--token-secret,--client-id,--client-secret. - App credential registration and credential rotation must be done by the user manually, outside the agent session.
- The most common setup mistake is omitting
--app my-appfromxurl auth oauth2, which saves the token to the built-indefaultapp profile that has no client-id or client-secret. Commands will fail with auth errors even though the OAuth flow appeared to succeed. - In Docker, the Hermes tool subprocesses use
/opt/data/homeasHOME, not/opt/data. If you set up credentials withHOME=/opt/data, Hermes tool calls will not see them. - X Articles cannot be read with the
xurl readshortcut. You must use raw API mode and request thearticletweet field. - Media uploads default to the
amplify_videocategory, which can causemedia processing failedon image uploads. Add--category tweet_image --media-type image/pngfor images.
What pairs with this
This skill is part of the Hermes Agent bundled skills. It replaces the older xitter skill. The upstream CLI is maintained at https://github.com/xdevplatform/xurl by the X developer platform team. The upstream agent skill is at https://github.com/openclaw/openclaw/blob/main/skills/xurl/SKILL.md.