Omar EldeebFind Hacker News stories between two dates or above a points threshold with the free Algolia API, and get past the 1,000-result cap. Tested Python and JS.
To search Hacker News by date, call https://hn.algolia.com/api/v1/search_by_date with numericFilters=created_at_i>=START,created_at_i<END in Unix seconds. To filter by score, add points>100 to the same parameter, comma-separated. Use /search instead when you want the highest-scoring stories first. No API key is needed. One query returns at most 1,000 hits, so split long ranges.
Every example below ran against the live API on 2026-10-14. For the full parameter list, see the HN Algolia API reference.
| Goal | Parameters |
|---|---|
| Posted in September 2026 (UTC) | numericFilters=created_at_i>=1788220800,created_at_i<1790812800 |
| At least 100 points | numericFilters=points>=100 |
| Busy discussions | numericFilters=num_comments>=200 |
| Both |
numericFilters=points>=100,num_comments>=200 (comma = AND) |
| Either |
numericFilters=(points>5000,num_comments>4000) (parentheses = OR) |
| Stories only | tags=story |
| Show HN or Ask HN |
tags=show_hn or tags=ask_hn
|
| Newest first | the /search_by_date endpoint |
| Highest score first | the /search endpoint with an empty query
|
created_at_i is in seconds, not milliseconds, and in UTC. The official page's own example is search_by_date?tags=story&numericFilters=created_at_i>X,created_at_i<Y.
Which endpoint sorts how. The same September window gave a different first page on each endpoint. /search_by_date returned stories from the last minutes of the window, each with a handful of points. /search returned the month's biggest stories. The official page says /search is "sorted by relevance, then points, then number of comments", and with no query, relevance is a tie, so points decide.
# Top 5 stories of September 2026 (UTC) by points
curl -s "https://hn.algolia.com/api/v1/search?tags=story&numericFilters=created_at_i%3E%3D1788220800,created_at_i%3C1790812800&hitsPerPage=5&attributesToRetrieve=title,points,created_at" \
| python -c "import sys, json; [print(h['points'], h['created_at'][:10], h['title']) for h in json.load(sys.stdin)['hits']]"
2391 2026-09-15 Show HN: An e-ink frame that hears birds and draws them as 1800s illustrations
2301 2026-09-04 Discovery of a new OpenAI agent message board
2279 2026-09-03 GPT-6 Astra
2230 2026-09-03 .name Termination
2054 2026-09-08 Navier-Stokes – Tristan Buckmaster [pdf]
%3E%3D is >= and %3C is <. To get the timestamps in Python, use int(datetime(2026, 9, 1, tzinfo=timezone.utc).timestamp()); that gives 1788220800.
The API sends Access-Control-Allow-Origin: *, so this works in a browser as well as in Node 18+.
// Stories from the last 7 days with 300+ points, highest first.
const since = Math.floor(Date.now() / 1000) - 7 * 24 * 3600;
const params = new URLSearchParams({
tags: "story",
numericFilters: `created_at_i>${since},points>=300`,
hitsPerPage: "5",
});
const res = await fetch(`https://hn.algolia.com/api/v1/search?${params}`);
const { hits, nbHits } = await res.json();
console.log(`${nbHits} stories`);
for (const h of hits) console.log(h.points, h.created_at.slice(0, 10), h.title);
Output on 2026-10-14:
98 stories
2123 2026-10-07 Margaret Hamilton has died
1440 2026-10-09 Triple-A Minesweeper
1407 2026-10-13 Gemini 4 Argon
1384 2026-10-09 Cloudflare acquires Deno
1134 2026-10-08 Why isn't the industry freaking out about DeepSeek 4.1 Flash?
A single query stops at 1,000 hits. Past that, the API returns no hits and the message "you can only fetch the 1000 hits for this query". The fix is to cut the date range into slices that each hold 1,000 or fewer and join them. This function does it automatically by halving any window that is too big:
import time
from datetime import datetime, timezone
import requests
API = "https://hn.algolia.com/api/v1/search_by_date"
s = requests.Session()
def ts(day):
"""'2025-01-01' -> Unix seconds (UTC)."""
return int(datetime.strptime(day, "%Y-%m-%d").replace(tzinfo=timezone.utc).timestamp())
def get(params, attempts=4):
for i in range(attempts):
r = s.get(API, params=params, timeout=30)
if r.status_code == 200:
return r.json()
time.sleep(2 ** i) # 1, 2, 4 s on 429/5xx
r.raise_for_status()
def search_window(tags, start, end, extra="", query=""):
"""Every hit with start <= created_at_i < end. Splits the window while a
slice holds more than 1,000 hits, because deeper pages are refused."""
nf = f"created_at_i>={start},created_at_i<{end}" + (f",{extra}" if extra else "")
first = get({"query": query, "tags": tags, "numericFilters": nf, "hitsPerPage": 1000})
if first["nbHits"] <= 1000:
return first["hits"]
mid = (start + end) // 2
return (search_window(tags, start, mid, extra, query)
+ search_window(tags, mid, end, extra, query))
if __name__ == "__main__":
t0 = time.time()
hits = search_window("show_hn", ts("2025-01-01"), ts("2026-01-01"), extra="points>=100")
ids = {h["objectID"] for h in hits}
print(f"{len(hits)} Show HN posts with 100+ points in 2025 "
f"({len(ids)} unique) in {time.time() - t0:.1f}s")
for h in sorted(hits, key=lambda h: -h["points"])[:5]:
print(h["points"], h["created_at"][:10], h["title"])
# A window that needs splitting: every story posted in one week.
t0 = time.time()
week = search_window("story", ts("2026-09-01"), ts("2026-09-08"))
print(f"{len(week)} stories posted 2026-09-01..07 in {time.time() - t0:.1f}s")
Output on 2026-10-14:
742 Show HN posts with 100+ points in 2025 (742 unique) in 3.5s
3346 2025-12-09 Show HN: Gemini Pro 3 imagines the HN front page 10 years from now
3237 2025-01-22 Show HN: I made an open-source laptop from scratch
1581 2025-02-26 Show HN: I got laid off from Meta and created a minor hit on Steam
1557 2025-12-20 Show HN: Jmail – Google Suite for Epstein files
1539 2025-06-27 Show HN: I'm an airline pilot – I built interactive graphs/globes of my flights
7020 stories posted 2026-09-01..07 in 18.5s
The first query fit in one request. The second didn't: 7,020 stories in one week, so the function split the week into slices. As a check, I asked for each of the seven days separately; their nbHits added up to exactly 7,020, and every returned ID was unique and inside the window.
>= at the start and < at the end, as above. With >= and <= on both ends, an item posted exactly on a boundary second lands in two slices.nbHits only when it's exact. For broad queries the response sets exhaustiveNbHits: false, and the count is an estimate. The splitting function only compares nbHits with 1,000 on narrow windows, where the counts I saw were exact.points is current, not historical. A story's points keep changing while it's active, and the filter uses the latest value. "Stories that had 100 points on day one" isn't something this API can answer.numericFilters=score>10 returned 0 hits and no error. The fields are created_at_i, points and num_comments.The free Hacker News search tool runs these searches live in your browser. Type a keyword, choose stories, comments, Show HN or Ask HN, sort by relevance or date, and set a minimum score. The results are real, with points, comment counts, author and date.
The Hacker News Scraper actor on Apify takes the same filters as inputs: a keyword, a date range in Unix seconds (dateFromUnix, dateToUnix), minimum score and comments, and a domain filter. It returns flat rows you can export as JSON, CSV or Excel, optionally with comment trees and author karma, and it runs on a schedule. It's free to start, then pay-as-you-go.
Disclosure: I build the datatooly tool and the Apify actor. The scripts above use only the public API.
Sources: HN Search API (official page), Algolia: numericFilters, and live responses from hn.algolia.com/api/v1 on 2026-10-14.