Skip to main content
skill CX Codex

xurl · xdevplatform — Claude Code skill: what it does, how to install

CLI skill for authenticated X API requests. Lets agents post updates, inspect timelines, and manage direct messages.

Install into Claude Code · Skills CLI (Vercel) All methods and platforms ↓
npx skills add xdevplatform/xurl --skill xurl -a claude-code
npx skills add xdevplatform/xurl --skill xdevplatform-xurl -a claude-code
Open on GitHub

What xurl does

The skill connects the xurl CLI tool to your agent for making authenticated requests to X API v2. It provides human-friendly shortcut commands alongside raw curl-style queries, returning all responses as JSON to stdout. Supported actions include publishing posts, replying to threads, uploading media files, managing followers, and reading encrypted XChat messages.

You need the standalone xurl utility installed on your machine before running commands. App credentials must be placed in ~/.xurl/auth.yml manually by the user outside the agent session. Never allow the agent to read ~/.xurl/, invoke xurl token, use --verbose, or pass secret flags inline, because doing so leaks private keys and active tokens into the LLM context.

What it helps with

  • post updates, replies, and quotes directly to X
  • query user profiles, timelines, and search results
  • manage follows, likes, and bookmarks from the terminal
  • send media attachments and encrypted XChat messages

Example prompts for the agent

  • Check profile identity with xurl whoami
  • Verify configured applications using xurl auth status
  • Search recent posts with xurl search "news"

How to install xurl

1Skills CLI (Vercel)

npx skills add xdevplatform/xurl --skill xurl -a claude-code
npx skills add xdevplatform/xurl --skill xdevplatform-xurl -a claude-code

2Manually: put the files in place

~/.claude/skills/xdevplatform-xurl/SKILL.md

Copy the whole skill folder; for one project use .claude/skills/xdevplatform-xurl/

Step by step, with commands for each platform: How to install skills in Claude Code: five ways

Questions about xurl

How to install xurl in Claude Code?

Run npx skills add xdevplatform/xurl --skill xurl -a claude-code in your terminal. You must also install the separate xurl binary on your machine.

What does xurl do?

It allows your agent to publish posts, query timelines, search feeds, and handle encrypted chats on X via the v2 API, outputting JSON to stdout.

How should credentials be configured for xurl?

Edit ~/.xurl/auth.yml manually on your machine before interacting with the skill. Never paste tokens or secret flags into prompt messages or agent commands.

Source text

SKILL.md · MIT
---
name: xurl
description: A curl-like CLI tool for making authenticated requests to the X (Twitter) API. Use this skill when you need to post tweets, reply, quote, search, read posts, manage followers, send DMs, send or read end-to-end encrypted XChat messages, upload media, or interact with any X API v2 endpoint. Supports multiple apps, OAuth 2.0, OAuth 1.0a, and app-only auth.
---

# xurl — Agent Skill Reference

`xurl` is a CLI tool for the X API. It supports both **shortcut commands** (human/agent‑friendly one‑liners) and **raw curl‑style** access to any v2 endpoint. All commands return JSON to stdout.

---

## Prerequisites

This skill requires the `xurl` CLI utility: <https://github.com/xdevplatform/xurl>.

Before using any command you must be authenticated. Run `xurl auth status` to check.

### Secret Safety (Mandatory)

