# Foxhole Twitter/X API

> Twitter/X data over HTTPS: user profiles, live timelines, tweet lookup, live search, and a per-API-key
> watch list — new tweets of watched users are stored as they are posted and can be streamed.

Base URL: https://tw-ws.pawx.ai
OpenAPI 3.1: https://tw-ws.pawx.ai/openapi.json
MCP (Streamable HTTP, stateless): https://tw-ws.pawx.ai/mcp

## Authentication

Every endpoint below needs an API key in the `X-API-Key` header (or `?apiKey=` query parameter).
Ask the operator for a key. Errors: 401 missing/invalid key, 400 bad parameters (message explains),
404 not found, 429 rate limited (back off and retry).

## Use it from an MCP client

Add a remote (Streamable HTTP) MCP server with URL `https://tw-ws.pawx.ai/mcp` and header `X-API-Key: <key>`
(`Authorization: Bearer <key>` also works). Example (Claude Code):

    claude mcp add --transport http foxhole-twitter https://tw-ws.pawx.ai/mcp --header "X-API-Key: <key>"

Example (.mcp.json / Cursor / other clients):

    {"mcpServers": {"foxhole-twitter": {"type": "http", "url": "https://tw-ws.pawx.ai/mcp", "headers": {"X-API-Key": "<key>"}}}}

Every endpoint is exposed as a tool with the same name shown below.

## Typical workflows

- Who is this account? `GET /api/v1/twitterUsers/info?screenName=VitalikButerin` (also gives the numeric id).
- What did they post recently? `GET /api/v1/twitterUsers/{userId}/tweets?limit=20` (live; run /info first so the user is known).
- What is being said about X? `GET /api/v1/search?q=...&type=Latest` and follow `cursor` for more.
- Watch accounts continuously: `POST /api/v1/subscriptions` with `{"userIds": [...]}` or `{"screenNames": [...]}`,
  then poll `GET /api/v1/twitterUsers/stored-tweets?userId=<ids>&createdAfter=<last seen>`, or stream (below).
- Ids are strings (they exceed 2^53). Times are ISO-8601 UTC.

## Endpoints

### Users

- `GET /api/v1/twitterUsers/info` — screenName, userId
  Get user profiles by handle and/or id. Profile, counts, verification and KOL influence for up to 200 users (screenName + userId combined). Served from the database; users not yet known are fetched live and cached. 404 if none are found. (MCP tool: `twitter_user_info`)
- `GET /api/v1/twitterUsers/kol-users`
  List tracked KOL accounts. All users flagged as KOLs, ordered by how many other KOLs follow them (desc). (MCP tool: `twitter_kol_users`)
- `GET /api/v1/twitterUsers/{userId}/top-interacted-with` — createdAfter, createdBefore, limit, offset
  Accounts this user replies to / quotes / retweets most. Based on stored tweets in the time window. (MCP tool: `twitter_user_top_interacted_with`)
- `GET /api/v1/twitterUsers/{userId}/top-interacted-by` — createdAfter, createdBefore, limit, offset
  Accounts that reply to / quote / retweet this user most. Based on stored tweets in the time window. (MCP tool: `twitter_user_top_interacted_by`)
- `GET /api/v1/twitterUsers/{userId}/tweet-timestamps` — createdAfter, createdBefore, limit
  Tweet ids + creation times for a user (stored tweets). Lightweight activity listing: only id and createdAt. (MCP tool: `twitter_user_tweet_timestamps`)
- `GET /api/v1/twitterUsers/{userId}/tweet-hourly-distribution` — createdAfter, createdBefore
  When a user tweets: 24-hour (UTC) distribution with percentages. Computed from stored tweets in the time window. (MCP tool: `twitter_user_tweet_hourly_distribution`)

### Tweets

