---
name: twitterapi-io
description: Read public X (Twitter) data through the twitterapi.io REST API with a plain API key, no X developer account or OAuth. Use when a task needs a user's profile, an account's recent posts, a keyword or from:user search, mentions of an account, a followers list, or specific posts by ID, and when the agent must keep the response small and know what each call costs.
license: MIT
compatibility: Needs network access to https://api.twitterapi.io and the TWITTERAPI_IO_API_KEY environment variable. Examples use curl and Python 3 with requests.
metadata:
  author: twitterapi.io
  version: "1.0"
  homepage: https://twitterapi.io/skills/twitterapi-io/SKILL.md
  docs: https://docs.twitterapi.io
---

# twitterapi.io: X (Twitter) data for agents

## When to use

- The user asks what an X account posted, who someone is on X, who follows an account,
  what people are saying about a topic, or wants specific posts by ID.
- You need the data as JSON in one HTTP call, billed per item returned, with no OAuth flow.

Do not use this skill for the official X API v2 (`api.x.com/2/...`): the paths, auth and
response fields below are twitterapi.io's and are different. Do not use it to post, like,
follow or DM unless the user explicitly asks for a write and accepts the caveats at the end.

## Setup

1. Get an API key at https://twitterapi.io/dashboard (sign-up gives up to $1 of free credits to new
   accounts that pass the automatic bot check, one per person or network, expiring after 31 days).
2. Put it in the environment: `export TWITTERAPI_IO_API_KEY=...`. Never print or commit it.
3. Every read below is a GET to the base URL `https://api.twitterapi.io` plus one of the paths below,
   with the header `X-API-Key: $TWITTERAPI_IO_API_KEY` (the write actions at the end are POSTs).

Rate limits: 0.2 requests per second before the account's first payment, 20 per second once
paid (50 on the Scale plan, 500 on Business). Space calls accordingly; retry on HTTP 429 after
a short pause.

## Cost (check before looping)

- $1 = 100,000 credits. Pay only for the data returned.
- Tweet endpoints (search, timelines, mentions): 15 credits per tweet returned,
  $0.15 per 1,000 tweets. Minimum 15 credits ($0.00015) per request, so an empty search costs
  $0.00015 and a full 20-tweet page costs $0.003.
- Tweets by ID (`/twitter/tweets`): 15 credits per ID requested, up to 50 IDs per call; IDs that
  come back missing or deleted are still billed.
- Profile (`/twitter/user/info`): 18 credits per user, $0.18 per 1,000 profiles.
- Followers / followings: 1 to 3 credits per user returned, in per-page tiers (1 each on a full
  200-row page); a 200-row page is 200 credits ($0.002).
- Tell the user the estimated cost before fetching more than 10 pages of anything.

## The six reads

All return JSON. Tweet objects look the same everywhere. Error text is always in `msg`.

| Need | Path and required params | Where the items are |
| --- | --- | --- |
| Profile | `/twitter/user/info?userName=<handle>` | `data` (one object) |
| Recent posts | `/twitter/user/last_tweets?userName=<handle>` (`includeReplies=true` to add replies) | `data.tweets[]`, 20 per page, newest first |
| Search | `/twitter/tweet/advanced_search?query=<q>&queryType=Latest` (`Top` for ranked) | `tweets[]`, up to 20 per page |
| Mentions | `/twitter/user/mentions?userName=<handle>` | `tweets[]` |
| Followers | `/twitter/user/followers?userName=<handle>` (`/followings` for who they follow) | `followers[]`, about 200 per page |
| Posts by ID | `/twitter/tweets?tweet_ids=<id1,id2,...>` | `tweets[]` |

Search `query` uses X search operators: `from:handle`, `to:handle`, `"exact phrase"`,
`lang:en`, `-filter:replies`, `min_faves:10`, `since_time:<unix>`, `until_time:<unix>`.
Combine with `OR` and parentheses.

### Tweet fields (camelCase)

`id`, `url`, `text`, `createdAt` (format `Thu Oct 16 12:34:56 +0000 2026`, not ISO 8601),
`lang`, `likeCount`, `retweetCount`, `replyCount`, `quoteCount`, `viewCount`, `bookmarkCount`,
`conversationId`, `isReply`, `inReplyToId`, `inReplyToUsername`, `entities` (hashtags, urls,
user_mentions), `extendedEntities.media[]` (type photo, video or animated_gif), `quoted_tweet`,
`retweeted_tweet`, and `author` with `id`, `userName`, `name`, `followers`, `following`,
`isBlueVerified`, `isVerified`, `createdAt`. There is no `public_metrics` object.

### Profile fields (`/twitter/user/info` -> `data`)