- Never read, print, parse, summarize, upload, or send anything under `~/.xurl/` (or copies of it) to the LLM context.
- `~/.xurl/keys.yml` contains XChat **private encryption keys** — the strictest no-read rule applies.
- Never pass `--pin` inline in agent/LLM sessions (`xurl chat keys restore --pin ...` leaks the recovery PIN to context and shell history). Run `xurl chat keys restore` without the flag so the PIN is prompted without echo, or have the user run it manually.
- Never ask the user to paste credentials/tokens into chat.
- The user must fill `~/.xurl/auth.yml` with required secrets manually on their own machine.
- Do not recommend or execute auth commands with inline secrets in agent/LLM sessions.
- Warn that using CLI secret options in agent sessions can leak credentials (prompt/context, logs, shell history).
- Never use `--verbose` / `-v` in agent/LLM sessions; it can expose sensitive headers/tokens in output.
- Never run `xurl token` in agent/LLM sessions: it prints a live OAuth2 access token to stdout, which is a credential and must not enter the LLM context.
- `xurl mcp` is for configuring an MCP client (it bridges stdio↔HTTP and injects the bearer token); it is not something to invoke directly from an agent/LLM session.
- Sensitive flags that must never be used in agent commands: `--bearer-token`, `--consumer-key`, `--consumer-secret`, `--access-token`, `--token-secret`, `--client-id`, `--client-secret`.
- To verify whether at least one app with credentials is already registered, run: `xurl auth status`.

### Register an app (recommended)

App credential registration must be done manually by the user outside the agent/LLM session.
After credentials are registered, authenticate against the app that holds those credentials:

```bash
xurl auth oauth2 --app APP_NAME
```

You can also run `xurl auth default APP_NAME` first and then use `xurl auth oauth2`.

On a remote/headless machine (no reachable browser callback), add `--headless`: `xurl auth oauth2 --app APP_NAME --headless` prints the authorization URL and reads the pasted redirect URL (or code) back, so no localhost callback is needed.

For multiple pre-configured apps, switch between them:
```bash
xurl auth default prod-app          # set default app
xurl auth default prod-app alice    # set default app + user
xurl --app dev-app /2/users/me      # one-off override
xurl auth apps redirect-uri get prod-app
xurl auth apps redirect-uri set prod-app http://localhost:8080/callback
```

### Other auth methods

Examples with inline secret flags are intentionally omitted. If OAuth1 or app-only auth is needed, the user must run those commands manually outside agent/LLM context.

Tokens are persisted to `~/.xurl/auth.yml` in YAML format (a legacy single-file `~/.xurl` is migrated automatically). Each app has its own isolated tokens and may also store a `redirect_uri`. `REDIRECT_URI` in the environment still takes precedence over the stored app value. Do not read this file (or anything under `~/.xurl/`) through the agent/LLM. Once authenticated, every command below will auto‑attach the right `Authorization` header.

---

## 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` |
| List a user's posts | `xurl posts @handle -n 10` |
| Home timeline | `xurl timeline -n 20` |
| Mentions | `xurl mentions -n 10` |
| Like | `xurl like POST_ID` |
| Unlike | `xurl unlike POST_ID` |
| Repost | `xurl repost POST_ID` |
| Undo repost | `xurl unrepost POST_ID` |
| Bookmark | `xurl bookmark POST_ID` |
| Remove bookmark | `xurl unbookmark POST_ID` |
| List bookmarks | `xurl bookmarks -n 10` |
| List likes | `xurl likes -n 10` |
| Follow | `xurl follow @handle` |
| Unfollow | `xurl unfollow @handle` |
| List following | `xurl following -n 20` |
| List followers | `xurl followers -n 20` |
| Block | `xurl block @handle` |
| Unblock | `xurl unblock @handle` |
| Mute | `xurl mute @handle` |
| Unmute | `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` |
| **Encrypted Chat (XChat)** | |
| Chat key status | `xurl chat keys status` |
| Restore chat keys | `xurl chat keys restore` (PIN prompted; never pass `--pin` in agent sessions) |
| Import chat keys | `xurl chat keys import` (blob prompted; avoid passing it as an argument) |
| List chat inbox | `xurl chat conversations` |
| Read a conversation | `xurl chat read @handle -n 50` |
| Send encrypted message | `xurl chat send @handle "message"` |
| Listen for new messages | `xurl chat listen @handle` |
| Rotate a conversation key | `xurl chat rotate CONV --yes` (write op — see notes) |
| Send with an attachment | `xurl chat send CONV "text" --file path/to/img.png` |
| Reply to a message | `xurl chat send CONV "text" --reply-to SEQUENCE_ID` |
| Download an attachment | `xurl chat download CONV MEDIA_HASH_KEY -o out.png` |
| Add group members | `xurl chat add-members GROUP @user --yes` (write op) |
| Mark read (explicit) | `xurl chat mark-read CONV` |
| Typing indicator (explicit) | `xurl chat typing CONV` |
| **App Management** | |
| Register app | Manual, outside agent (do not pass secrets via agent) |
| List apps | `xurl auth apps list` |
| Update app config | Manual, outside agent (do not pass secrets via agent) |
| View app redirect URI | `xurl auth apps redirect-uri get [NAME]` |
| Set app redirect URI | `xurl auth apps redirect-uri set NAME URI` |
| Remove app | `xurl auth apps remove NAME` |
| Set default (interactive) | `xurl auth default` |
| Set default (command) | `xurl auth default APP_NAME [USERNAME]` |
| Use app per-request | `xurl --app NAME /2/users/me` |
| Auth status | `xurl auth status` |

