# X (Twitter) Watchlist 🔔 (new posts, all-in price) (`tagadanar/x-account-watchlist`) Actor

Watch a list of X (Twitter) accounts and get their new posts in clean JSON: text, date, likes, reposts, replies, quotes, media, links, hashtags and the author's follower count. Monitor mode returns only what is new since the last run. No login, no API key, proxies included.

- **URL**: https://apify.com/tagadanar/x-account-watchlist.md
- **Developed by:** [Tagada Data](https://apify.com/tagadanar) (community)
- **Categories:** Social media, News, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.14 / 1,000 posts

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.
Actors are written with capital "A".

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## X (Twitter) Account Watchlist

Give it a list of X accounts and it brings back their posts: the text, when it went up, how many likes, reposts, replies and quotes it has, the media and links inside it, and the author's follower count at the moment you read it. Turn on monitor mode, put it on a schedule, and every run after the first returns only what has been posted since the last one. That is the part a social listening subscription charges by the month for.

There is no login, no API key and no X developer account. It reads the same public embed timeline X itself serves to any web page that puts a profile widget on it, so nothing here depends on an account that can be suspended.

This is a watchlist, not a search engine. If you want every post mentioning a keyword across all of X, this is the wrong tool. If you want to know what a specific set of accounts said, it is exactly the right one.

### What it costs

One flat price per post delivered, platform usage and residential proxies included, plus a small fee for each account actually read. Handles that do not exist, are suspended or are protected are never billed. Neither is an account we could not reach. A monitor run that finds nothing new costs the start fee and the per-account check, nothing more.

| | Price | Billed when |
| --- | --- | --- |
| Post | $0.20 per 1,000 | A post is delivered in the results |
| Account checked | $0.003 each | The account's public timeline was read and parsed |
| Actor start | $0.001 per run | Once per run |

At the list price a watchlist of 10 accounts pulling 50 posts each costs about 13 cents. The volume leader in this category charges $0.40 per 1,000 posts.

### What you get

One record per post (`recordType: "post"`):

| Field | Description |
| --- | --- |
| `account`, `handle` | The account as you typed it, and the handle X resolved it to |
| `authorName`, `authorId`, `authorUrl`, `authorProfileImageUrl` | Who wrote it |
| `authorFollowers`, `authorVerified` | Follower count and verification, read at the time of the run |
| `postId`, `url` | The post id as a string (they are 64-bit, so never read them as numbers) and a direct link |
| `postedAt` | When it went up, ISO 8601 UTC |
| `text`, `textLength` | The full text. For a repost this is the original post, not the truncated "RT @" version |
| `lang` | The language X detected |
| `likeCount`, `repostCount`, `replyCount`, `quoteCount` | Engagement at the time of the run |
| `isReply`, `isSelfThread`, `replyToHandle`, `replyToPostId`, `replyToUrl` | Whether it answers an earlier post, and which one. An account replying to itself is how threads are written, so those are flagged as threads |
| `isRepost`, `repostOfHandle`, `repostOfPostId`, `repostOfUrl`, `repostOfPostedAt` | Where a repost came from |
| `isQuote`, `quotedHandle`, `quotedPostId`, `quotedUrl`, `quotedText` | The post being quoted, with its text |
| `hasMedia`, `mediaTypes`, `mediaUrls` | Photos, videos and GIFs attached, with direct image URLs |
| `videoUrl`, `videoDurationSeconds` | The best MP4 X exposes for a video post, and how long it runs |
| `links`, `hashtags`, `mentions`, `symbols` | Links resolved past `t.co`, plus hashtags, @mentions and cashtags |
| `linkPreviewUrl`, `linkPreviewTitle`, `linkPreviewSite` | The link card under the post, when there is one |
| `possiblySensitive` | X's own sensitive-content flag |
| `conversationId` | The thread this post belongs to |
| `isNewSinceLastRun`, `isBaseline` | Monitor mode only: whether this is fresh or part of the first snapshot |
| `scrapedAt` | Run timestamp |

And one summary per account (`recordType: "account"`):

| Field | Description |
| --- | --- |
| `handle`, `authorName`, `authorId`, `authorBio`, `authorLocation` | The profile as X shows it |
| `authorFollowers`, `authorFollowing`, `authorPostsTotal`, `authorLikesTotal`, `authorListedCount` | The full counter set |
| `authorVerified`, `authorVerifiedType`, `authorProtected`, `authorCreatedAt` | Verification, protection and when the account was opened |
| `authorProfileImageUrl`, `authorBannerUrl`, `profileUrl` | Pictures and the profile link |
| `status`, `statusDetail` | `ok`, `unavailable`, `rate-limited` or `error`, and a sentence saying why. Only `ok` is billed |
| `postsInFeed`, `postsFetched`, `postsDelivered` | What the public timeline held, what matched your filters, what was billed |
| `latestPostIdInFeed` | The newest post id X had at read time |
| `lastPostId`, `previousLastPostId` | Monitor mode: where the watch stands now and where it stood before |
| `includeRetweets` | The filter this run used |

Media is reported, not downloaded, so you learn a post carried a 4K video without paying to transfer it.

### Who uses it

Brand and comms teams watching competitors, executives and industry accounts so a launch or an incident is seen the hour it goes up rather than the morning after. Crypto and trading desks following project and founder accounts for announcements. Newsrooms watching official and government accounts. Investor-relations and VC teams following portfolio founders. Researchers building a dated corpus of what a defined set of accounts published, without a personal account in the loop. Anyone who used to pay for a social listening seat and only ever used the watchlist half of it.

### Input

| Field | What it does |
| --- | --- |
| `handles` | The accounts to watch, up to 200 per run. A name (`nasa`), a handle (`@nasa`) or a profile link all work |
| `maxPostsPerAccount` | Posts to return per account, newest first (1–100 — the public timeline holds 100) |
| `sinceDays` | Skip anything older than this many days. 0 means no date limit |
| `onlyNewSinceLastRun` | Monitor mode: return only posts that were not there on the previous run |
| `includeRetweets` | On by default. Off keeps original posts only |
| `slackWebhookUrl` | Optional Slack incoming webhook for a digest of what the run found. Never charged |

The 200-account ceiling is what a default 3600-second run finishes comfortably
(a healthy account takes about 3.5 seconds). The run also watches its own clock:
if X is throttling and accounts start costing retries, it stops starting new
ones while there is still time to charge and deliver everything it collected,
and finishes with a status saying how many accounts were read and that raising
the run timeout — or watching fewer accounts — is the fix. It never gets killed
mid-list.

### Input examples

Watch three accounts, the 20 most recent posts each:

```json
{
  "handles": ["nasa", "BBCBreaking", "@Space_Station"],
  "maxPostsPerAccount": 20
}
```

A daily alert feed. Schedule this and every run after the first returns only what is new, with a Slack digest:

```json
{
  "handles": [
    "https://x.com/nasa",
    "@BBCBreaking"
  ],
  "onlyNewSinceLastRun": true,
  "maxPostsPerAccount": 100,
  "slackWebhookUrl": "https://hooks.slack.com/services/T000/B000/xxxx"
}
```

Original posts only from the last week, no reposts:

```json
{
  "handles": ["nasa"],
  "sinceDays": 7,
  "includeRetweets": false,
  "maxPostsPerAccount": 100
}
```

### How monitor mode works

The first run with "only posts published since the last run" turned on gives you the current picture and remembers the newest post it delivered for each account. Every run after that returns only what came later. Each account keeps its own position in a key-value store named `x-account-watchlist-state`, one record per account under the key `account-<handle>` (lower-cased), so adding or removing an account from the list never disturbs the others.

Post ids are compared as 64-bit integers, which is what makes the mark exact. If a run is cut short by a charge limit, the posts it did not deliver are not marked as seen, so they come back as new next time instead of being lost.

If an account has posted more since the last run than your `maxPostsPerAccount` allows, monitor mode gives you the oldest of those first and the rest on the following run, so nothing is skipped over. In normal (non-monitor) mode the same cap gives you the newest, which is what you want when you are asking for "the latest N".

### Things worth knowing before you run it

The public timeline holds up to 100 posts per account and there is no way to page further back on this surface. For a busy account that is a few hours of history; for most accounts it is weeks. Setting `maxPostsPerAccount` above 100 does not reach further, it just caps at what X serves.

The timeline is not in strict date order. X mixes pinned posts and older high-engagement ones in among the recent ones. Everything this actor returns is sorted newest first by the real posting date, and `sinceDays` filters on that date, so the feed's own ordering never leaks into your results.

You get the "Posts" tab, which is originals, threads, reposts and quote posts. Replies to other people are not on this surface at all, at any price, so there is no switch for them. Threads are complete: an account replying to itself is kept and flagged with `isSelfThread`.

X applies a request quota per exit IP on this surface. The actor takes a fresh residential exit for every account in your list, which is why a long watchlist works, and it stops and tells you rather than hammering if the pool has a bad minute. If some accounts come back `rate-limited`, re-running a few minutes later picks them up.

### FAQ

**Do I need an X API key or a developer account?** No. Nothing here touches the paid X API, and there is no key to supply.

**Does this scrape private or protected accounts?** No. Only accounts whose posts are public. A protected, suspended, deleted or misspelled handle comes back with `status: "unavailable"` and is not charged.

**How far back can it go?** Up to 100 posts per account, which is what the public timeline exposes. There is no deeper history on this surface at any price.

**Can I search X for a keyword instead?** Not with this actor. Search on X needs a logged-in account, which is a path we do not take. This one watches accounts you name.

**Can I get an account's replies to other people?** No. The public timeline this reads is the "Posts" tab, and replies to other accounts are not in it. Threads the account writes to itself are included in full.

**Can I get the replies under a specific post?** No, this reads account timelines. Each post does carry its `replyCount` and, for a thread, a `replyToUrl` pointing at the post above it.

**Is the engagement count live?** It is the count at the moment of the run. Run it again later and you get the updated numbers for the same post.

**What is the difference between this and a Twitter scraper?** Most of them are bulk search scrapers priced per tweet. This one is built for the watchlist job: a fixed set of accounts, a new-since-last-run delta, per-account status reporting, and a price that stays flat because the proxies are on us.

**Why is there a per-account fee as well as a per-post price?** Because a monitor run that finds nothing new still has to read every account's timeline. The per-account fee covers that work, and it is what lets the per-post price stay at $0.20 per 1,000 instead of being padded to cover empty runs.

**What happens if I put a bad handle in the list?** It is reported in the run's status message and in an `unavailable` account record, and it is not billed. The run still returns everything else. This matters more than it sounds: a handle that does not exist gets a perfectly normal-looking page from X rather than an error, so an actor that does not check for it would charge you for typos.

### Related actors

- [Telegram Channel Monitor](https://apify.com/tagadanar/telegram-channel-monitor) for the same watchlist job on public Telegram channels, with keyword alerts.
- [Threads Scraper](https://apify.com/tagadanar/threads-scraper) for Meta's Threads.
- [Brand News Monitor](https://apify.com/tagadanar/brand-news-monitor) for press and news coverage of the same names.
- [LinkedIn Hiring Signals Monitor](https://apify.com/tagadanar/linkedin-hiring-signals-monitor) for what those companies are doing on the hiring side.

***

X scraper, Twitter scraper, X account monitor, Twitter account monitor, tweet scraper, X timeline scraper, Twitter timeline to JSON, social listening, X alerts, new tweet alerts, competitor monitoring on X, brand monitoring Twitter, X data export, Twitter API alternative, no API key Twitter scraper.

# Actor input Schema

## `handles` (type: `array`):

One entry per account, up to 200 per run. A name (<code>nasa</code>), a handle (<code>@nasa</code>) or a profile link (<code>https://x.com/nasa</code>) all work. Protected, suspended and deleted accounts have no public timeline: they come back as a row saying so, and they are never charged. A healthy account takes about 3.5 seconds, so 200 is what a default 3600-second run finishes with room to spare; if X is throttling, the run stops on its own clock and still delivers everything it collected.

## `maxPostsPerAccount` (type: `integer`):

How many posts to return for each account, newest first. The public timeline holds up to 100 posts per account, so anything above that is capped there.

## `sinceDays` (type: `integer`):

Skip anything posted longer ago than this. Leave at 0 for no date limit. Worth setting: the timeline mixes pinned and old high-engagement posts in with the recent ones, so a date limit is the cleanest way to get only what is actually new.

## `onlyNewSinceLastRun` (type: `boolean`):

Turn this on and put the actor on a schedule: the first run gives you the current picture, every run after that returns only posts that were not there before. The position in each account is remembered between runs.

## `includeRetweets` (type: `boolean`):

On by default. Turn it off to drop reposts of other accounts and keep only original posts.

## `slackWebhookUrl` (type: `string`):

Paste a Slack incoming-webhook URL to also get a short digest of the new posts sent to a channel. Never charged.

## Actor input object example

```json
{
  "handles": [
    "nasa",
    "BBCBreaking"
  ],
  "maxPostsPerAccount": 20,
  "sinceDays": 0,
  "onlyNewSinceLastRun": false,
  "includeRetweets": true
}
```

# Actor output Schema

## `posts` (type: `string`):

One item per post, plus one summary item per account, in the default dataset.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "handles": [
        "nasa",
        "BBCBreaking"
    ],
    "maxPostsPerAccount": 20
};

// Run the Actor and wait for it to finish
const run = await client.actor("tagadanar/x-account-watchlist").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "handles": [
        "nasa",
        "BBCBreaking",
    ],
    "maxPostsPerAccount": 20,
}

# Run the Actor and wait for it to finish
run = client.actor("tagadanar/x-account-watchlist").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "handles": [
    "nasa",
    "BBCBreaking"
  ],
  "maxPostsPerAccount": 20
}' |
apify call tagadanar/x-account-watchlist --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,tagadanar/x-account-watchlist"
        }
    }
}

```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/HB2E7uK1531fNKTsX/builds/3n7RQT5tAErmhnmBh/openapi.json
