---
triggers:
  - x cli
  - x command
  - X API CLI
  - x コマンド
  - X CLI
  - x setup
  - x configure
  - x credentials
  - OAuth 1.0a setup
  - X API credentials
  - X認証設定
  - x 認証設定
  - credentials.toml
  - x liked
  - liked tweets
  - liked posts
  - yesterday likes
  - JST likes
  - 前日いいね
  - 昨日のいいね
  - liked list
  - x mcp
  - x MCP server
  - MCP X API
  - Remote MCP X
  - X MCP サーバー
  - MCP サーバー起動
description: Complete reference for the x CLI — X (formerly Twitter) API v2 CLI + Remote MCP server. Covers installation, all subcommands, credential setup, liked list workflows, and MCP server configuration.
---

# x — X (Twitter) API v2 CLI & Remote MCP Server

`x` is a single Go binary providing both a CLI and a Remote MCP server (Streamable HTTP) for the X (formerly Twitter) API v2. Authentication is OAuth 1.0a User Context.

---

## Installation

```bash
# Homebrew
brew install youyo/tap/x

# Go install
go install github.com/youyo/x/cmd/x@latest
```

---

## Subcommands

| Subcommand | Description |
|---|---|
| `configure` | Interactive OAuth 1.0a credential setup and validation |
| `me` | Fetch the authenticated user profile |
| `liked list` | Fetch liked tweets (JST date helpers, full pagination, NDJSON) |
| `tweet get` | Look up a tweet by ID or URL; `--ids` for batch (up to 100) |
| `tweet liking-users` | List users who liked a tweet |
| `tweet retweeted-by` | List users who retweeted a tweet |
| `tweet quote-tweets` | List quote tweets of a tweet |
| `tweet search` | Search recent tweets — past 7 days (**Basic tier required**) |
| `tweet thread` | Fetch a full thread via conversation_id + search/recent (**Basic tier required**) |
| `timeline tweets` | Fetch a user's tweet timeline (default: self) |
| `timeline mentions` | Fetch mentions of a user (default: self) |
| `timeline home` | Fetch the home timeline of the authenticated user |
| `user get` | Look up users by ID, @username, URL, or batch |
| `user search` | Search users by keyword |
| `user following` | List users that a user follows |
| `user followers` | List followers of a user |
| `user blocking` | List users blocked by the authenticated user (self only) |
| `user muting` | List users muted by the authenticated user (self only) |
| `list get` | Look up a List by numeric ID or X List URL |
| `list tweets` | Fetch tweets from a List |
| `list members` | List members of a List |
| `list owned` | List Lists owned by a user (default: self) |
| `list followed` | List Lists followed by a user (default: self) |
| `list memberships` | List Lists that a user is a member of (default: self) |
| `list pinned` | List pinned Lists (self only) |
| `space get` | Look up a Space by ID or URL (active/live only) |
| `space by-ids` | Look up multiple Spaces by IDs (batch, up to 100) |
| `space search` | Search active Spaces by keyword |
| `space by-creator` | Look up Spaces by creator user IDs |
| `space tweets` | Fetch tweets associated with a Space |
| `trends get` | Fetch trends by WOEID (Tokyo=1118370 / Japan=23424856 / Worldwide=1) |
| `trends personal` | Fetch personalized trends for the authenticated user |
| `dm list` | List recent DM events (**Pro tier recommended**, Basic ~1 call/24h) |
| `dm get` | Look up a single DM event by ID |
| `dm conversation` | Fetch DM events for a specific conversation |
| `dm with` | Fetch 1-on-1 DM events with a specific user |
| `mcp` | Start the Remote MCP server (Streamable HTTP) |
| `version` | Show version, commit, and build date |

> **Tier notes:**
> - `tweet search` / `tweet thread` require **Basic tier** ($200/month) or higher.
> - `dm *` commands require **Pro tier** ($5,000/month) for practical use; Basic has ~1 call/24h limit.
> - All other commands work on any tier that includes OAuth 1.0a User Context access.

---

## Exit Codes

All subcommands share the same exit code convention:

| Code | Meaning |
|---|---|
| 0 | Success |
| 1 | Generic error (rate limit, API error) |
| 2 | Argument error (invalid flag, bad format) |
| 3 | Auth error (expired/revoked token) |
| 4 | Permission error (missing Read / User Context scope) |
| 5 | Not found |

---

## Setup & Configure

### Prerequisites