> **Post IDs vs URLs:** Anywhere `POST_ID` appears above you can also paste a full post URL (e.g. `https://x.com/user/status/1234567890`) — xurl extracts the ID automatically.

> **Usernames:** Leading `@` is optional. `@elonmusk` and `elonmusk` both work.

---

## Command Details

### Posting

```bash
# Simple post
xurl post "Hello world!"

# Post with media (upload first, then attach)
xurl media upload photo.jpg          # → note the media_id from response
xurl post "Check this out" --media-id MEDIA_ID

# Multiple media
xurl post "Thread pics" --media-id 111 --media-id 222

# Reply to a post (by ID or URL)
xurl reply 1234567890 "Great point!"
xurl reply https://x.com/user/status/1234567890 "Agreed!"

# Reply with media
xurl reply 1234567890 "Look at this" --media-id MEDIA_ID

# Quote a post
xurl quote 1234567890 "Adding my thoughts"

# Delete your own post
xurl delete 1234567890
```

### Reading

```bash
# Read a single post (returns author, text, metrics, entities)
xurl read 1234567890
xurl read https://x.com/user/status/1234567890

# Search recent posts (default 10 results)
xurl search "golang"
xurl search "from:elonmusk" -n 20
xurl search "#buildinpublic lang:en" -n 15
```

### User Info

```bash
# Your own profile
xurl whoami

# Look up any user
xurl user elonmusk
xurl user @XDevelopers

# List a user's recent posts (by @username)
xurl posts elonmusk
xurl posts @XDevelopers -n 25
```

### Timelines & Mentions

```bash
# Home timeline (reverse chronological)
xurl timeline
xurl timeline -n 25

# Your mentions
xurl mentions
xurl mentions -n 20
```

### Engagement

```bash
# Like / unlike
xurl like 1234567890
xurl unlike 1234567890

# Repost / undo
xurl repost 1234567890
xurl unrepost 1234567890

# Bookmark / remove
xurl bookmark 1234567890
xurl unbookmark 1234567890

# List your bookmarks / likes
xurl bookmarks -n 20
xurl likes -n 20
```

### Social Graph

```bash
# Follow / unfollow
xurl follow @XDevelopers
xurl unfollow @XDevelopers

# List who you follow / your followers
xurl following -n 50
xurl followers -n 50

# List another user's following/followers
xurl following --of elonmusk -n 20
xurl followers --of elonmusk -n 20

# Block / unblock
xurl block @spammer
xurl unblock @spammer

# Mute / unmute
xurl mute @annoying
xurl unmute @annoying
```

### Direct Messages

```bash
# Send a DM
xurl dm @someuser "Hey, saw your post!"

# List recent DM events
xurl dms
xurl dms -n 25
```

### Encrypted Chat (XChat)

`xurl chat` is an end-to-end encrypted XChat client: encryption and decryption happen locally via the chat-xdk crypto library, so the server only sees ciphertext. Requires OAuth2 user auth with `dm.read` + `dm.write` scopes, and is available on macOS (Intel/Apple Silicon) and Linux amd64 — release binaries include it there; elsewhere (Windows, Linux arm64/i386) `xurl chat` prints a stub that says so.

