# Stocktwits Scraper - Messages, Sentiment & New Posts Monitor (`neverempty/stocktwits-scraper`) Actor

For traders, quant researchers and sentiment analysts: Stocktwits messages for tickers, users and trending, with text, author, Bullish/Bearish label, likes and time. On 2026-09-21, 14 of 30 $AAPL posts had a label. Monitoring mode returns only new posts. Unofficial. Public data only.

- **URL**: https://apify.com/neverempty/stocktwits-scraper.md
- **Developed by:** [NeverEmpty](https://apify.com/neverempty) (community)
- **Categories:** Social media, Business, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.46 / 1,000 message returneds

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?

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

## Stocktwits Scraper - Messages, Sentiment & New Posts Monitor

For traders, quant researchers and sentiment analysts: Stocktwits messages for any list of tickers ($AAPL, BTC.X), users or the trending stream, with the message text, the author's username and display name, the Bullish or Bearish label, likes, replies, reshares, the time posted and the symbols mentioned. On 2026-09-21, 14 of the latest 30 $AAPL messages and 18 of the latest 30 BTC.X messages carried a Bullish or Bearish label. Monitoring mode returns only the messages posted since your last run, so a schedule pays for each new message once.

Export as JSON, CSV or Excel.

Unofficial. Public data only. This Actor is not affiliated with, endorsed by or sponsored by Stocktwits. It collects what people posted; it does not give investment advice, and nothing in its output is a recommendation to buy or sell anything.

### What you get

- **Many streams in one run** - list as many symbols (`symbols`) and users (`usernames`) as you like (up to 100 per run), and add the trending stream with `includeTrending`.
- **Sentiment label per message** - `sentiment` is `Bullish`, `Bearish` or `null`. Authors set the label themselves; many messages have none. The `sentiment` input returns only labeled, only Bullish, only Bearish or only unlabeled messages, and messages filtered out are not charged.
- **Only new posts** - with `monitoringMode` on, the first run of a stream remembers its newest message, and every later run returns only messages newer than that.
- **A per-stream count** - the log line of each stream and the `SENTIMENT_SUMMARY` record of the run's key-value store give the number of Bullish, Bearish and unlabeled messages returned for each stream.
- **No wrong rows** - a message is returned for a symbol stream only if it mentions that symbol, and for a user stream only if that user posted it. The `streamSymbol` and `streamTitle` columns show what Stocktwits answered with, so a mistyped ticker is visible (BTC is an exchange-traded fund; Bitcoin is BTC.X).
- **Free reason rows** - unknown symbols or users, blocks, rate limits, invalid input, duplicates and budget stops come back as rows that say why and are not charged.

### What it does not do

- **Only the latest 30 messages of each stream.** Stocktwits returns 30 messages per stream on the page this Actor reads. Older messages need paging URLs with a query string, which Stocktwits' robots.txt (`Disallow: /*?`) does not allow, so this Actor does not read them. For history, run it on a schedule with monitoring on.
- How fast 30 messages go by depends on the stream. Measured on 2026-09-21 during US market hours: the latest 30 $AAPL messages covered 93 minutes, BTC.X 22 minutes, and the trending stream about 1 minute. In monitoring mode, if all 30 messages of a stream are new since the last check, a free `possible-gap` row says that messages posted in between may be missing; check that stream more often.
- Blocks, browser checks and rate limits are not worked around (no proxy rotation, no browser emulation). A stream that gets one is returned as a free row, and the rest of the list is not read in that run.

### Input

| Field | What it does |
|---|---|
| `symbols` | Tickers: `AAPL`, `$TSLA`, `BRK.B`, `BTC.X` or a `stocktwits.com/symbol/...` link. Crypto ends in `.X`. |
| `usernames` | Users: `howardlindzon`, `@howardlindzon` or a `stocktwits.com/<username>` link. |
| `includeTrending` | Also read the trending message stream. |
| `maxMessagesPerStream` | 1 to 30 (default 30). The newest messages that pass the sentiment filter. Rows come newest first with monitoring off and oldest first with monitoring on. In monitoring mode it limits only the first check. |
| `sentiment` | `any` (default), `bullish`, `bearish`, `labeled` or `unlabeled`. |
| `monitoringMode` | Return only messages posted since the last run of each stream. |
| `resetMonitoringState` | Forget what was remembered for every stream (turn it off again after one run). |

If you leave `symbols`, `usernames` and `includeTrending` all out with monitoring off, the example symbols AAPL, TSLA and BTC.X are read. With monitoring on, at least one stream is required, so that a schedule never pays to watch the examples.

```json
{
    "symbols": ["AAPL", "TSLA", "NVDA", "BTC.X"],
    "usernames": ["howardlindzon"],
    "sentiment": "labeled",
    "monitoringMode": true
}
```

### Output

One row per message:

```json
{
    "source": "stocktwits",
    "status": "ok",
    "scrapedAt": "2026-09-21T16:21:15.000Z",
    "stream": "symbol",
    "input": "aapl",
    "streamSymbol": "AAPL",
    "streamUsername": null,
    "streamTitle": "Apple Inc",
    "messageId": 664717167,
    "url": "https://stocktwits.com/urduja13/message/664717167",
    "body": "$AAPL puts puts puts..",
    "createdAt": "2026-09-21T15:24:40Z",
    "sentiment": "Bearish",
    "likeCount": 1,
    "replyCount": 0,
    "reshareCount": 0,
    "isReply": false,
    "inReplyToMessageId": null,
    "conversationRootId": null,
    "isReshare": false,
    "resharedMessageId": null,
    "symbols": [
        "AAPL"
    ],
    "authorUsername": "urduja13",
    "authorName": "ry tot",
    "authorId": 793030,
    "authorFollowers": 21,
    "authorOfficial": false,
    "authorProfileUrl": "https://stocktwits.com/urduja13",
    "chartUrl": null,
    "isDiscussion": false,
    "sourceApp": "StockTwits For Android",
    "isFirstCheck": null,
    "isNew": null
}
```

- `likeCount`, `replyCount` and `reshareCount` are the counts at the time of the run; a message Stocktwits lists without any is 0. On 2026-09-21 five rows were compared with their stocktwits.com pages: text, label, likes and time matched on all five.
- `isReply`, `inReplyToMessageId` and `conversationRootId` place replies in their thread; `isReshare` and `resharedMessageId` mark reshares.
- `isFirstCheck` and `isNew` are filled in monitoring mode.
- Rows with another `status` (`not-found`, `blocked`, `rate-limited`, `invalid-input`, `duplicate`, `no-messages`, `no-matching-messages`, `no-new-messages`, `possible-gap`, `budget-reached`, `not-checked`) are free and carry a `note` that says why.

### Pricing

- **$2.00 per 1,000 message rows** (`message-returned`), on the Free plan; paid Apify plans get the lower tier prices shown on the pricing tab. Reason rows are free.
- **Monitoring mode only: $0.30 per 1,000 stream checks** (`stream-checked`), charged for every stream read in a monitoring run, whether or not it has new messages. Example: 20 symbols checked every 30 minutes = 28,800 checks a month = $8.64, plus $2.00 per 1,000 new messages returned.
- No start fee. The Actor stops before the run's maximum total charge: in monitoring mode it reads a stream only if the budget still has room for its check and one message row, and messages that did not fit are not remembered, so a later run returns them while they are still among the stream's latest 30.

### Monitoring notes

- What is remembered is the newest message id per stream, stored in a key-value store named `stocktwits-monitoring` in your account, together with the ids of newer messages that were already returned for another stream of the same run (a message that mentions two of your symbols is returned once).
- Do not put the same stream in two schedules that can run at the same time. The Actor re-reads the remembered position right before writing it and never moves it backwards, but Apify's key-value store has no atomic update, so two runs that read the same new messages at the same moment can both return them.

### FAQ

**Why only 30 messages per stream?** Stocktwits' robots.txt disallows URLs with a query string, which is how older pages are requested. This Actor stays within it and reads the first page only.

**Why did BTC return an ETF?** On Stocktwits, BTC is the Grayscale Bitcoin Mini Trust ETF. Use BTC.X for Bitcoin, ETH.X for Ether, and so on.

**Why is the sentiment empty on many rows?** The label is set by the author when posting and is optional.

**Is this investment advice?** No. The rows are what users posted on Stocktwits, returned as data.

# Actor input Schema

## `symbols` (type: `array`):

Stocktwits symbols whose message stream to read: AAPL, $TSLA, BRK.B, or a stocktwits.com/symbol/... link. Crypto symbols end in .X: BTC.X is Bitcoin, while BTC is a different, exchange-traded symbol (every row shows the symbol and title Stocktwits answered with). One per line; commas and spaces also separate entries. A repeated symbol is read and charged once and the repeat gets a free 'duplicate' row. If you leave symbols, usernames and trending all out with monitoring off, the example symbols AAPL, TSLA and BTC.X are read; with monitoring on at least one is required.

## `usernames` (type: `array`):

Stocktwits users whose own messages to read: howardlindzon, @howardlindzon or a stocktwits.com/<username> link. Not case-sensitive.

## `includeTrending` (type: `boolean`):

On = also read Stocktwits' trending message stream (messages across all symbols).

## `maxMessagesPerStream` (type: `integer`):

Each symbol, user or trending stream returns at most its latest 30 messages: this Actor reads only the first page of a stream, because Stocktwits' robots.txt disallows the paging URLs. With monitoring off, the newest messages that pass the sentiment filter are returned per stream, up to this number. In monitoring mode it limits only the first check of a stream; later checks return every new message found. A message that appears in two streams of the same run (for example $AAPL and $TSLA) is returned and charged once.

## `sentiment` (type: `string`):

Return only messages with this sentiment label. Authors set the label themselves (Bullish or Bearish) and many messages have none. Messages that do not match are not returned and not charged.

## `monitoringMode` (type: `boolean`):

Off = every run returns the latest messages of each stream, charged per row. On = the first run of a stream returns up to maxMessagesPerStream messages and remembers the newest message id; later runs return only messages newer than the remembered one. **In monitoring mode every stream read costs $0.30 per 1,000 stream checks, whether or not it has new messages**, plus the row price for the messages returned. Example: 20 symbols checked every 30 minutes = 28,800 checks a month = $8.64. If all 30 messages of a stream are new since the last check, older new messages may have been missed and a free 'possible-gap' row says so: check busy streams more often. Remembered per stream; do not put the same stream in two schedules that can run at the same time.

## `resetMonitoringState` (type: `boolean`):

Clears everything remembered for every stream, so the next monitoring run treats each stream as a first check again. This affects all your monitoring runs, because the memory is stored per stream rather than per list. Turn it off again after one run: left on in a schedule, every run is a first check.

## Actor input object example

```json
{
  "symbols": [
    "AAPL",
    "TSLA",
    "BTC.X"
  ],
  "includeTrending": false,
  "maxMessagesPerStream": 30,
  "sentiment": "any",
  "monitoringMode": false,
  "resetMonitoringState": false
}
```

# Actor output Schema

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

One row per Stocktwits message: the message text, the author's username, display name and follower count, the Bullish or Bearish label the author set (or none), likes, replies and reshares at the time of the run, the time it was posted, the symbols it mentions, the message id and link, and which symbol, user or trending stream it came from. In monitoring mode only messages posted since the last check are returned. Unknown symbols or users, blocks, rate limits and invalid input come back as free rows that say why.

# 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 = {
    "symbols": [
        "AAPL",
        "TSLA",
        "BTC.X"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("neverempty/stocktwits-scraper").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 = { "symbols": [
        "AAPL",
        "TSLA",
        "BTC.X",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("neverempty/stocktwits-scraper").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 '{
  "symbols": [
    "AAPL",
    "TSLA",
    "BTC.X"
  ]
}' |
apify call neverempty/stocktwits-scraper --silent --output-dataset

```

## MCP server setup

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

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/l6ZcFglSFwOdTfPZc/builds/y9rjbaPZuHHfj5wId/openapi.json