Obtain four values from the [X Developer Portal](https://developer.twitter.com/):

- API Key (`X_API_KEY`)
- API Secret (`X_API_SECRET`)
- Access Token (`X_ACCESS_TOKEN`)
- Access Token Secret (`X_ACCESS_TOKEN_SECRET`)

Your app must have **Read** permission and **OAuth 1.0a User Context** enabled.

### Interactive Setup

```bash
x configure
```

Prompts for all four fields and writes them to `credentials.toml` with permissions `0600`.

### Inspect Paths

```bash
x configure --print-paths           # JSON
x configure --print-paths --no-json # Human-readable
```

Default locations (XDG-compliant):

| File | Default path |
|---|---|
| `credentials.toml` | `~/.local/share/x/credentials.toml` (`0600`) |
| `config.toml` | `~/.config/x/config.toml` (`0644`) |

### Validate Configuration

```bash
x configure --check
x configure --check --no-json
```

Checks: file permissions are exactly `0600`, all four fields are present, `config.toml` contains no secret keys.

### Env-Only Operation (CI / Lambda / MCP mode)

Skip `credentials.toml` entirely and export env vars directly:

```bash
export X_API_KEY=your_api_key
export X_API_SECRET=your_api_secret
export X_ACCESS_TOKEN=your_access_token
export X_ACCESS_TOKEN_SECRET=your_access_token_secret
```

Env vars always take priority over `credentials.toml` in CLI mode. **MCP mode reads env vars only — `credentials.toml` is never consulted** (see [MCP Server](#mcp-server)).

For GitHub Actions:

```yaml
env:
  X_API_KEY: ${{ secrets.X_API_KEY }}
  X_API_SECRET: ${{ secrets.X_API_SECRET }}
  X_ACCESS_TOKEN: ${{ secrets.X_ACCESS_TOKEN }}
  X_ACCESS_TOKEN_SECRET: ${{ secrets.X_ACCESS_TOKEN_SECRET }}
```

### Security

Add `credentials.toml` to `.gitignore`. Do not put API keys in `config.toml` — `x configure --check` will reject them.

### Verify Auth

```bash
x me           # JSON
x me --no-json # Human-readable
```

### Troubleshooting

| Exit | Symptom | Action |
|---|---|---|
| 3 | Auth error on `x me` or `x liked` | Re-run `x configure` or update env vars; check token expiry |
| 4 | Permission error | Enable Read + OAuth 1.0a User Context in Developer Portal |
| 1 | `credentials.toml` perm != `0600` | `chmod 0600 ~/.local/share/x/credentials.toml` |
| 2 | Missing required fields | Re-run `x configure` and supply all four values |

---

## liked list Workflows

### Most Common Command

```bash
x liked list --yesterday-jst --all --ndjson
```

- `--yesterday-jst` — Expands to JST previous day 00:00–23:59 as `start_time`/`end_time`
- `--all` — Auto-follows `next_token` up to `--max-pages` (default: 50)
- `--ndjson` — One JSON object per line, HTML escape disabled — ideal for pipe processing

### All Flags

| Flag | Default | Description |
|---|---|---|
| `--yesterday-jst` | — | Previous JST day (overrides `--since-jst`) |
| `--since-jst YYYY-MM-DD` | — | JST date (overrides `--start-time`/`--end-time`) |
| `--start-time RFC3339` | — | Earliest tweet time in UTC |
| `--end-time RFC3339` | — | Latest tweet time in UTC |
| `--all` | false | Auto-follow pagination |
| `--max-pages N` | 50 | Max pages when `--all` is set |
| `--max-results N` | 100 | Tweets per page (1–100) |
| `--pagination-token TOKEN` | — | Resume from a previous `next_token` |
| `--user-id ID` | authenticated user | Target a different user (requires app-level permission) |
| `--ndjson` | false | Line-delimited JSON output |
| `--no-json` | false | Human-readable tab-separated output |
| `--tweet-fields FIELDS` | `id,text,author_id,created_at,entities,public_metrics,note_tweet` | Comma-separated tweet fields |
| `--user-fields FIELDS` | `username,name` | Comma-separated user fields |
| `--expansions FIELDS` | `author_id` | Comma-separated expansions |

Defaults for `--tweet-fields`, `--user-fields`, `--expansions`, and `--max-pages` can be overridden in `~/.config/x/config.toml` under `[liked]`.

### Date / Time Patterns

```bash
# Previous JST day (most common)
x liked list --yesterday-jst --all --ndjson

# Specific JST date
x liked list --since-jst 2026-05-12 --all --ndjson

# RFC3339 UTC time range (manual)
x liked list \
  --start-time 2026-05-11T15:00:00Z \
  --end-time   2026-05-12T14:59:59Z \
  --all --ndjson
```

Prefer `--since-jst` / `--yesterday-jst` over manual UTC conversion to avoid timezone mistakes.

### Output Modes

| Flag | Use case |
|---|---|
| (default) | Full `{data, includes, meta}` JSON response |
| `--ndjson` | One tweet per line — use for pipes and streaming |
| `--no-json` | Tab-separated human-readable — do not parse with scripts |

### jq Pipeline Examples

```bash
# Filter Japanese-language tweets
x liked list --yesterday-jst --all --ndjson \
  | jq -c 'select(.lang == "ja")'

# Extract text of tweets mentioning Go, Rust, or TypeScript
x liked list --yesterday-jst --all --ndjson \
  | jq -c 'select(.text | test("Go|Rust|TypeScript"))' \
  | jq -r '.text'

# Count likes
x liked list --yesterday-jst --all --ndjson | wc -l

# Save for later processing
x liked list --yesterday-jst --all --ndjson > /tmp/yesterday-liked.ndjson
```

### Manual Pagination

```bash
# Page 1
x liked list --max-results 100 | tee /tmp/page1.json

# Extract next_token
NEXT=$(jq -r '.meta.next_token' /tmp/page1.json)

# Page 2
x liked list --max-results 100 --pagination-token "$NEXT"
```

`meta.next_token` is absent (or `null`) when there are no more pages.

### Rate Limits

When the limit is hit, `x liked list` exits `1` and prints error details. Wait for the rate limit window to reset (typically 15 minutes). Use `--max-pages` to cap requests per run in bulk operations.

### Notes

- `exit 0` with an empty `data` array is normal — it means no likes in that time range.
- Use `jq '.meta.result_count'` to distinguish empty from error programmatically.
- In automation scripts, always consume JSON/NDJSON output, not `--no-json`.

---

## MCP Server

### Critical Invariant

**MCP mode reads credentials from env vars only.** `credentials.toml` is never read, regardless of whether the file exists. This is a spec invariant (§11) designed for immutable infrastructure (Lambda, containers) where secrets come from SSM Parameter Store → env.

Required env vars:

```
X_API_KEY
X_API_SECRET
X_ACCESS_TOKEN
X_ACCESS_TOKEN_SECRET
```

### Start Command

```bash
x mcp [flags]
```

| Flag | Env var | Default | Description |
|---|---|---|---|
| `--auth` | `X_MCP_AUTH` | `idproxy` | Auth mode: `none` \| `apikey` \| `idproxy` |
| `--host` | `X_MCP_HOST` | `127.0.0.1` | Bind host |
| `--port` | `X_MCP_PORT` | `8080` | Bind port |
| `--path` | `X_MCP_PATH` | `/mcp` | MCP endpoint path |
| `--apikey-env` | — | `X_MCP_API_KEY` | Env var name holding the shared secret (apikey mode only) |

### Auth Modes

#### `none` — No authentication (local dev only)

```bash
X_API_KEY=... X_API_SECRET=... \
X_ACCESS_TOKEN=... X_ACCESS_TOKEN_SECRET=... \
x mcp --auth none --host 127.0.0.1 --port 8080
```

Do not expose `--auth none` to the network.

#### `apikey` — Bearer token

```bash
X_MCP_API_KEY=my-shared-secret \
X_API_KEY=... X_API_SECRET=... \
X_ACCESS_TOKEN=... X_ACCESS_TOKEN_SECRET=... \
x mcp --auth apikey --host 0.0.0.0 --port 8080
```

Clients must send `Authorization: Bearer my-shared-secret`. The secret is compared with constant-time comparison to prevent timing attacks.

To use a custom env var name:

```bash
x mcp --auth apikey --apikey-env MY_SECRET_VAR
```

#### `idproxy` — OIDC cookie session

```bash
STORE_BACKEND=memory \
X_API_KEY=... X_API_SECRET=... \
X_ACCESS_TOKEN=... X_ACCESS_TOKEN_SECRET=... \
x mcp --auth idproxy --host 0.0.0.0 --port 8080
```

Additional env vars required by `idproxy`:

```
OIDC_ISSUER
OIDC_CLIENT_ID
OIDC_CLIENT_SECRET
COOKIE_SECRET
EXTERNAL_URL
STORE_BACKEND    # memory | sqlite | redis | dynamodb
```

Store backend options:

| `STORE_BACKEND` | Use case |
|---|---|
| `memory` | Single-instance, dev/test (state lost on restart) |
| `sqlite` | Single-instance, persistent (`SQLITE_PATH` for custom path) |
| `redis` | Multi-instance, low latency (`REDIS_URL`, e.g. `redis://localhost:6379/0`) |
| `dynamodb` | Serverless / Lambda, managed persistence |

### Provided MCP Tools

| MCP Tool | Equivalent CLI | Notes |
|---|---|---|
| `get_user_me` | `x me` | Authenticated user profile |
| `get_liked_tweets` | `x liked list` | With JST date helpers and pagination |
| `get_tweet` | `x tweet get` | Single tweet by ID |
| `get_tweets` | `x tweet get --ids` | Batch lookup (up to 100 IDs) |
| `get_liking_users` | `x tweet liking-users` | |
| `get_retweeted_by` | `x tweet retweeted-by` | |
| `get_quote_tweets` | `x tweet quote-tweets` | |
| `search_recent_tweets` | `x tweet search` | Basic tier required |
| `get_tweet_thread` | `x tweet thread` | Basic tier required |
| `get_user_tweets` | `x timeline tweets` | |
| `get_user_mentions` | `x timeline mentions` | |
| `get_home_timeline` | `x timeline home` | |
| `get_user` | `x user get` (by ID) | Lookup by numeric user ID |
| `get_user_by_username` | `x user get` (by @username) | Lookup by @handle |
| `get_user_following` | `x user following` | |
| `get_user_followers` | `x user followers` | |
| `get_list` | `x list get` | |
| `get_list_tweets` | `x list tweets` | |
| `search_spaces` | `x space search` | |
| `get_trends` | `x trends get` | |

### CLI Only (No MCP Equivalent)

The following CLI commands have no MCP tool counterpart:

`user search`, `user blocking`, `user muting`, `list members`, `list owned`, `list followed`, `list memberships`, `list pinned`, `space get`, `space by-ids`, `space by-creator`, `space tweets`, `trends personal`, `dm list`, `dm get`, `dm conversation`, `dm with`, `configure`, `version`

### Lambda Deployment

1. Build the binary with GoReleaser (or use the prebuilt GitHub Release artifact).
2. Set env vars via SSM Parameter Store → Lambda environment:

```
X_API_KEY        → ssm:/x/api_key
X_API_SECRET     → ssm:/x/api_secret
X_ACCESS_TOKEN   → ssm:/x/access_token
X_ACCESS_TOKEN_SECRET → ssm:/x/access_token_secret
X_MCP_AUTH       → apikey
X_MCP_API_KEY    → ssm:/x/mcp_api_key
STORE_BACKEND    → dynamodb
```

3. Set `X_MCP_HOST=0.0.0.0` to bind on all interfaces.
4. Place an ALB or API Gateway in front for HTTPS termination.

Do not mount `credentials.toml` in Lambda — MCP mode ignores it.

### Docker Deployment

```bash
docker run --rm \
  -e X_API_KEY=... \
  -e X_API_SECRET=... \
  -e X_ACCESS_TOKEN=... \
  -e X_ACCESS_TOKEN_SECRET=... \
  -e X_MCP_AUTH=apikey \
  -e X_MCP_API_KEY=... \
  -e X_MCP_HOST=0.0.0.0 \
  -p 8080:8080 \
  ghcr.io/youyo/x:latest mcp
```

The server handles graceful shutdown on SIGTERM/SIGINT.

---

## Quick Reference

```bash
# Verify auth
x me --no-json

# Fetch yesterday's likes (JST) as NDJSON — most common pipeline command
x liked list --yesterday-jst --all --ndjson

# Filter liked tweets by language or keyword
x liked list --yesterday-jst --all --ndjson | jq -c 'select(.lang == "ja")'
x liked list --yesterday-jst --all --ndjson | jq -c 'select(.text | test("Go|Rust"))'

# Look up a tweet by ID or URL
x tweet get 2054681397256962438
x tweet get https://x.com/USER/status/2054681397256962438

# Batch tweet lookup (up to 100)
x tweet get --ids 123,456,789

# Search recent tweets (past 7 days, Basic tier required)
x tweet search "NGINX CVE" --max-results 20 --no-json
x tweet search "from:youyo" --yesterday-jst --all --ndjson

# Fetch a full conversation thread
x tweet thread 2054681397256962438 --no-json

# Home timeline as NDJSON
x timeline home --yesterday-jst --all --ndjson

# Look up a user by @username
x user get @youyo --no-json

# List followers of self
x user followers --no-json

# Fetch tweets from a List
x list tweets <LIST_ID> --all --ndjson

# Search active Spaces
x space search "AI engineering" --no-json

# Tokyo trends (WOEID 1118370)
x trends get 1118370 --no-json

# Start MCP server locally (dev, no auth)
X_API_KEY=... X_API_SECRET=... X_ACCESS_TOKEN=... X_ACCESS_TOKEN_SECRET=... \
x mcp --auth none --host 127.0.0.1 --port 8080

# Show version
x version --no-json
```
