# Hacker News Search Scraper (`xtracto/hackernews-search`) Actor

Search Hacker News stories and comments: title, URL, author, points, comment counts and full comment text, with filters for Show HN, Ask HN, front page, author, score and date. Keyless Algolia index.

- **URL**: https://apify.com/xtracto/hackernews-search.md
- **Developed by:** [Farhan Febrian Nauval](https://apify.com/xtracto) (community)
- **Categories:** News
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $6.67 / 1,000 results

This Actor is paid per event and usage. You are charged both the fixed price for specific events and for Apify platform usage.
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

## Hacker News Search Scraper — Stories, Comments, Show HN & Ask HN

Search the whole Hacker News archive through its Algolia index: stories and comments with title,
link, author, points, comment counts and full comment text, filtered by type, author, score and
date. Keyless. No login, no browser, **no proxy needed**.

### Input

Every field is optional, but give at least one of a search term, an item type or an author.

| Field | Type | Default | Description |
|---|---|---|---|
| `searchQueries` | array | — | One search per entry |
| `itemTypes` | array | *all* | `story`, `comment`, `ask-hn`, `show-hn`, `poll`, `front-page` |
| `author` | string | — | Only items by this HN username |
| `minPoints` / `minComments` | integer | — | Score and discussion thresholds |
| `createdAfterTimestamp` / `createdBeforeTimestamp` | integer | — | Unix seconds |
| `sortByDate` | boolean | `false` | Newest first instead of most relevant |
| `maxItemsPerQuery` | integer | `200` | Capped by the index at ~1,000 |

Searching by filters alone works: leave `searchQueries` empty and ask for every Show HN above
100 points.

### Output

One row shape for both stories and comments.

```jsonc
{
  "_input": "rust",
  "_source": "S1-hn-algolia",
  "_scrapedAt": "2026-09-09T14:02:31Z",

  "objectId": "22238335",
  "type": "story",                 // story | comment | ask_hn | show_hn | poll
  "hnUrl": "https://news.ycombinator.com/item?id=22238335",

  "title": "Why Discord is switching from Go to Rust",
  "isOwnTitle": true,              // false on a comment — see below
  "url": "https://blog.discordapp.com/why-discord-is-switching-from-go-to-rust…",
  "text": null,                    // comment body, plain text

  "author": "Sikul",
  "authorUrl": "https://news.ycombinator.com/user?id=Sikul",

  "points": 1582,
  "numComments": 642,
  "childCount": 40,

  "storyId": 22238335, "parentId": null,

  "createdAt": "2020-02-04T17:30:40Z",
  "createdAtTimestamp": 1580837440,
  "updatedAt": "2023-09-06T20:31:44Z",
  "tags": ["story", "author_Sikul", "story_22238335"]
}
```

### Three things worth knowing

**On a comment, `title` is the parent story's — `isOwnTitle` tells you which.** Comments come back
with `story_title` and `story_url` rather than their own, so a naive merge silently attributes a
story's headline to a comment. Both are merged into `title`/`url` for a single usable row shape, and
`isOwnTitle` is `false` whenever the title came from the parent. `points` and `numComments` are
empty on comments because HN does not score them.

**Only about 1,000 results per query are reachable.** `nbHits` reports the true match count —
60,019 for one test — but `nbPages × hitsPerPage` always works out to roughly 1,000 whatever page
size is requested. Paging past the end returns an empty `hits` list with `nbPages: 0`, which is
honest as these things go. Slice by `createdAfterTimestamp` / `createdBeforeTimestamp` to walk a
large query in windows.

**Item type comes from `_tags`, not from which fields are missing.** A comment can be spotted by
its lack of a `title`, but that is fragile, and it cannot distinguish an Ask HN or Show HN post from
a plain story. `_tags` states the kind outright, so that is what `type` is derived from.

### Errors

| `_error` | Meaning |
|---|---|
| `no_results` | The search ran and matched nothing |
| `unexpected_shape` | A 200 without a `hits` list, or a hit without an `objectID` |
| `blocked` | Every TLS profile was refused |
| `network_error` | The ladder never reached the server |

If every search fails, the run itself fails rather than reporting success over an empty dataset.

# Actor input Schema

## `searchQueries` (type: `array`):

One search per entry. Leave empty to search by filters alone, e.g. every Show HN above 100 points.

## `itemTypes` (type: `array`):

Restrict to stories, comments, Ask HN, Show HN, polls or the current front page. Leave empty for everything.

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

Only items by this HN username.

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

Only items with at least this score. Comments usually have no score.

## `minComments` (type: `integer`):

Only stories with at least this many comments.

## `createdAfterTimestamp` (type: `integer`):

Also the way past the 1,000-result ceiling: run the same search over several time windows.

## `createdBeforeTimestamp` (type: `integer`):

Upper bound on creation time.

## `sortByDate` (type: `boolean`):

Use the search_by_date index. Same corpus, newest first.

## `maxItemsPerQuery` (type: `integer`):

Algolia serves at most about 1,000 results for any one query, however many matched.

## `proxyConfiguration` (type: `object`):

Optional. This is a keyless public index with no anti-bot layer, so a proxy is not needed.

## Actor input object example

```json
{
  "searchQueries": [
    "rust"
  ],
  "sortByDate": false,
  "maxItemsPerQuery": 200
}
```

# Actor output Schema

## `overview` (type: `string`):

Dataset items shown in the 'Items' view.

## `items` (type: `string`):

Every record this run produced, with all fields, as JSON.

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

// Run the Actor and wait for it to finish
const run = await client.actor("xtracto/hackernews-search").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 = { "searchQueries": ["rust"] }

# Run the Actor and wait for it to finish
run = client.actor("xtracto/hackernews-search").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 '{
  "searchQueries": [
    "rust"
  ]
}' |
apify call xtracto/hackernews-search --silent --output-dataset

```

## MCP server setup

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

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/MfxLKtpmH90eJntoh/builds/ucJQuoCjesQTJqyfC/openapi.json
