# Hacker News Monitor & Keyword Alerts (`keepable_injera/hacker-news-monitor`) Actor

Monitor Hacker News stories, Ask/Show HN, jobs and comments via the official Firebase API and Algolia HN Search. Keyword alerts on title, text or comments, type filters, onlyNew mode. Unofficial; not affiliated with Y Combinator.

- **URL**: https://apify.com/keepable_injera/hacker-news-monitor.md
- **Developed by:** [Chee Chiaw Lim](https://apify.com/keepable_injera) (community)
- **Categories:** News, Social media, Automation
- **Stats:** 2 total users, 1 monthly users, 0.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$0.50 / 1,000 item results

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

## Hacker News Monitor & Keyword Alerts

### What it does

Monitor public Hacker News items for keywords. Search mode queries Algolia by date. Latest mode reads the official Firebase lists and items. Each run writes matching items to the dataset. Schedule repeated runs with `onlyNew` to collect alerts as new dataset rows. This Actor does not send notifications.

### Input example

```json
{
  "mode": "search",
  "keywords": ["apify", "web scraping"],
  "matchIn": ["title", "text"],
  "lookbackHours": 168,
  "maxItems": 20,
  "onlyNew": false
}
```

### Input fields

| Field | Default | Meaning |
| --- | --- | --- |
| `mode` | `search` | `search` uses Algolia. `latest` uses Firebase. |
| `keywords` | `[]` | Match any keyword, case-insensitively. Empty means everything. |
| `matchIn` | `["title","text"]` | Select `title`, `text`, or `comments`. Comment text matches `text` or `comments`. |
| `types` | All five types | Select `story`, `comment`, `ask`, `show`, or `job`. |
| `minPoints` | `0` | Minimum points for non-comment items. |
| `lookbackHours` | `24` | Include items within this age window. |
| `maxItems` | `200` | Maximum matching items per run, from 1 to 10000. |
| `onlyNew` | `true` | Skip IDs saved after successful earlier pushes. |
| `includeCommentsForStories` | `false` | Fetch up to 100 descendants per story. A story can match their text when `matchIn` includes `comments`. |
| `stateStoreName` | `hacker-news-monitor-state` | Named key-value store holding the `SEEN` array of string IDs. |

### Output sample

```json
{
  "id": 1,
  "type": "story",
  "title": "Apify web scraping & tools",
  "url": "https://example.org/1",
  "text": "Public APIs",
  "author": "alice",
  "points": 42,
  "numComments": 1,
  "createdAt": "2026-10-20T00:00:00.000Z",
  "hnUrl": "https://news.ycombinator.com/item?id=1",
  "parentId": null,
  "storyId": null,
  "matchedKeywords": ["apify", "web scraping"],
  "matchedIn": ["title"],
  "source": "algolia",
  "checkedAt": "2026-10-20T00:01:00.000Z"
}
```

Text has HTML removed and entities decoded. Missing text fields are empty strings. Missing counts are zero. `parentId` and `storyId` can be null. The default key-value store contains `RUN_SUMMARY`, including timestamps, counts, charging-limit status, and any run error.

### How only new works

The Actor deduplicates IDs within each run. With `onlyNew`, it also skips IDs from earlier successful pushes. Updates to an existing item do not make its ID new. Use a separate state store for each independent monitor.

IDs are saved after a successful dataset push. Failed pushes remain eligible. Dataset writes and state writes are separate operations, so a crash between them can produce duplicates on retry. Avoid overlapping runs that share the same state store. Seen history grows with unique pushed IDs.

### Pricing

The pay-per-event price is $0.0005 per item, or $0.50 per 1,000 items, with no run fee. The charging event is `item`. Skipped records have no item event charge. Local runs do not bill events. Store pricing must be configured separately as described in `PUBLISHING.md`.

### Limits

- Algolia's index can lag behind Hacker News. Search queries use Algolia's search semantics before local keyword filtering. Local matching can reject candidates but cannot recover candidates omitted by Algolia.
- Firebase has no search API. Latest mode scans the current new, ask, show, and job lists. Comments come from those stories, so this is not a complete comment firehose.
- Comment inspection is bounded to 100 descendants per story. Matches beyond that bound can be missed. Comment enrichment uses Firebase even for Algolia stories.
- Keyword matching uses word boundaries around substring phrases. It is case-insensitive and does not perform stemming or semantic matching.
- Requests start at least 100 milliseconds apart per host. API rate limits still apply. HTTP 429 and 5xx responses retry up to three times with backoff and `Retry-After`. Requests time out after 30 seconds.
- Runs stop at `maxItems`, exhausted source pages or lists, or the remaining charging allowance. Algolia also limits accessible search pages.

### Disclaimer

This Actor is unofficial and is not affiliated with or endorsed by Y Combinator, Hacker News, or Algolia. It uses public documented APIs only. It uses no logos and requires no login.

### Local development

Node 20 or newer is required. Tests stub fetch and use local storage.

```sh
npm test
NODE_OPTIONS="--import=./test/offline-preload.js" npm start
```

The second command runs the real Actor with fixture responses and the sample input. Read `storage/datasets/default/` and `storage/key_value_stores/default/RUN_SUMMARY.json`. Normal `npm start` uses the public APIs.

# Actor input Schema

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

Search Algolia or fetch latest Firebase lists.

## `keywords` (type: `array`):

Match any keyword. Empty means everything.

## `matchIn` (type: `array`):

Fields used for local matching.

## `types` (type: `array`):

Item types to emit.

## `minPoints` (type: `integer`):

Minimum points for non-comment items.

## `lookbackHours` (type: `integer`):

Age window in hours.

## `maxItems` (type: `integer`):

Maximum matching records per run.

## `onlyNew` (type: `boolean`):

Skip IDs successfully pushed in earlier runs.

## `includeCommentsForStories` (type: `boolean`):

Fetch up to 100 descendants per story for matching in comments.

## `stateStoreName` (type: `string`):

Named key-value store holding the SEEN array.

## Actor input object example

```json
{
  "mode": "search",
  "keywords": [],
  "matchIn": [
    "title",
    "text"
  ],
  "types": [
    "story",
    "comment",
    "ask",
    "show",
    "job"
  ],
  "minPoints": 0,
  "lookbackHours": 24,
  "maxItems": 200,
  "onlyNew": true,
  "includeCommentsForStories": false,
  "stateStoreName": "hacker-news-monitor-state"
}
```

# Actor output Schema

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

No description

## `runSummary` (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 = {};

// Run the Actor and wait for it to finish
const run = await client.actor("keepable_injera/hacker-news-monitor").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 = {}

# Run the Actor and wait for it to finish
run = client.actor("keepable_injera/hacker-news-monitor").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 '{}' |
apify call keepable_injera/hacker-news-monitor --silent --output-dataset

```

## MCP server setup

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

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/NHtSRleoSOJxPu01g/builds/dD95tQ5v5DycthWxE/openapi.json