**Keys come from another XChat client** — xurl never generates or registers encryption keys. The account must already have keys (e.g. from the X app); bring them to this machine once with `restore` (Juicebox PIN recovery) or `import` (an exported key blob). Private keys are stored in `~/.xurl/keys.yml` (mode 600) — never read that file into LLM context.

A conversation is addressed by `@username`, a bare user id, or a conversation id (1:1 ids look like `123-456`; group ids look like `g123`). Every command accepts `-u USERNAME` to act as a specific authenticated account.

```bash
# 1. Keys — one-time setup (xurl never generates/registers keys)
xurl chat keys status                # local key presence/fingerprint + registered versions
xurl chat keys restore               # recover from Juicebox; prompts for the PIN (no echo)
xurl chat keys import                # paste an exported private-key blob (no echo)

# 2. Browse the inbox
xurl chat conversations              # pretty list; decrypts group names when keys are present
xurl chat conversations --json       # raw JSON

# 3. Read history (oldest first; auto-marks the conversation read)
xurl chat read @someuser
xurl chat read g1234567890 -n 50     # -n = how many events to fetch (max 100)
xurl chat read @someuser --json      # decrypted events as JSON (each has id, sequence_id, content)
xurl chat read @someuser --no-mark-read   # read without sending a read receipt

# 4. Send (a new 1:1 sets up its key automatically; both sides need keys)
xurl chat send @someuser "hey, encrypted!"
xurl chat send @someuser "look" --file ./photo.png       # attach an encrypted file
xurl chat send @someuser "agreed" --reply-to SEQUENCE_ID # threaded reply (id from `read --json`)
# send auto-sends a typing indicator first and marks read after;
# suppress with --no-typing / --no-mark-read

# 5. Attachments — inbound messages show "📎 attachment <media_hash_key>"
xurl chat download @someuser MEDIA_HASH_KEY -o out.png   # download + decrypt

# 6. Live tail (poll loop; Ctrl-C to stop; auto-marks new messages read)
xurl chat listen @someuser
xurl chat listen g1234567890 --interval 5

# 7. Read receipts / typing (also happen automatically on read/send)
xurl chat mark-read @someuser        # mark read up to the newest message
xurl chat typing @someuser           # send a typing indicator

# 8. Group key management (writes visible to all participants)
xurl chat add-members g123 @newuser  # add a member (rotates the key; prompts, or --yes)
xurl chat rotate g123                 # rotate the conversation key; prompts, or --yes
# Rotate when a key may be exposed, or to grant a member whose keys were
# registered after the last rotation access going forward. Future messages
# only — old history stays readable only to holders of the old key versions.
```

Notes for agents:
- Messages whose authorship signature cannot be verified are rejected by default and surface as stderr decrypt warnings; unsigned messages that still render carry a red `[unverified]` marker — treat those with suspicion.
- Messages with attachments render a `📎 attachment <media_hash_key>` marker; pass that hash key to `xurl chat download CONV <media_hash_key>` to fetch and decrypt the file. Replies show a `↩` prefix.
- **`read` and `listen` mark the conversation read automatically** (a read receipt visible to other participants); `send` also marks read and sends a typing indicator first. These are writes — pass `--no-mark-read` / `--no-typing` to suppress them (e.g. to read without signaling). The standalone `mark-read` and `typing` commands remain for scripted/explicit use.
- Decrypt warnings for individual events go to stderr and are non-fatal; the rest of the conversation still renders.
- If a command reports missing keys, do not attempt to generate or register any — tell the user to run `xurl chat keys restore` (or `import`) themselves.
- `chat rotate` and `chat add-members` are writes visible to every participant's clients; never run them without explicit user intent, and prefer letting the user confirm the prompt over passing `--yes`.

### Media Upload