`id`, `name`, `userName`, `description`, `location`, `url`, `followers`, `following`,
`statusesCount`, `mediaCount`, `favouritesCount`, `createdAt`, `isBlueVerified`, `isVerified`,
`canDm`, `isAutomated`, `protected`.

### Follower rows (`/twitter/user/followers` -> `followers[]`, snake_case)

`id`, `screen_name`, `name`, `description`, `location`, `created_at`, `followers_count`,
`following_count`, `statuses_count`, `favourites_count`, `media_tweets_count`, `protected`,
`verified`, `can_dm`, `profile_image_url_https`, `url`.

## Pagination

Every list response carries `has_next_page` (boolean) and `next_cursor` (string). To get the
next page, repeat the same request with `cursor=<next_cursor>`. Stop when `has_next_page` is
false or the page is empty. Each page is billed separately.

## Errors

- HTTP 200 with `"status": "error"` and a `msg` string is the normal error shape. An unknown
  handle on `/twitter/user/info` returns 200, `status: "error"`, `msg: "user not found"`,
  `data: null` (never 404). Check `status` before reading `data`.
- HTTP 401: missing or wrong `X-API-Key`. HTTP 402 or an `msg` about credits: the account is
  out of credits. HTTP 429: over the rate limit; wait and retry.
- A protected account returns an empty `tweets` list rather than an error.

## Keep the context small

Raw responses are large because each tweet repeats its full `author` object, `entities` and
media. Before reasoning over results, keep only what the task needs. The default subset is
`id`, `createdAt`, `text`, `url`, `author.userName`, `likeCount`, `retweetCount`,
`replyCount`, `viewCount`; for profiles `id`, `userName`, `name`, `description`, `followers`,
`following`, `statusesCount`, `createdAt`, `isBlueVerified`; for follower rows `id`,
`screen_name`, `name`, `followers_count`, `created_at`. Fetch one page, trim, then decide
whether another page is needed. Never paste a raw page into the conversation.

## Examples

Shell (one search page, trimmed with jq):

```bash
curl -s -G "https://api.twitterapi.io/twitter/tweet/advanced_search" \
  -H "X-API-Key: $TWITTERAPI_IO_API_KEY" \
  --data-urlencode 'query=from:NASA -filter:replies' --data-urlencode 'queryType=Latest' \
  | jq -c '.tweets[] | {id, createdAt, text, url, user: .author.userName, likeCount, retweetCount, replyCount, viewCount}'
```

Python (profile, two timeline pages, pagination, error check, trimming):

```python
import os, requests

BASE = "https://api.twitterapi.io"
H = {"X-API-Key": os.environ["TWITTERAPI_IO_API_KEY"]}
KEEP = ("id", "createdAt", "text", "url", "likeCount", "retweetCount", "replyCount", "viewCount")

def trim(t):
    d = {k: t.get(k) for k in KEEP}
    d["user"] = (t.get("author") or {}).get("userName")
    return d

r = requests.get(f"{BASE}/twitter/user/info", headers=H, params={"userName": "NASA"}, timeout=30)
r.raise_for_status()
body = r.json()
if body.get("status") == "error":
    raise SystemExit(body.get("msg"))            # e.g. "user not found"
profile = body["data"]
print(profile["userName"], profile["followers"], profile["statusesCount"])

posts, cursor = [], None
for _ in range(2):                                # 2 pages = up to 40 posts = $0.006
    params = {"userName": "NASA"}
    if cursor:
        params["cursor"] = cursor
    r = requests.get(f"{BASE}/twitter/user/last_tweets", headers=H, params=params, timeout=30)
    r.raise_for_status()
    body = r.json()
    if body.get("status") == "error":
        raise SystemExit(body.get("msg"))
    page = (body.get("data") or {}).get("tweets") or []
    posts.extend(trim(t) for t in page)
    if not body.get("has_next_page") or not body.get("next_cursor") or not page:
        break
    cursor = body["next_cursor"]
print(len(posts), "posts;", "credits:", 15 * len(posts))
```

## Writes (only on explicit request)

twitterapi.io also offers write actions, which run through an authenticated session: one
`/twitter/user_login_v2` login ($0.10, with a proxy the customer supplies) returns
`login_cookies`; each post, delete, like, retweet, follow, unfollow or DM is then $0.0006
(`/twitter/create_tweet_v2`, `/twitter/follow_user_v2`, and so on). Every write acts as that
X account, which X can rate-limit, lock or suspend; keep a human pace, expect the session to
need a re-login, and note that calls are billed per HTTP 200 response. Reads need no X account.

## Reference

- API reference and OpenAPI: https://docs.twitterapi.io (machine-readable index:
  https://docs.twitterapi.io/llms.txt)
- Prices and plans: https://twitterapi.io/pricing
- Dashboard, key and usage: https://twitterapi.io/dashboard
- MCP server with the same reads as tools: https://twitterapi.io/twitter-mcp-server
