# Hacker News Search & Comments API (`atesen-software/hacker-news-search-comments`) Actor

Search Hacker News stories and comments, or retrieve the front page with bounded discussion expansion. Public APIs only.

- **URL**: https://apify.com/atesen-software/hacker-news-search-comments.md
- **Developed by:** [Atesen Software](https://apify.com/atesen-software) (community)
- **Categories:** News, Developer tools
- **Security:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$1.50 / 1,000 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 Search & Comments API

Search Hacker News stories or comments by text, date and story score, or retrieve the current front page. Optional bounded comment expansion keeps parent and story context in flat rows. Uses the public Algolia HN Search and official Firebase HN APIs. No login, API key, browser, proxy or paid upstream API is needed.

Useful for developer trend monitoring, research datasets, mention tracking and sampling discussions. This is a point-in-time fetcher, not an exhaustive archive or continuous monitor.

### Quick start

The default input returns at most **3 stories**, without comments. For search:

```json
{
  "mode": "query",
  "query": "sqlite",
  "itemType": "story",
  "sort": "relevance",
  "minScore": 20,
  "maxResults": 20,
  "maxPages": 3,
  "maxRequests": 30
}
```

For the current front page with a bounded discussion sample:

```json
{
  "mode": "frontpage",
  "maxResults": 15,
  "includeComments": true,
  "maxCommentsPerStory": 2,
  "maxCommentDepth": 1,
  "maxRequests": 30
}
```

For comment search, set `itemType` to `comment`, leave `minScore` at zero and `includeComments` false. Algolia returns available parent ID, story ID and story title directly; the actor does not fetch linked external articles or resolve missing search context.

### Filters and bounds

| Input | Default | Behavior |
| --- | --- | --- |
| `mode` | `query` | `query` searches Algolia; `frontpage` loads Firebase topstories in ranked order. |
| `query` | empty | Up to 500 characters. Empty search text lists records by the chosen order. Frontpage requires empty text. |
| `itemType` | `story` | `story` or `comment`; frontpage requires story. Jobs, polls, dead and deleted items are excluded. |
| `sort` | `date` | `date` or `relevance` for query mode. Frontpage uses upstream ranking. |
| `fromDate`, `toDate` | unset | Inclusive UTC ISO timestamps, e.g. `2026-01-01T00:00:00Z`. Applied to data rows, including expanded comments. |
| `minScore` | 0 | Story score threshold, 0–100000. Comment scores are not public; comment search requires zero. |
| `maxResults` | 3 | Hard combined story/comment row limit, 1–1000. Status row is additional. |
| `maxPages` | 5 | Query page ceiling, 1–20. Algolia can impose a smaller result window. |
| `pageSize` | 50 | Query records per page, 1–100. |
| `maxRequests` | 200 | Shared hard HTTP attempt ceiling, 1–500, including retries and comment/story lookups. |
| `includeComments` | false | Expand matching stories with Firebase replies. |
| `maxCommentsPerStory` | 5 | Maximum comment IDs visited per story, 0–100, including missing/deleted/filtered entries. This can produce fewer rows. |
| `maxCommentDepth` | 2 | Breadth-first expansion, 1–5. Top-level replies have depth 1. |
| `requestDelayMs` | 200 | Sequential request spacing, 100–5000 ms. |
| `timeoutSecs` | 15 | Per-attempt timeout including body read, 1–60 s. |

There are at most two retries (three attempts total) for network errors, HTTP 429 and 5xx. Access denial is not retried. Response bodies are capped at 2 MB. Every attempt counts toward `maxRequests`, including retries. Frontpage scans at most the 500 upstream top IDs; filtering can exhaust the request budget before enough matches are found.

Comment expansion happens immediately after each emitted story. Comments share the global result limit, so requesting three results with expansion can produce one story and two comments. Set the result and request limits accordingly. Nested replies preserve `parentId`, `storyId`, `storyTitle`, `storyUrl` and `depth`; direct comment search has `depth=null` because no tree is traversed.

### Output

The default dataset contains flat records:

- `recordType`: `story`, `comment`, or final `status`.
- `id`, `title`, `textHtml`, `author`, `createdAt` (UTC), `timestamp` (Unix seconds).
- `score` (stories only), `commentCount` (when provided), `url` (external story link, never fetched), `hnUrl`.
- `parentId`, `storyId`, `storyTitle`, `storyUrl`, `depth`.
- `source` (`algolia` or `firebase`), `apiUrl`, `fetchedAt`.

Missing fields are `null`. Source HTML is stored unchanged as inert text; downstream applications must sanitize before rendering it. Link fields accept only HTTP(S). The actor never executes returned HTML or requests article URLs.

The final status row and `SUMMARY` key-value record include `status`, `message`, `partial`, `resultCount` and `requests`. Status values include `completed`, `result_limit`, `page_limit`, `charge_limit`, `request_limit`, `invalid_input`, `access_denied`, `rate_limited`, `upstream_error`, `network_error`, `invalid_response`, and `response_too_large`.

An empty successful result set has `status=completed` and `resultCount=0`. Access and rate failures have separate explicit statuses, and previously collected records remain available. A technically successful Actor process can therefore contain an upstream failure summary: integrations must inspect `SUMMARY.status` or the final status row. `partial=true` means a limit/error stopped collection; `completed` means this requested bounded operation finished, not that all HN history was retrieved.

### How much does the Hacker News actor cost?

Pay per event: **$0.0015 per result** ($1.50 per 1,000 saved rows). Each saved data row is one `result` event. Status rows, error rows and empty lookups are not charged. The run stops when it reaches the maximum cost per run. No Apify proxy and no login.

### FAQ

**How complete is the history?**
The actor uses the two public Hacker News APIs: Algolia for search and Firebase for live items and comment expansion. Algolia's index can lag behind Firebase, and deleted or moderated items disappear or change after collection. This is a point-in-time fetcher with bounded limits, not an exhaustive archive - frontpage mode can only see the current top 500 IDs, and empty result sets are normal for dated or low-score queries.

**Why can I not filter comments by score?**
Comment vote scores are not public in either API. `minScore` applies to stories only; comment search requires `minScore` to stay at zero.

**What are the hard limits?**
`maxResults` caps combined story and comment rows at 1,000; `maxRequests` caps all HTTP attempts at 500 (including retries and comment expansions); `maxPages` caps query paging at 20. Comment expansion shares the same result budget, so a small `maxResults` plus `includeComments` yields few rows - raise both limits together.

**What happens on rate limits or access errors?**
Network errors, HTTP 429 and 5xx are retried up to twice; access denial is not retried. The run ends with an explicit status (`rate_limited`, `access_denied`, ...) and keeps every record collected so far. Check `SUMMARY.status`: a technically successful run can still report an upstream failure.

**Is this allowed, and what about personal data?**
The actor reads Hacker News through the API that HN itself provides for public use (the Algolia HN Search API and the official Firebase API), with no scraping or login. Content is stored as text and never rendered or executed. Authors publish on HN under its own terms, but you are responsible for downstream use - handle author names as personal data under laws such as the GDPR where applicable. This tool is not affiliated with Hacker News.

### Support

Questions, bugs or feature requests: use the **Issues** tab on the Actor page. Independent tool, not affiliated with, endorsed by or sponsored by Hacker News or Y Combinator.

### Local use and verification

Requires Node.js 22 or newer. Install from the lockfile:

```powershell
npm ci --ignore-scripts
npm test
npm run check
$env:LOCAL_MODE='1'
node src/main.js input.example.json local-output.json
```

`LOCAL_MODE=1` reads the first argument as input JSON and writes a JSON array to the second argument. It does not read Apify storage, initialize Apify, or need an Apify token. Invalid or malformed input JSON yields a free invalid-input status. A missing input file is a local filesystem error.

Runtime dependency `apify` is pinned to 3.7.2. A narrow `basic-ftp` override to 6.2.3 fixes the vulnerable FTP parser transitively included by the SDK; the actor itself does not use FTP. The exact override, lockfile and SDK paths used by charging integration tests should be reviewed together when upgrading the SDK.

Evidence in `evidence/` includes unit/mock/SDK charging checks, syntax checks, npm audit output, and live Algolia/Firebase samples. These are local tests. Cloud build, platform billing configuration, and publication are separate deployment steps.

### Data limitations and sources

Algolia indexing can lag behind Firebase. Search data and live discussion expansion can therefore disagree in counts or timing. Deleted/moderated records and scores can change after collection. Story text and title may contain HTML. Comment vote scores are deliberately absent, and missing parent/story context remains null rather than being fabricated. Date and score filters may yield fewer rows than requested.

API and SDK behavior were checked against primary documentation on 2026-10-21:

- [Algolia HN Search API](https://hn.algolia.com/api)
- [Official Hacker News Firebase API](https://github.com/HackerNews/API)
- [Apify Actor SDK API, pushData overload](https://docs.apify.com/sdk/js/reference/class/Actor)
- [Apify pay-per-event and charge-limit handling](https://docs.apify.com/sdk/js/docs/concepts/pay-per-event)
- [Dataset schema](https://docs.apify.com/storage/dataset-schema) and [output schema](https://docs.apify.com/actors/development/actor-definition/output-schema)
- [basic-ftp maintainer release 6.2.3](https://github.com/patrickjuchli/basic-ftp/releases/tag/v6.2.3)

# Actor input Schema

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

Query uses Algolia; frontpage uses the current Firebase topstories list.

## `query` (type: `string`):

Empty means latest/relevant records without a text filter. Must be empty in frontpage mode.

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

Frontpage requires story. Comment scores are not public.

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

Applies to query mode. Frontpage preserves the live topstories order.

## `fromDate` (type: `string`):

Optional inclusive UTC timestamp, e.g. 2026-01-01T00:00:00Z. Also applies to expanded comments.

## `toDate` (type: `string`):

Optional inclusive UTC timestamp, e.g. 2026-10-01T00:00:00Z.

## `minScore` (type: `integer`):

Story score threshold. Must be zero for comment search; comment scores are unavailable.

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

Combined limit for stories and comments. A free final status row is additional.

## `maxPages` (type: `integer`):

Query mode only. Algolia's own pagination window can be smaller.

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

Query mode only.

## `maxRequests` (type: `integer`):

Hard shared budget for search, stories, comments, and retries.

## `includeComments` (type: `boolean`):

Retrieve Firebase comment trees for matching stories within the limits below.

## `maxCommentsPerStory` (type: `integer`):

Maximum comment IDs visited per story, including deleted/filtered comments. Breadth-first order.

## `maxCommentDepth` (type: `integer`):

Top-level replies have depth 1.

## `requestDelayMs` (type: `integer`):

Single sequential request stream; retries also obey spacing.

## `timeoutSecs` (type: `integer`):

Includes response body reading. Maximum two retries for transient failures.

## Actor input object example

```json
{
  "mode": "query",
  "query": "",
  "itemType": "story",
  "sort": "date",
  "minScore": 0,
  "maxResults": 3,
  "maxPages": 5,
  "pageSize": 50,
  "maxRequests": 200,
  "includeComments": false,
  "maxCommentsPerStory": 5,
  "maxCommentDepth": 2,
  "requestDelayMs": 200,
  "timeoutSecs": 15
}
```

# Actor output Schema

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

Flat records. Filter recordType=story/comment for data; status rows are free and carry completeness/errors.

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

Final status, number of accepted data rows and HTTP attempts.

# 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("atesen-software/hacker-news-search-comments").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("atesen-software/hacker-news-search-comments").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 atesen-software/hacker-news-search-comments --silent --output-dataset

```

## MCP server setup

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

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/fTI8EYejyWITBm1qz/builds/iS3fcK5iUyojhgNsT/openapi.json
