# Hacker News Mentions Scraper - Stories, Comments & Evidence (`maydit/hacker-news-mentions-scraper`) Actor

Find literal brand, product or topic mentions in public Hacker News stories and comments. Export matching excerpts, thread links and saved changes using the public Algolia index. No API key.

- **URL**: https://apify.com/maydit/hacker-news-mentions-scraper.md
- **Developed by:** [Brandt May](https://apify.com/maydit) (community)
- **Categories:** Social media
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.60 / 1,000 mention observations

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

![Mentions you can trace to the text — illustrative workflow](https://api.apify.com/v2/key-value-stores/YGxD9JRpduom8Q5Zd/records/hacker-news-mentions-scraper-3f28ab92b419.png?signature=Dwr51zJI83sg8vLZPd3y)

### Mentions you can trace to the text

Build a repeatable brand, product or topic research watchlist from the public Hacker News Algolia index. Each saved story or comment includes literal match evidence and a discussion link.

[Open this Actor in Console](https://console.apify.com/actors/CcWenJwr0DRG9oUe6/input) · [First run](#try-this-actor) · [Results](#result-preview) · [Cost](#usage-and-pricing) · [Full guide](#full-guide)

### Try this Actor

1. **Choose a small scope.** Use the existing **OpenAI Mentions in Hacker News Comments** example below: ten saved comments at most, drawn from a candidate window of one hundred.
2. **Run and review.** Open the dataset and inspect `matchedTerms` to see which source text qualified. The run's key-value-store `SUMMARY` separates inspected candidates, saved matches and source failures.
3. **Use the results.** Export JSON for matching excerpts and saved comparisons, or CSV for a discussion list. Follow the item or thread link when reviewing context.

**Read this before using the data:** Literal matches do not establish sentiment, purchase intent or the truth of a post. The search index can lag Hacker News, and its bounded result window is not exhaustive history. Missing later results do not prove deletion.

**OpenAI Mentions in Hacker News Comments** — copy this input into the JSON editor in Console.

```json
{
  "queries": [
    "OpenAI"
  ],
  "itemType": "comment",
  "maxResults": 10,
  "maxCandidatesPerQuery": 100
}
```

### Result preview

The dataset view highlights these columns:

- **Mention ID** · `mentionId`
- **Story or comment** · `itemType`
- **Item or thread title** · `title`
- **Text** · `text`
- **Public author handle** · `author`
- **Indexed publication time** · `publishedAt`

Use the original source links and the field definitions below when interpreting the results. CSV works well for flat columns; JSON preserves nested values.

### Usage and pricing

Base result rate on the FREE tier: **$1 per 1,000 mention observations**. The [Pricing tab](https://apify.com/maydit/hacker-news-mentions-scraper/pricing) shows your plan's result rate and any Actor Start charge. Set a small result limit for the first run; the requested limit is not a guarantee of that many results.

***

### Full guide

Find literal brand, product and topic mentions in public Hacker News stories and comments. Get matching excerpts, canonical discussion links and saved comparisons for a repeatable research watchlist.

The Actor uses the documented public [Hacker News Search API by Algolia](https://hn.algolia.com/api). No API key, account, proxy or browser is required. It does not crawl linked articles or infer sentiment, purchase intent or personal profiles.

#### Quick start

Run with `{}` to save up to 20 recent indexed story/comment mentions of `OpenAI`. The Console prefill uses the same phrase.

```json
{
  "queries": ["OpenAI"],
  "itemType": "comment",
  "maxResults": 20,
  "maxCandidatesPerQuery": 100
}
```

Pricing is **$0.001 per saved mention ($1 per 1,000)**. A mention is one unique story or comment ID. Duplicate matches across phrases, rejected fuzzy matches, filtered records, unchanged observations skipped by `onlyChanges`, and error diagnostics are not saved as paid rows. Spending limits may stop a run before the requested window is finished.

#### Literal evidence, not inferred relevance

Every saved item must match the current phrase in actual source text:

- A story may match its title, self-text or linked URL.
- A comment must match its own text. A parent story title alone does not qualify it.

Matching is a case-insensitive **literal substring**, so `AI` also matches those adjacent characters inside longer words. Use a more specific phrase when that is too broad. Algolia typo/prefix matching is disabled where supported, and an independent local check rejects fuzzy results. Each row's `matchedTerms` contains matching fields and short excerpts for all configured phrases found in that item.

The service is an index, not a live verification of the underlying item. Edits, deletion and metrics may lag [Hacker News's official item API](https://github.com/HackerNews/API). A missing item in a later search does not prove deletion.

#### Inputs

| Input | Behavior |
| --- | --- |
| `queries` | Up to 10 literal phrases, at most 100 characters each. Omitted or empty uses `OpenAI`. Phrases are normalized to lowercase, deduplicated and processed in sorted order. |
| `itemType` | `all` (default), `story` or `comment`. |
| `sort` | `recent` (default) uses publication order from the index; `relevance` uses Algolia ranking. Multiple query results are not globally resorted. |
| `storyTag` | `any` (default), `ask_hn` or `show_hn`. A restricted tag requires `itemType: "story"`. |
| `author` | Optional exact public Hacker News handle. No profile or contact enrichment. |
| `startDate`, `endDate` | Optional real `YYYY-MM-DD` dates, inclusive in UTC, filtering the item publication date. |
| `maxCandidatesPerQuery` | Default 100, range 1-1,000. Candidate window before local filtering and deduplication. This is not a promise of that many saved matches. |
| `pageSize` | Default 100, range 1-100 indexed candidates per request. Smaller pages require more requests. |
| `maxResults` | Default 20, maximum 1,000 unique saved rows across all queries. |
| `comparePrevious` | Return matching rows with new, changed or unchanged comparison status. Default false. |
| `onlyChanges` | Implies comparison and skips unchanged observations. Default false. |
| `snapshotName` | History namespace, default `default`; 1-80 letters, digits, underscores or hyphens. |
| `maxRunSeconds` | Default 240; range 30-3,600. Also respects the platform deadline minus 25 seconds. |

#### Output

| Fields | Meaning |
| --- | --- |
| `mentionId`, `itemId`, `itemType` | Stable Hacker News item identity and `story`/`comment` type. |
| `title`, `storyTitle` | A story's title or a comment's thread title. If comment thread title metadata is absent, `title` is the explicit label `Comment <itemId>`. Literal comment evidence still comes from its own text. |
| `text`, `textHash`, `textTruncated` | Normalized self-text/comment text with HTML entities decoded. Output is capped at 50,000 characters; SHA-256 and evidence are computed from the full received text. Ordinary link stories can have empty self-text. |
| `author`, `publishedAt` | Public author handle when present and the item's indexed publication time in UTC. |
| `points`, `commentCount` | Public indexed counts when supplied. Comment scores/counts are usually absent and remain null rather than zero. |
| `storyId`, `parentId` | Thread and immediate parent IDs when published by the index. Stories point to their own thread and have no parent. Missing comment ancestry remains null. |
| `itemUrl`, `threadUrl` | Canonical Hacker News item and thread links. |
| `sourceUrl` | Linked article/page URL metadata only; content is not downloaded. A comment may use its thread's linked URL. Missing or unsafe URL metadata is null. |
| `matchedTerms` | Literal phrase, source field and matching excerpt. |
| `source`, `indexUpdatedAt`, `observedAt` | Index provenance and timestamps. Missing index update time remains null. |
| `comparisonStatus`, `changedFields`, `previous`, `previousObservedAt` | Comparison against prior saved observations. |

#### Bounded search coverage

Algolia exposes at most about **1,000 hits per search**, even when `nbHits` is much larger. Each query's `SUMMARY.sourceCoverage` separates reported hits/pages from inspected candidates, fetched pages and saved results. Requested candidate/result caps are successful bounded checks, not full-history claims. Narrow dates or phrases for deeper targeted research.

Deleted/dead indexed items are excluded when flagged. Unexpectedly empty pages, repeated pages, invalid envelopes and unusable records are reported explicitly. A candidate window with no literal match can legitimately save zero rows.

#### Repeat runs and saved history

Enable `comparePrevious` to return `new`, `changed` or `unchanged`; otherwise status is `not_requested`. `onlyChanges` saves new/changed observations only. Changes include title, full text hash, linked URL, ancestry, author, points and comment count. Thus discussion metric updates can be meaningful changes even when text is unchanged. Index update and observation timestamps do not trigger changes.

The first comparison run warns that matching observations establish history. A row marked `new` reports the comparison result; **`SUMMARY.historyUpdated` confirms whether history was committed**. An unchanged rerun can succeed with zero saved rows.

The named store `maydit-hacker-news-mention-history` is scoped by `snapshotName`, sorted queries, type, story tag, author and date filters. Only confirmed saved rows advance per-item history. Limits, filters, failures and missing search results do not remove prior observations. History is capped at 20,000 identities per scope.

Use **one concurrent writer per history scope**. A final reread detects some concurrent changes, but this is not a transactional lock. Malformed/incompatible history fails clearly; a new `snapshotName` starts fresh.

#### Failure and limit handling

`SUMMARY` in the default key-value store records partial source failures, unusable hits, filters, duplicates, unchanged rows, candidate windows and time/spending stops. When all sources fail and no usable observations are saved, the run fails after writing its summary. Saved rows survive a later-page outage, with status `partial`. Errors are never written as mention rows.

All requests use the public API, a 25-second request deadline, the overall run budget and bounded retries. No login, rate-limit or challenge bypass is attempted. This workflow provides public evidence for research; it does not establish a person's intent or the truth of user-generated claims.

#### Ready-to-run examples

The following exact inputs also appear in `examples.json`.

##### OpenAI Mentions in Hacker News Comments

Find 10 recent indexed comments with a literal OpenAI mention in the comment itself, with evidence and canonical thread links.

Use the [copyable first-run input](#try-this-actor) above.

##### PostgreSQL Stories on Hacker News

Read 10 recent indexed stories whose title, self-text or linked URL literally mentions PostgreSQL. Includes public scores and discussion links.

```json
{
  "queries": [
    "PostgreSQL"
  ],
  "itemType": "story",
  "maxResults": 10,
  "maxCandidatesPerQuery": 100
}
```

##### Show HN AI Stories - Saved Observation Watch

Compare 10 indexed Show HN stories with literal AI text against prior saved observations. AI is a substring phrase, not an inferred category.

```json
{
  "queries": [
    "AI"
  ],
  "itemType": "story",
  "storyTag": "show_hn",
  "maxResults": 10,
  "maxCandidatesPerQuery": 100,
  "comparePrevious": true,
  "snapshotName": "show-hn-ai-demo"
}
```

### Help with your run

Open the **Issues** tab on this Actor with the run URL, the input field that caused trouble and what you expected to receive. Remove API keys and private information before posting.

[Explore more Mayd-It Actors](https://apify.com/maydit)

### Understand your run

Open **Run report** in the run’s Output tab for saved-record counts, reported source issues and limits, and suggested next steps. Use **CSV export** to take the saved results into a spreadsheet. The existing JSON results and detailed machine-readable summary remain available.

The report reflects this run’s recorded coverage. A successful run or a saved result does not establish that every possible source record was collected.

# Actor input Schema

## `queries` (type: `array`):

Up to 10 literal case-insensitive phrases, at most 100 characters each. Empty/omitted uses OpenAI. A story must match its title, text or linked URL; a comment must match its own text. Fuzzy search and parent-only matches are rejected.

## `itemType` (type: `string`):

Search stories, comments, or both. Comment titles provide thread context, while literal match evidence must be in the comment text.

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

Recent uses the newest indexed matches first; relevance uses Algolia search ranking. Results from multiple queries are processed in sorted query order, not globally ranked.

## `storyTag` (type: `string`):

Optional documented story tag. ask_hn and show_hn require itemType story; tags are also checked locally.

## `author` (type: `string`):

Optional exact Hacker News username, 1-50 letters, digits, underscores or hyphens. This uses the public search author tag and a local exact check.

## `startDate` (type: `string`):

Optional real YYYY-MM-DD date, inclusive at 00:00 UTC. Filters the item publication time, not the index update time.

## `endDate` (type: `string`):

Optional real YYYY-MM-DD date, inclusive through the end of that UTC day.

## `maxCandidatesPerQuery` (type: `integer`):

Inspect this many indexed candidates per query before local literal checks and deduplication. Algolia only exposes up to 1,000 hits for a search; this is not complete historical coverage.

## `pageSize` (type: `integer`):

Number of indexed candidates requested per page. Small pages help bounded pagination checks but require more requests.

## `maxResults` (type: `integer`):

Maximum unique matching rows saved across the whole run. Normal requested caps are bounded coverage, not source failures.

## `comparePrevious` (type: `boolean`):

Return matching rows with new, changed or unchanged comparison status. Uses a named persistent store. Only confirmed saved rows advance history. The first run establishes observations, and SUMMARY confirms whether history was committed.

## `onlyChanges` (type: `boolean`):

Implies comparison. Unchanged matching observations are skipped without a dataset charge. A first run saves matching observations; subsequent unchanged runs can correctly save zero. No disappearance/deletion inference.

## `snapshotName` (type: `string`):

Use 1-80 letters, digits, underscores or hyphens. History also includes the sorted sources/queries and content filters. Run only one writer at a time for each scope. Start a new name for an incompatible saved baseline.

## `maxRunSeconds` (type: `integer`):

Requests use this budget and the platform deadline minus 25 seconds. Each request is capped at 25 seconds. Partial coverage and spending stops are explicit in SUMMARY.

## Actor input object example

```json
{
  "queries": [
    "OpenAI"
  ],
  "itemType": "all",
  "sort": "recent",
  "storyTag": "any",
  "maxCandidatesPerQuery": 100,
  "pageSize": 100,
  "maxResults": 20,
  "comparePrevious": false,
  "onlyChanges": false,
  "snapshotName": "default",
  "maxRunSeconds": 240
}
```

# Actor output Schema

## `dataset` (type: `string`):

No description

## `runReport` (type: `string`):

Readable summary of saved records, reported source coverage, limits and next steps. Available when this run saves its SUMMARY.

## `csvExport` (type: `string`):

Download saved dataset records as a CSV file for spreadsheets and other tools.

## `summary` (type: `string`):

No description

# 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 = {
    "queries": [
        "OpenAI"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("maydit/hacker-news-mentions-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 = { "queries": ["OpenAI"] }

# Run the Actor and wait for it to finish
run = client.actor("maydit/hacker-news-mentions-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 '{
  "queries": [
    "OpenAI"
  ]
}' |
apify call maydit/hacker-news-mentions-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,maydit/hacker-news-mentions-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/CcWenJwr0DRG9oUe6/builds/vBsJxpiopP4akzaDc/openapi.json