- `GET /api/v1/twitterUsers/{userId}/tweets` — createdAfter, createdBefore, limit
  A user's recent timeline (live) by user id. Fetched live from upstream providers (tweets + replies), newest first. The user must already be known — call twitter_user_info first, otherwise 404. Without createdAfter the result is capped at limit (default 20); with createdAfter it pages back to that time (slower). (MCP tool: `twitter_user_tweets`)
- `GET /api/v1/twitterUsers/{username}/tweets-by-username` — createdAfter, createdBefore, limit
  A user's recent timeline (live) by handle. Same as twitter_user_tweets but addressed by handle. The user must already be known (call twitter_user_info first, otherwise 404). (MCP tool: `twitter_user_tweets_by_username`)
- `GET /api/v1/twitterUsers/stored-tweets` — screenName, userId, createdAfter, createdBefore
  Tweets already stored for up to 200 users. Database only (no live fetch) — fast. Tweets of subscribed users are stored as they are posted, so this is the way to read a watch list incrementally: poll with createdAfter = your last seen time. createdBefore, if given, must be at least 5 minutes in the past. (MCP tool: `twitter_stored_tweets`)
- `GET /api/v1/twitterUsers/tweets` — id (required)
  Look up tweets by id. Full tweet objects (text, author, metrics, media, references) for 1-100 tweet ids. (MCP tool: `twitter_tweet_status`)
- `GET /api/v1/twitterUsers/tweets/{tweetId}/referencing-tweets` — createdAfter, createdBefore, minKolFollowers, limit
  Stored replies, quotes and retweets of a tweet. From stored tweets; use minKolFollowers to keep only influential accounts. (MCP tool: `twitter_tweet_referencing_tweets`)
- `GET /api/v1/twitterUsers/tweets/{tweetId}/referencing-counts` — minKolFollowers
  Counts of stored replies / quotes / retweets of a tweet. Counts only accounts with at least minKolFollowers KOL followers (default 5). (MCP tool: `twitter_tweet_referencing_counts`)
- `GET /api/v1/twitterUsers/{userId}/referencing-tweets` — createdAfter, createdBefore, minKolFollowers, limit
  Stored tweets that reply to, quote or retweet a user. From stored tweets; use minKolFollowers to keep only influential accounts. (MCP tool: `twitter_user_referencing_tweets`)

### Search

- `GET /api/v1/search` — q (required), type, cursor
  Search tweets (live). Live X search. Supports operators (from:user, "exact phrase", $CASHTAG, contract addresses, lang:en, since:2026-11-01, …). ~20 results per page; pass the returned cursor for the next page. Cashtags cannot start with a digit ($4cat finds nothing; search 4cat). (MCP tool: `twitter_search`)

### Subscriptions

- `GET /api/v1/subscriptions`
  List this API key's watch list. Returns clientId (also your WebSocket client id), limit, count and the subscribed users. (MCP tool: `subscriptions_list`)
- `POST /api/v1/subscriptions` — JSON body {screenNames?: string[], userIds?: string[]}
  Watch users (their new tweets get stored and streamed). Idempotent. Each requested user gets its own status: subscribed, already_subscribed, not_found, protected (private accounts cannot be watched), limit_reached or error. New tweets of watched users are stored (read them with twitter_stored_tweets) and pushed on the WebSocket. (MCP tool: `subscriptions_add`)
- `DELETE /api/v1/subscriptions` — JSON body {screenNames?: string[], userIds?: string[]}
  Stop watching users. Each requested user gets a status: unsubscribed, not_subscribed, not_found or error. (MCP tool: `subscriptions_remove`)

## Real-time stream (WebSocket)

Connect to `wss://tw-ws.pawx.ai/ws/<clientId>` (clientId comes from `GET /api/v1/subscriptions`). The connection is authenticated by the clientId and immediately receives events for every user on the key's watch list (subscriptions added later via REST apply to an open connection too). Messages are JSON: `user-update` (profile/status change with the new tweet in data.status), `user-full-tweet` (full tweet objects in data.relatedFullTweets). Send `{"type":"ping"}` periodically; reconnect with backoff on close.
