GuidesHACKER NEWS
Hacker News API and RSS feeds
The Hacker News Firebase API and the Algolia HN Search API both send Access-Control-Allow-Origin, so browser JavaScript can call them directly with no key and no proxy. The RSS feeds at news.ycombinator.com/rss and hnrss.org send no CORS header; prefix them with https://proxy.cors.dev/, which is free, needs no signup and allows 600 requests per minute per client IP for feeds up to 1 MiB. The feeds below weigh 11 to 32 KB.
Which Hacker News source to use
| Source | Base URL | From the browser | Best for |
|---|---|---|---|
| Firebase API (official) | https://hacker-news.firebaseio.com/v0/ | Direct | Story lists, single items, user profiles |
| Algolia HN Search | https://hn.algolia.com/api/v1/ | Direct | Search by keyword, author, date and points |
| Official RSS feed | https://news.ycombinator.com/rss | Via proxy | The 30 front page stories in one request |
| hnrss.org feeds | https://hnrss.org/frontpage | Via proxy | Filtered feeds by points, comments, keyword or user |
The Firebase API and Algolia echo your page's origin in Access-Control-Allow-Origin, so call them directly: a proxy would only add a hop. All four sources are read-only and need no key; you cannot post, vote or comment through any of them.
Top, new and best stories with the Firebase API
The story lists return arrays of item IDs, not stories. Fetch the list, then fetch each item from /v0/item/<id>.json. The API README says there is currently no rate limit.
| Path | Returns | Size |
|---|---|---|
| /v0/topstories.json | 500 top story IDs | 4.4 KB |
| /v0/newstories.json | 500 newest story IDs | 4.4 KB |
| /v0/beststories.json | 200 best story IDs | 1.8 KB |
| /v0/askstories.json | Latest Ask HN IDs (23 when checked) | 208 B |
| /v0/showstories.json | Latest Show HN IDs (164 when checked) | 1.4 KB |
| /v0/jobstories.json | Latest job IDs (31 when checked) | 280 B |
| /v0/item/8863.json | One story, comment, job or poll | 375 B |
| /v0/user/pg.json | Profile with karma and every submitted item ID | 112 KB |
| /v0/maxitem.json | The newest item ID | 8 B |
| /v0/updates.json | Recently changed items and profiles | 807 B |
A story item has id, type, by, time (Unix seconds), title, url, score, descendants (the comment count) and kids (IDs of top-level comments). Ask HN posts have text instead of url, so link those to the discussion page. This snippet loads the top 30 stories with one request for the list and 30 parallel requests for the items:
// Markup: <ol id="stories"></ol>
const HN = 'https://hacker-news.firebaseio.com/v0';
async function getJson(url) {
const response = await fetch(url);
if (!response.ok) throw new Error(`Hacker News answered ${response.status} for ${url}`);
return response.json();
}
async function stories(list = 'topstories', limit = 30) {
const ids = await getJson(`${HN}/${list}.json`);
const items = await Promise.all(ids.slice(0, limit).map((id) => getJson(`${HN}/item/${id}.json`)));
return items
.filter((item) => item && !item.deleted && !item.dead)
.map((item) => ({
title: item.title,
url: item.url ?? `https://news.ycombinator.com/item?id=${item.id}`,
points: item.score,
comments: item.descendants ?? 0,
by: item.by,
posted: new Date(item.time * 1000),
}));
}
const list = document.querySelector('#stories');
stories().then((top) => {
list.replaceChildren(
...top.map((story) => {
const entry = document.createElement('li');
const link = document.createElement('a');
if (/^https?:/.test(story.url)) link.href = story.url;
link.textContent = story.title;
entry.append(link, ` (${story.points} points, ${story.comments} comments)`);
return entry;
}),
);
});Search Hacker News with Algolia
/api/v1/search sorts by relevance and /api/v1/search_by_date puts the newest first. Filter with tags (story, comment, ask_hn, show_hn, front_page, author_<username>) and with numericFilters on points, num_comments and created_at_i; separate conditions with commas to require all of them. Results come 20 per page by default; set hitsPerPage and page for more.
| Request | Returns | Size |
|---|---|---|
| /api/v1/search?query=cors&tags=story | The 20 most relevant stories | 19 KB |
| /api/v1/search_by_date?query=cors&tags=story | The 20 newest matching stories | 42 KB |
| /api/v1/search?tags=front_page&hitsPerPage=30 | All 30 front page stories | 31 KB |
| /api/v1/search?tags=comment&query=cors&hitsPerPage=5 | Five matching comments | 9.7 KB |
| /api/v1/search?tags=story,author_pg&hitsPerPage=1 | Latest story by one user | 1.6 KB |
| /api/v1/items/8863 | A story with its full comment tree | 40 KB |
| /api/v1/users/pg | A user profile | 54 B |
Each hit has title, url, points, num_comments, author, created_at_i and objectID, which is the Hacker News item ID:
// Runs in a module script or the DevTools console
async function searchStories(query, { minPoints = 0, days = 30, newestFirst = false } = {}) {
const since = Math.floor(Date.now() / 1000) - days * 86400;
const params = new URLSearchParams({
query,
tags: 'story',
numericFilters: `points>=${minPoints},created_at_i>${since}`,
hitsPerPage: '20',
});
const endpoint = newestFirst ? 'search_by_date' : 'search';
const response = await fetch(`https://hn.algolia.com/api/v1/${endpoint}?${params}`);
if (!response.ok) throw new Error(`Algolia answered ${response.status}`);
const { hits, nbHits } = await response.json();
return {
total: nbHits,
stories: hits.map((hit) => ({
title: hit.title,
url: hit.url ?? `https://news.ycombinator.com/item?id=${hit.objectID}`,
points: hit.points,
comments: hit.num_comments,
author: hit.author,
posted: new Date(hit.created_at_i * 1000),
})),
};
}
const { total, stories } = await searchStories('rust', { minPoints: 100, days: 365 });
console.log(`${total} stories, showing ${stories.length}`);
console.table(stories);Hacker News RSS feeds
https://news.ycombinator.com/rss returns the 30 front page stories with title, link, pubDate and comments (11 KB). hnrss.org adds feeds for new, best, Ask HN, Show HN, jobs and single users, with filters. Neither sends a CORS header, so a direct fetch() fails and the proxy prefix makes it work.
| Feed | Returns | Size |
|---|---|---|
| https://news.ycombinator.com/rss | The 30 front page stories | 11 KB |
| https://hnrss.org/frontpage | 20 front page stories | 17 KB |
| https://hnrss.org/newest | The 20 newest stories | 15 KB |
| https://hnrss.org/best | Best stories | 25 KB |
| https://hnrss.org/ask | Ask HN posts | 25 KB |
| https://hnrss.org/show | Show HN posts | 15 KB |
| https://hnrss.org/jobs | Job posts | 15 KB |
| https://hnrss.org/newest?points=100 | New stories with 100 or more points | 18 KB |
| https://hnrss.org/newest?comments=50 | New stories with 50 or more comments | 18 KB |
| https://hnrss.org/newest?q=cors | New stories matching a keyword | 16 KB |
| https://hnrss.org/user?id=pg | Posts and comments by one user | 18 KB |
| https://hnrss.org/frontpage.jsonfeed | The front page as JSON Feed | 18 KB |
| https://hnrss.org/whoishiring/jobs | Who is hiring job comments | 32 KB |
hnrss.org feeds return 20 items by default and count=N raises that. Filters combine, for example /newest?q=rust&points=50. link=comments points each item's link at the discussion instead of the article, and the .atom and .jsonfeed endpoints return the same items as Atom or JSON. Each item's description includes Points: N and # Comments: N, and dc:creator holds the author:
// Markup: <ul id="feed"></ul>
const DC = 'http://purl.org/dc/elements/1.1/';
async function readFeed(feedUrl) {
const response = await fetch('https://proxy.cors.dev/' + feedUrl, { credentials: 'omit' });
if (!response.ok) {
throw new Error(`Feed request failed: ${response.headers.get('X-Cors-Error') ?? response.status}`);
}
const xml = new DOMParser().parseFromString(await response.text(), 'application/xml');
if (xml.querySelector('parsererror')) throw new Error('The feed is not valid XML');
return [...xml.querySelectorAll('item')].map((item) => {
const description = item.querySelector('description')?.textContent ?? '';
return {
title: item.querySelector('title')?.textContent.trim() ?? '',
link: item.querySelector('link')?.textContent.trim() ?? '',
comments: item.querySelector('comments')?.textContent.trim() ?? '',
author: item.getElementsByTagNameNS(DC, 'creator')[0]?.textContent ?? '',
posted: new Date(item.querySelector('pubDate')?.textContent ?? 0),
points: Number(description.match(/Points: (\d+)/)?.[1] ?? 0),
};
});
}
const list = document.querySelector('#feed');
readFeed('https://hnrss.org/frontpage')
.then((items) => {
list.replaceChildren(
...items.map((item) => {
const entry = document.createElement('li');
const link = document.createElement('a');
if (/^https?:/.test(item.link)) link.href = item.link;
link.textContent = item.title;
entry.append(link, ` (${item.points} points, by ${item.author})`);
return entry;
}),
);
})
.catch((error) => {
list.textContent = error.message;
});Fetch an RSS feed in JavaScript covers Atom feeds and other parsing details, and the free news API guide lists 27 news feeds that load the same way.
Limits
- Firebase API: no key, CORS for any origin and no rate limit according to its README. Every story costs one request, so a page of 30 stories is 31 requests.
- Algolia: no key and CORS for any origin. Big pages are fine when called directly;
hitsPerPage=1000returned 1.3 MB. - hnrss.org: popular feeds such as
/frontpagecome from the hnrss.org cache and answer quickly. Rarely requested feeds are built on demand:/newcomments,/threads?id=dangand/frontpage?count=100answered 504upstream_timeoutthrough the proxy on a first try and 200 later, once hnrss.org had built them. Retry after a 504. - Proxy free tier: 600 requests per minute per client IP, no daily or monthly quota, GET and HEAD only, text responses up to 1 MiB and a 10-second deadline for the upstream to answer.
- [Pro](/pricing): $5 per month for 500,000 requests per billing period, with opt-in caching through
X-Cors-Cachefor 1 to 300 seconds if many visitors load the same feed.
Good questions.
Is there an official Hacker News API?
Yes. Hacker News publishes its data through Firebase at https://hacker-news.firebaseio.com/v0/. It is read-only, needs no key, has no rate limit according to its README and sends CORS headers, so fetch() works from any web page.
Does the Hacker News API need an API key?
No. The Firebase API, Algolia HN Search, news.ycombinator.com/rss and hnrss.org all work without a key or signup.
What is the Hacker News RSS feed URL?
https://news.ycombinator.com/rss returns the 30 front page stories. hnrss.org has more feeds, such as https://hnrss.org/frontpage, https://hnrss.org/newest and https://hnrss.org/best, plus filters like ?points=100. In browser code, prefix either with https://proxy.cors.dev/.
Why does fetch() fail on the Hacker News RSS feed?
news.ycombinator.com and hnrss.org send no Access-Control-Allow-Origin header, so the browser blocks your script from reading the response. Request https://proxy.cors.dev/https://hnrss.org/frontpage instead, or use the Firebase or Algolia API, which allow browser calls.
How do I search Hacker News by date or points?
Use Algolia: https://hn.algolia.com/api/v1/search_by_date?query=rust&tags=story&numericFilters=points%3E%3D100 returns matching stories with at least 100 points, newest first (%3E%3D is >= URL-encoded). Add a condition like created_at_i>1790000000, comma-separated, to set a start date in Unix seconds.
How do I get the Hacker News front page in one request?
Call https://hn.algolia.com/api/v1/search?tags=front_page&hitsPerPage=30 directly (31 KB, JSON), or load https://news.ycombinator.com/rss through the proxy (11 KB, RSS). The Firebase API needs 31 requests: the list plus one per story.