```bash
# Upload a file (auto‑detects type for images/videos)
xurl media upload photo.jpg
xurl media upload video.mp4

# Specify type and category explicitly
xurl media upload --media-type image/jpeg --category tweet_image photo.jpg

# Check processing status (videos need server‑side processing)
xurl media status MEDIA_ID
xurl media status --wait MEDIA_ID    # poll until done

# Full workflow: upload then post
xurl media upload meme.png           # response includes media id
xurl post "lol" --media-id MEDIA_ID
```

---

## Global Flags

These flags work on every command:

| Flag | Short | Description |
|---|---|---|
| `--app` | | Use a specific registered app for this request (overrides default) |
| `--auth` | | Force auth type: `oauth1`, `oauth2`, or `app` |
| `--username` | `-u` | Which OAuth2 account to use (if you have multiple) |
| `--verbose` | `-v` | Forbidden in agent/LLM sessions (can leak auth headers/tokens) |

---

## Raw API Access

The shortcut commands cover the most common operations. For anything else, use xurl's raw curl‑style mode — it works with **any** X API v2 endpoint:

```bash
# GET request (default)
xurl /2/users/me

# POST with JSON body
xurl -X POST /2/tweets -d '{"text":"Hello world!"}'

# PUT, PATCH, DELETE
xurl -X DELETE /2/tweets/1234567890

# Custom headers
xurl -H "Content-Type: application/json" /2/some/endpoint

# Force streaming mode
xurl -s /2/tweets/search/stream

# Full URLs also work
xurl https://api.x.com/2/users/me
```

---

## Streaming

Streaming endpoints are auto‑detected. Known streaming endpoints include:
- `/2/tweets/search/stream`
- `/2/tweets/sample/stream`
- `/2/tweets/sample10/stream`

You can force streaming on any endpoint with `-s`:
```bash
xurl -s /2/some/endpoint
```

---

## Output Format

All commands return **JSON** to stdout, pretty‑printed with syntax highlighting. The output structure matches the X API v2 response format. A typical response looks like:

```json
{
  "data": {
    "id": "1234567890",
    "text": "Hello world!"
  }
}
```

Errors are also returned as JSON:
```json
{
  "errors": [
    {
      "message": "Not authorized",
      "code": 403
    }
  ]
}
```

---

## Common Workflows

### Post with an image
```bash
# 1. Upload the image
xurl media upload photo.jpg
# 2. Copy the media_id from the response, then post
xurl post "Check out this photo!" --media-id MEDIA_ID
```

### Reply to a conversation
```bash
# 1. Read the post to understand context
xurl read https://x.com/user/status/1234567890
# 2. Reply
xurl reply 1234567890 "Here are my thoughts..."
```

### Search and engage
```bash
# 1. Search for relevant posts
xurl search "topic of interest" -n 10
# 2. Like an interesting one
xurl like POST_ID_FROM_RESULTS
# 3. Reply to it
xurl reply POST_ID_FROM_RESULTS "Great point!"
```

### Check your activity
```bash
# See who you are
xurl whoami
# Check your mentions
xurl mentions -n 20
# Check your timeline
xurl timeline -n 20
```

### Set up multiple apps
```bash
# App credentials must already be configured manually outside agent/LLM context.
# Authenticate users on each pre-configured app
xurl auth default prod
xurl auth oauth2                       # authenticates on prod app

xurl auth default staging
xurl auth oauth2                       # authenticates on staging app

# Switch between them
xurl auth default prod alice           # prod app, alice user
xurl --app staging /2/users/me         # one-off request against staging
```

---

## Error Handling

- Non‑zero exit code on any error.
- API errors are printed as JSON to stdout (so you can still parse them).
- Auth errors suggest re‑running `xurl auth oauth2` or checking your tokens.
- If a command requires your user ID (like, repost, bookmark, follow, etc.), xurl will automatically fetch it via `/2/users/me`. When that endpoint is unreliable, use `--username USERNAME` or authenticate with `xurl auth oauth2 --app APP_NAME USERNAME` so xurl can fall back to username lookup.
- If X returns `client-forbidden` / `client-not-enrolled` after successful auth, check the app’s X developer-console package and environment. In current testing, moving the app to `Pay-per-use` and `Production` fixed `/2/*` read failures without changing local `xurl` auth data.

