# ReadX - X/Twitter JSON API

Access Twitter/X data as JSON via micropayments - pay per call with either
[x402](https://x402.org) (at `readx.sh`) or [MPP](https://mpp.dev) (at
`mpp.readx.sh`). Each call costs a small amount of USDC (default **$0.05**);
read-only, no X login required.

Base URLs: **`https://readx.sh`** (x402) | **`https://mpp.readx.sh`** (MPP)

## When to use

- Look up an X user's profile, bio, follower counts, or pinned tweet
- Export a user's followers or following list (paginated, ~200 per page)
- Fetch a user's timeline (tweets, replies, or media)
- Read a single tweet - with its conversation (replies), or on its own
- Read a tweet's edit history (latest plus prior versions)
- Read an X article (long-form post) by its id
- Search tweets or users; resolve a hashtag
- Read X Lists (info, timeline, members) or a broadcast
- Resolve a numeric user id -> username, or an @handle -> user id

## Payment

The same `/api/*` endpoints are served on two hosts, one per payment protocol -
pick whichever your client speaks:

- **`readx.sh` - x402.** A request with no payment returns `402` with a
  `PAYMENT-REQUIRED` header. The challenge offers two rails, **USDC on Base** and
  **USDC on Solana**, and the client picks whichever it can pay.
- **`mpp.readx.sh` - MPP** ([Machine Payments Protocol](https://mpp.dev)). A
  request with no payment returns `402` with a `WWW-Authenticate: Payment ...`
  header; the client retries with an `Authorization` credential and the response
  carries a `Payment-Receipt`. The rail is **USDC on Base** (EIP-3009).

Each host serves its own `/openapi.json` (x402 spec on the apex, MPP spec on the
`mpp.` host). Both cost the same per call and serve identical JSON. An
[agentcash](https://agentcash.dev) client speaks both:

```bash
# x402 (USDC on Base or Solana)
npx agentcash fetch https://readx.sh/api/jack/profile

# MPP (USDC on Base)
npx agentcash fetch https://mpp.readx.sh/api/jack/profile
```

- The client needs native **USDC** in its wallet (Base for either protocol, or
  Solana for x402). On Base, bridged/wrapped USDC (e.g. Stargate) is not accepted
  - both protocols require EIP-3009 native USDC.
- **Bypass cache** on any endpoint with `?no-cache` to force fresh data.

The endpoint table below uses the x402 host; swap the host to
`mpp.readx.sh` to pay the same endpoint via MPP.

## Endpoints

| Method & path | Returns |
|---------------|---------|
| `GET /api/:name` | user timeline (append `/with_replies` or `/media`) |
| `GET /api/:name/profile` | user object + photo rail + pinned tweet |
| `GET /api/:name/followers` | a user's followers (~200/page; paginate with `?cursor=` + `next_cursor`) |
| `GET /api/:name/following` | accounts a user follows (~200/page; paginate with `?cursor=` + `next_cursor`) |
| `GET /api/:name/about` | account info (join date, location, verification) |
| `GET /api/:name/pinned` | the user's pinned tweet |
| `GET /api/status/:id` | a single tweet, without its conversation |
| `GET /api/:name/status/:id` | a tweet + conversation (before/after/replies) |
| `GET /api/:name/status/:id/history` | a tweet's edit history (`latest` + prior versions) |
| `GET /api/search?q=...` | tweet search (or user search for `@`/`f:user` queries) |
| `GET /api/hashtag/:tag` | redirect to search for `#tag` |
| `GET /api/:name/lists/:slug` | a List by owner + slug |
| `GET /api/lists/:id` | a List by id |
| `GET /api/lists/:id/timeline` | tweets in a List |
| `GET /api/lists/:id/members` | members of a List |
| `GET /api/:name/id` | `{ "id": ... }` for an @handle |
| `GET /api/user/:id` | `{ "username": ... }` for a numeric user id |
| `GET /api/broadcasts/:id` | a broadcast |
| `GET /api/articles/:id` | an X article by wrapper tweet id (public) |
| `GET /api/article-entities/:article_id?auth_token=...` | an X article by entity rest_id (owner-only) |

`:name` is an X handle without `@`. Timeline pagination: pass the response's
`pagination.bottom` (or `replies.bottom`) cursor back as `?cursor=...`.
Followers/following pagination: pass `next_cursor` from the response as
`?cursor=...` until `has_more` is false.

## Response format

Most endpoints wrap data as `{ "code": 0, "data": ... }` on success and
`{ "code": -1, "error": ..., "error_type": ... }` on error (the List endpoints
return the bare object; GetXAPI routes - `/api/:name/followers`,
`/api/:name/following`, `/api/articles/:id`, and `/api/article-entities/:article_id`
- return the upstream payload verbatim). Examples:

```bash
# profile
curl -s https://readx.sh/api/jack/profile | jq '.data.user | {username, followers, verifiedType}'

# followers (first page) + next cursor
curl -s https://readx.sh/api/jack/followers | jq '{count: .user_count, has_more, next: .next_cursor, users: [.followers[] | {userName, followers}]}'

# following (first page)
curl -s https://readx.sh/api/jack/following | jq '{count: .user_count, has_more, users: [.following[] | .userName]}'

# timeline (first page) + next cursor
curl -s https://readx.sh/api/jack | jq '{next: .data.pagination.bottom, tweets: [.data.timeline[] | {id, text}]}'

# a tweet + its conversation
curl -s https://readx.sh/api/jack/status/20 | jq '.data.tweet | {text, time, stats}'

# just the tweet (no conversation)
curl -s https://readx.sh/api/status/20 | jq '.data | {text, time, stats}'

# a tweet's edit history
curl -s https://readx.sh/api/jack/status/20/history | jq '{latest: .data.latest.text, versions: (.data.history | length)}'

# search
curl -s "https://readx.sh/api/search?q=typescript" | jq '.data.timeline | length'
```

A `Tweet` includes `id`, `text`, `time` (unix), `user`, `stats`
(replies/retweets/likes/quotes/views), and `media` (photos/videos/gifs);
retweets/quotes/polls/cards are nested when present. A `User` includes
`username`, `fullname`, `bio`, `followers`, `following`, `verifiedType`,
`joinDate`, etc.

## Notes

- Read-only mirror of X's public data; no posting/auth.
- Stable entities (profiles, lists, broadcasts) are cached; timelines, search,
  and conversations are always fresh.
- Errors surface as the normal envelope, e.g. `404 not_found` for a missing
  user/tweet, `429 rate_limited`, `402` when payment is required.
