# Who's Piling On? X/Bluesky Pile-on & Rumor Tracker (`pressfront/pressfront-trace`) Actor

Who is piling on an X or Bluesky account, and who started a rumor? We read X, Bluesky, Instagram comments, Reddit, forums and the news: every account and post, sides read by a model. Instant check, full trace with a 4-stop answer card, watch names on a schedule, account lookup, evidence pack.

- **URL**: https://apify.com/pressfront/pressfront-trace.md
- **Developed by:** [Pressfront](https://apify.com/pressfront) (community)
- **Categories:** AI, Social media
- **Security:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 0.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

Pay per event

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

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

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
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.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## 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.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## Who's piling on? X and Bluesky pile-on and rumor tracker

**Type an X or Bluesky name: get who is piling on, on which side, who set it off and what they claim. Type a story going around: get who said it first and who spread it.** Not a feed of posts, the picture behind them, with a link to every post.

### Sample result (invented example)

An **instant** check of `@quillandclaw.bsky.social`, a made-up account from our demo trace. Every name, post and number below is fiction.

| | |
|---|---|
| **Headline** | Of 187 accounts: **135 critical** and **17 supportive** of @quillandclaw.bsky.social. 24 stayed neutral, 11 were not read. |
| **Started** | Their own post, Oct 21, 8:12 PM: *"I wrote up what actually happened with the review, dates and all. It's long. Read it before you decide what you think."* 388 replies, 97 quotes. |
| **Set off by** | **@gravelmouth.bsky.social** (40,200 followers) quote-posted it 5 hours later: *"This is the person who lectured all of us about honesty. Read the replies and decide for yourself."* **54 accounts arrived in the 40 minutes after**, 4 before. |
| **First critical post** | @tinfoilteapot.bsky.social, 11 minutes after the post: *"you deleted the thread last time too. we remember."* |
| **What they claim** | "the dates don't line up" (20 accounts), "changed the story three times" (16), "deleted the thread last time" (16), "took money for the review" (15), "blocked people asking questions" (12) |
| **Before?** | 3 of 187 had posted about @quillandclaw before. |
| **Pace** | slowing: 0 accounts arrived in the last hour. |

Try it yourself with the public demo key on the [Pressfront API](https://pressfront.co/developers#try); the same answer comes back here as one dataset row.

### Modes and prices

| Mode | What you get | Speed | Price |
|---|---|---|---|
| ⚡ **instant** | Who is posting about a name, on which side, who set it off, what they claim, the pace | ~1 second if traced today, ~1 minute if not | **$0.49** |
| 🔍 **trace** | A fresh trace of a name, a story or a situation, start to finish, with the four-stop answer card | ~4 to 7 minutes | **$4.99** |
| 📖 **read** | One part of a finished trace (answer card, accounts, posts, timeline, groups, who, sites, saved posts) | ~1 second | **$0.05** |
| 🧭 **understand** | What Pressfront would trace from your words (a name or a story, and what it would look into). Starts nothing | a few seconds | **$0.05** |
| 👀 **watch** | Up to 25 names checked against your last run, spikes flagged, optional webhook. **Schedule it** | ~1 to 4 minutes | **$0.15 per name, per run** |
| 🔎 **account** | Which traces this account showed up in before, how it took part, its profile then | ~1 second | **$0.05** |
| 🗂 **evidence** | The sealed evidence pack of a finished trace: every post captured, hashed and time-stamped | ~1 second once built | **$9.99 once per trace** |

**Never an empty charge.** A search still running, a pack still building, a failed trace or an error costs nothing. The run ends with a row that says what to do (usually: run again in a few minutes).

### Where we look

**X, Bluesky, Instagram comments, Reddit, Hacker News, forums and boards, the news**, plus Know Your Meme, Community Notes and archived copies of posts. You type an X or Bluesky name (or plain words); we find who is talking about it in all of those places. Pressfront reads **thousands of accounts per name** (5,764 in one trace) so your agent, dashboard or report does not have to.

### What can I use it for?

- 🚨 **Pile-on and crisis monitoring**: is it growing or fading, and who set it off? Put the names on **watch**.
- 📰 **Journalism and research**: where a story started, the earliest public post we could find, how it spread.
- 💬 **"Everyone is saying X about me"**: type the rumor in a few words; see who posted it first and who passed it on.
- 🏢 **PR, talent and brand teams**: who is critical, who is supportive, which big accounts are in it.
- ⚖️ **Legal and trust & safety**: the **evidence pack**, a sealed zip with hashes and an RFC 3161 time-stamp.
- 🤖 **AI agents**: one call returns a Markdown brief with numbered sources, ready for an LLM to explain.

### How to use it

1. Pick a **mode**.
2. **instant** or **account**: a **Name** (`@elonmusk`, `x.com/someone`, or `name.bsky.social`). **watch**: the **Names to watch** list. **trace**: fill in **one** of Name, Story going around, or Situation in your own words; the one you change is the one traced. **read** or **evidence**: the **Trace id** from a trace run.
3. Press **Start**. Read the result in the **Output** tab, or download it as JSON, CSV or Excel.

Call it from code, Make, Zapier, n8n or an AI agent with Apify's API like any other Actor.

### Watch names on a schedule

**What it does.** Every run checks each name (the same instant check) and compares it with your **last run**. The last look is kept in **your own** Apify key-value store named `pressfront-watch`, one record per name, so nothing of yours sits on Pressfront.

**Schedule it.** In Apify Console open **Schedules**, **Create new**, pick this Actor, set the input to mode `watch` with your names (and a `webhookUrl` if you want one), and choose **hourly** (`0 * * * *`) or **daily** (`0 9 * * *`). Each run asks for a look no older than an hour, so runs closer than an hour apart get the same look back (status quiet) and are still charged: hourly is the shortest schedule that makes sense.

**The spike rule** (facts only, never a word about why they came):

| Status | When |
|---|---|
| **first look** | the first time you watch this name: the baseline, no alert |
| **spike** | accounts **at least doubled AND grew by at least 25** since the last look, or **critical** accounts did |
| **quiet** | anything else, including the same look as last time, or a one-minute search after a full trace (not the same yardstick: the baseline resets) |
| **searching** | the search was still running when the run had to end (about 4.5 minutes); the next run picks it up. **Not charged** |
| **not found / error** | not a name, or Pressfront could not answer. **Not charged** |

**One row per name:** `handle`, `status`, `alert` (true on a spike), `counts` (accounts, critical, supportive, neutral, notRead), `previous` (the last look), `change` (accounts and critical: from, to, by, x), `why` (one sentence), `headline`, `pace`, `link`, `checkedAt`.

**Webhook.** With `webhookUrl` (https), the run POSTs the spike rows (only spikes) as JSON: `{ event: "pressfront.watch.spike", at, spikes: [...], text, content }`. `text` and `content` are one line per spike, so a **Slack** or **Discord** incoming-webhook URL shows them as is. 10 second timeout, best effort, never fails the run.

**Cost.** $0.15 per name answered, per run. 10 names hourly is about $36 a day; 10 names daily is $1.50 a day.

### The evidence pack

Every **full trace of a name** builds a sealed pack a few minutes after it finishes. Run mode **evidence** with the `traceId`:

- **One capture record per post**: the text as it stood, ids, times, the raw source record and, on Bluesky, the signed repo proof.
- **Screenshots** of the public pages, **Internet Archive copies** where the Archive took one.
- A **SHA-256 manifest** and an **RFC 3161 time-stamp** on it, so anyone can check nothing changed after the fact.
- A one-hour **download link** for the zip, plus `counts` (posts, screenshots, archive copies, verified records) and the hashes.

**$9.99 once per trace.** The run that gets the pack handed over ready is the one charged; reading the same pack again later (for a fresh link) is free, remembered in your own store `pressfront-evidence`. Still building? The run waits up to 4 minutes, then ends cleanly with "run again in a few minutes", nothing charged. Story traces have no pack.

### Account lookup

Mode **account** with a Name: which traces run through this Actor the account showed up in (as the name each trace was about, with dates), how it took part each time (reply, quote, mention, liker, reposter), its profile then (name, followers, bio: renames and changes show as history), and where X says it is based. $0.05. A cheap first look before paying for a trace.

### What comes back

For **instant**, **watch** and **trace** of a name:

| Field | What it is |
|---|---|
| `headline` | e.g. *"Of 187 accounts: 135 critical and 17 supportive of @quillandclaw.bsky.social."* |
| `counts` | accounts by side and tone: critical (strongly critical, mocking, disagreeing when read), supportive, neutral, not read |
| `sides` | the biggest voice on each side, with their words and the link |
| `started` | the post it started from, or the first critical post |
| `trigger` | the account whose post set off the jump, and how many arrived after it |
| `carriers` | the accounts others came through (who spread it) |
| `claims` | what the critical posts claim, summarized |
| `pace` | growing, steady, slowing or quiet now |
| `brief` | the whole situation as plain Markdown facts with numbered sources, for an LLM |

```json
{
  "mode": "instant",
  "found": true,
  "handle": "@quillandclaw.bsky.social",
  "headline": "Of 187 accounts: 135 critical and 17 supportive of @quillandclaw.bsky.social.",
  "counts": { "critical": 135, "supportive": 17, "neutral": 24, "notRead": 11 },
  "trigger": { "handle": "@gravelmouth.bsky.social", "followers": 40200, "type": "quote", "arrivedAfter": 54, "minutes": 40 },
  "pace": { "state": "slowing", "line": "0 accounts arrived in the last hour." },
  "brief": "# Trace: @quillandclaw.bsky.social\n\n## What happened\nOf 187 accounts: ... [1]\n\n## Sources\n[1] https://bsky.app/..."
}
```

#### The answer card (every trace, its own item with `"type": "case"`)

| Field | What it is |
|---|---|
| `headline` | the answer in one sentence |
| `stops` | four stops in order: **Started** (the earliest public post we could find, with its link), **Spread** (how many accounts; for a story, how many repeated it, asked if it was real, or said it was not), **Who** (what the accounts have in common, like a word in their bios), **Before?** (whether the same accounts or the same story came round before) |
| `huh` | one fact worth knowing, like how many accounts posted the exact same sentence |
| `data` | the numbers under each stop, for your own charts |
| `note` | how to read it: a language model read whether each post repeated, questioned or denied the story |

```json
{
  "type": "case",
  "kind": "origin",
  "headline": "The \"Lara went partying after the concert was cancelled\" story started with one post with no photo or link, and that post is now gone.",
  "stops": [
    { "n": 1, "label": "Started", "text": "@gossipdesk · Nov 3, 9:14 PM · no photo, no link · now gone · a Community Note says: misleading", "url": "https://x.com/..." },
    { "n": 2, "label": "Spread", "text": "190 accounts · 120 repeated it, 40 asked if it was real, 3 said it was not" },
    { "n": 3, "label": "Who", "text": "7 in 10 have \"manon\" in their name or bio" },
    { "n": 4, "label": "Before?", "text": "31 of 140 were in earlier waves (Jul 2, Sep 18)" }
  ],
  "huh": { "big": "14", "line": "14 accounts posted the exact same sentence." }
}
```

"Started" is the earliest public post we could find, not proof that nothing came before it. A post that is "now gone" was deleted, edited away or made private: from outside these look the same.

A **trace** run also returns a `traceId`. Use it with **read** to get every account (`part: accounts`), every post (`part: posts`), who moved together (`part: groups`), the timeline (`part: timeline`), where X says accounts are based (`part: who`), posts on other sites (`part: sites`) and the saved posts (`part: evidence`), filtered by side and paged; and with **evidence** for the sealed pack.

### Examples

```json
{ "mode": "instant", "handle": "@elonmusk" }
```

```json
{ "mode": "watch", "names": ["@elonmusk", "name.bsky.social"], "webhookUrl": "https://hooks.slack.com/services/..." }
```

```json
{ "mode": "trace", "handle": "name.bsky.social" }
```

```json
{ "mode": "trace", "claim": "Lara went partying after concert cancelled" }
```

```json
{ "mode": "understand", "text": "people keep saying Lara went partying after the concert was cancelled" }
```

```json
{ "mode": "read", "traceId": "01M6...", "part": "posts", "side": "critical", "sort": "followers", "limit": 200 }
```

```json
{ "mode": "evidence", "traceId": "01M6..." }
```

```json
{ "mode": "account", "handle": "@gravelmouth" }
```

(Lara, Manon, @gossipdesk, @quillandclaw and @gravelmouth are made-up examples.)

### FAQ

**Where does it look?** X and Bluesky first, plus Instagram comments, Reddit, forums, archives and the open web.

**How are sides decided?** A language model reads each post's text, and every answer says so. Groups ("posted within 5 minutes of each other", "same words") are measured timing and wording matches, not proof anyone coordinated.

**Is it private data?** No. Public posts only, and it never says who anyone is in real life. The words you type for a story or a situation are read once and never stored. Your watch baselines live in your own Apify storage.

**Does it tell me if a story is true?** No. It shows who posted it first (the earliest public post we could find), how it spread and who carried it. Whether it is true is for you to judge from the posts.

**Why did instant take a minute?** Nobody traced that name in the last day, so it searched it fresh. The answer's `depth` says `quick` (that minute search) or `full` (a full trace). After that, **instant** answers in about a second for a day.

**What if the search is not done in time?** The run ends with a row saying "still searching, run again", nothing charged. The next run answers from the finished search.

**How fresh is a watch check?** Each run asks for a look no older than an hour, so an hourly or daily schedule gets a fresh one-minute search each time; a run less than an hour after the last one gets the same look back (status quiet).

**What does understand do?** It reads your words and says back what a trace would look for: a name or a story, and two or three "looking into" lines. Use it to check before paying for a trace.

**What can I read from a story trace?** The answer card (`part: case`) and the brief. The account-by-account parts and the evidence pack are for name traces.

**Want it inside Claude, ChatGPT or your own app?** See the [Pressfront API](https://pressfront.co/developers): REST, MCP and OpenAPI, with a public demo key.

Questions or a custom need: support@pressfront.co

# Actor input Schema

## `mode` (type: `string`):

instant = who is posting about a name: about a second if it was traced today, otherwise Pressfront searches it now, about a minute ($0.49). trace = a fresh trace of a name, a story going around, or the situation in your own words, about 4 to 7 minutes ($4.99). read = one part of a finished trace by its id ($0.05). understand = what Pressfront would trace from your words, without starting anything ($0.05). watch = check a list of names against the last run and flag spikes; schedule it ($0.15 per name per run). evidence = the sealed evidence pack of a finished trace ($9.99, once per trace). account = which traces an account showed up in before ($0.05).

## `handle` (type: `string`):

An X @handle, an x.com link, or a Bluesky handle like name.bsky.social. For instant, account and trace. For trace, fill in ONE of Name, Story or Situation: the one you changed is used.

## `names` (type: `array`):

For watch: up to 25 X @handles or Bluesky handles. Every run checks each one and compares it with your last run (the last look is kept in your own key-value store named pressfront-watch). Schedule the run hourly or daily under Schedules. $0.15 per name answered, per run.

## `webhookUrl` (type: `string`):

For watch, optional: an https URL that gets one POST per run with the spike rows (only spikes) as JSON, plus a text line Slack and Discord incoming webhooks show as is. Best effort: a failed post never fails the run, the rows are in the dataset either way.

## `claim` (type: `string`):

For trace: the story people are passing around, in the few words they post it with. Words, not a link. You get the earliest public post we could find, how it spread, who carried it and whether it came round before. It shows what people posted; it never says whether the story is true.

## `text` (type: `string`):

For trace and understand: what is going on, the way you would tell a friend (up to 6,000 characters). Pressfront reads it as a name or a story first. Your words are never stored.

## `traceId` (type: `string`):

From a 'trace' run's output. For read and evidence.

## `part` (type: `string`):

For read: case (the answer in four stops: who said it first, how it spread, who they are, whether it happened before), brief (plain-facts summary), story, accounts, posts, timeline, groups, who, sites or evidence (the saved posts; the sealed pack is the evidence mode). A story trace has case and brief.

## `side` (type: `string`):

For read accounts or posts: strongly_critical, mocking, disagreeing, critical, supportive, neutral or not_read.

## `sort` (type: `string`):

For read accounts (followers, first, touches, newest_account) or posts (oldest, newest, followers).

## `limit` (type: `integer`):

For read accounts or posts, 1 to 200.

## `cursor` (type: `string`):

The 'next' value from the previous read.

## Actor input object example

```json
{
  "mode": "instant",
  "handle": "@elonmusk",
  "names": [
    "@elonmusk"
  ],
  "claim": "Lara went partying after concert cancelled",
  "text": "people keep saying Lara went partying after the concert was cancelled, and Manon fans keep posting it",
  "part": "brief"
}
```

# Actor output Schema

## `results` (type: `string`):

Every row this run pushed: the instant check or trace summary, counts, link and brief.

## `case` (type: `string`):

Who started it, how it spread, who they are, and whether it happened before.

## `watch` (type: `string`):

One row per watched name: spike, quiet, first look or still searching, with the change since the last run.

## `accounts` (type: `string`):

Which traces the account showed up in before, how it took part, and its profile then.

# 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 = {
    "handle": "@elonmusk",
    "names": [
        "@elonmusk"
    ],
    "claim": "Lara went partying after concert cancelled",
    "text": "people keep saying Lara went partying after the concert was cancelled, and Manon fans keep posting it"
};

// Run the Actor and wait for it to finish
const run = await client.actor("pressfront/pressfront-trace").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 = {
    "handle": "@elonmusk",
    "names": ["@elonmusk"],
    "claim": "Lara went partying after concert cancelled",
    "text": "people keep saying Lara went partying after the concert was cancelled, and Manon fans keep posting it",
}

# Run the Actor and wait for it to finish
run = client.actor("pressfront/pressfront-trace").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 '{
  "handle": "@elonmusk",
  "names": [
    "@elonmusk"
  ],
  "claim": "Lara went partying after concert cancelled",
  "text": "people keep saying Lara went partying after the concert was cancelled, and Manon fans keep posting it"
}' |
apify call pressfront/pressfront-trace --silent --output-dataset

```

## MCP server setup

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

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/tRQBVfYLEJLtPCjGr/builds/UdVcROutRRh2RHlPU/openapi.json