---

## Notes

- **Rate limits:** The X API enforces rate limits per endpoint. If you get a 429 error, wait and retry. Write endpoints (post, reply, like, repost) have stricter limits than read endpoints.
- **Scopes:** OAuth 2.0 tokens are requested with broad scopes. If you get a 403 on a specific action, your token may lack the required scope — re‑run `xurl auth oauth2` to get a fresh token.
- **Token refresh:** OAuth 2.0 tokens auto‑refresh when expired. No manual intervention needed.
- **Multiple apps:** Each app has its own isolated credentials, tokens, and optional stored `redirect_uri`. Configure credentials manually outside agent/LLM context, then switch with `xurl auth default` or `--app`.
- **Redirect URI precedence:** The effective redirect URI resolves from `REDIRECT_URI` in the environment first, then the app's stored `redirect_uri` in `~/.xurl/auth.yml`, then the built-in default.
- **Redirect URI management:** Use `xurl auth apps redirect-uri get [NAME]`, `xurl auth apps redirect-uri set NAME URI`, or `xurl auth apps update NAME --redirect-uri URI` to inspect and manage the stored per-app callback value.
- **X platform enrollment:** A successful OAuth callback does not guarantee `/2/*` reads will work. If you see `client-not-enrolled`, verify the app is in the correct X package/environment. Current confirmed fix: `Apps` -> `Manage apps` -> `Move to package` -> choose `Pay-per-use`, then move the app to `Production`.
- **Multiple accounts:** You can authenticate multiple OAuth 2.0 accounts per app and switch between them with `--username` / `-u` or set a default with `xurl auth default APP USER`.
- **Default user:** When no `-u` flag is given, xurl uses the default user for the active app (set via `xurl auth default`). If no default user is set, it uses the first available token.
- **Token storage:** `~/.xurl` is a directory; `~/.xurl/auth.yml` holds each app's credentials and tokens. Never read or send anything under `~/.xurl/` to LLM context.
- **Chat key storage:** `~/.xurl/keys.yml` holds XChat **private encryption keys** per user (mode 600). Losing it means losing the ability to decrypt on this machine (recoverable via `xurl chat keys restore` if a Juicebox PIN backup exists). Never read or send this file to LLM context.
- **Chat key registration:** xurl performs none — no public-key registration and no Juicebox writes. Only keys already registered by another XChat client can be restored or imported; unregistered keys are rejected.
- **Access tokens:** `xurl token` prints a valid (refreshed) OAuth2 access token for the active app to stdout, refreshing and persisting it if expired. It never opens a browser. The output is a secret — use it only in the user's own scripts, never in agent/LLM sessions.
- **MCP bridge:** `xurl mcp [URL]` bridges a stdio MCP client to a remote Streamable HTTP MCP server (default `https://api.x.com/mcp`), injecting `Authorization: Bearer <token>` and refreshing the token automatically. On first run with no cached token it opens the browser for a one-time OAuth2 login using the `CLIENT_ID`/`CLIENT_SECRET` from its environment (the handshake waits for it, so set a generous `startup_timeout_sec`); on a headless host, authenticate out-of-band first with `xurl auth oauth2 --headless`. Configure it in an MCP client via the npm launcher: `{"command":"npx","args":["-y","@xdevplatform/xurl","mcp","https://api.x.com/mcp"],"env":{"CLIENT_ID":"...","CLIENT_SECRET":"..."},"startup_timeout_sec":300}`.

© xdevplatform · MIT · xdevplatform/xurl

Similar skills

All skills for Backend and APIs →

xurl by other authors comes first.

Project sponsors

Companies that keep this analytics open
Gold sponsor

Impressions, clicks and code copies — open statistics. We keep the numbers public for the same reason NeBlask itself is public. Become a sponsor