Cloudflare Worker proxy for Hacker News clients that need a service-friendly auth layer.
The Worker logs in to Hacker News with the normal acct / pw form flow, stores the returned HN user cookie server-side in Cloudflare KV, and returns a signed JWT for browser or PWA clients. The JWT contains only the HN username and an internal session id.
GET /docsserves Swagger UI.GET /openapi.jsonserves the generated OpenAPI 3.1 document.POST /auth/loginwith{ "username": "...", "password": "..." }returns a bearer JWT.GET /auth/mereturns the current JWT-backed session.GET /auth/submissionsreturns the current user's submitted HN stories as parsed JSON.GET /auth/commentsreturns the current user's HN comments page as parsed JSON.GET /auth/upvoted?type=submissions|commentsreturns the current user's upvoted HN submissions or comments as parsed JSON.GET /auth/favorites?type=submissions|commentsreturns the current user's favorite HN submissions or comments as parsed JSON.POST /auth/logoutdeletes the server-side HN session.GET /v0/*proxies the official Firebase Hacker News API./hn/*proxiesnews.ycombinator.com/*with the stored HN cookie injected. SendAuthorization: Bearer <token>.
The frontend project that consumes this API is Yukaii/ykhn.
pnpm install
cp .dev.vars.example .dev.vars
pnpm devSet JWT_SECRET in .dev.vars to a long random value.
Login attempts are rate-limited per client IP using LOGIN_RATE_LIMIT_MAX and
LOGIN_RATE_LIMIT_WINDOW_SECONDS from wrangler.toml.
pnpm format
pnpm lint
pnpm typecheck
pnpm test
pnpm checkThe project uses Oxlint and Oxfmt. pnpm check runs formatting checks, linting, TypeScript, and tests.
OpenAPI is generated by @hono/zod-openapi from route definitions and Zod schemas in src/index.ts.
The raw upstream proxy endpoints are registered in the same OpenAPI registry because they intentionally pass through non-JSON upstream responses.
The /hn/* endpoint is a cookie-injecting proxy for Hacker News web endpoints. It does not invent a separate typed API for HN actions; clients should fetch the HN page, parse the action URL that Hacker News generated for the logged-in user, then call that URL through /hn/*.
All logged-in calls need the JWT returned by /auth/login:
curl -H "Authorization: Bearer $TOKEN" \
"https://hn-api.yukai.dev/hn/item?id=48080800"To vote on an item:
- Fetch the item page through
/hn/item?id=<item-id>. - Parse the HN vote link from the returned HTML.
- Call the parsed link through
/hn/<vote-link>.
HN vote links include an account/session-specific auth parameter, so clients should not construct them manually.
Example shape:
vote?id=48080800&how=up&auth=<hn-auth-token>&goto=item%3Fid%3D48080800
vote?id=48080800&how=un&auth=<hn-auth-token>&goto=item%3Fid%3D48080800Call it through the proxy:
curl -i -H "Authorization: Bearer $TOKEN" \
"https://hn-api.yukai.dev/hn/vote?id=48080800&how=up&auth=<hn-auth-token>&goto=item%3Fid%3D48080800"A successful vote or unvote currently returns HN's normal redirect response, usually 302 back to the goto page. The deployed API has been tested with an upvote followed by an unvote; after upvoting, the item page exposed how=un, and after unvoting it exposed how=up again.
To show a reply form:
- Fetch the item page through
/hn/item?id=<story-id>. - Parse an actual
reply?...link from the returned HTML. - Call that parsed reply link through
/hn/<reply-link>.
Reply links are for comments, not the story root. Calling /hn/reply?id=<story-id> may return an empty page. A valid reply URL looks like:
reply?id=<comment-id>&goto=item%3Fid%3D<story-id>%23<comment-id>The reply page returns HN's normal comment form, including hidden parent, goto, hmac, and a textarea. Creating posts and deleting comments are intentionally not supported/documented yet.
The API exposes typed JSON wrappers around HN's logged-in account pages:
curl -H "Authorization: Bearer $TOKEN" \
"https://hn-api.yukai.dev/auth/submissions?page=1"
curl -H "Authorization: Bearer $TOKEN" \
"https://hn-api.yukai.dev/auth/comments?page=1"
curl -H "Authorization: Bearer $TOKEN" \
"https://hn-api.yukai.dev/auth/upvoted?type=submissions&page=2"
curl -H "Authorization: Bearer $TOKEN" \
"https://hn-api.yukai.dev/auth/upvoted?type=comments&page=2"
curl -H "Authorization: Bearer $TOKEN" \
"https://hn-api.yukai.dev/auth/favorites?type=submissions&page=1"
curl -H "Authorization: Bearer $TOKEN" \
"https://hn-api.yukai.dev/auth/favorites?type=comments&page=1"These wrap HN's pages:
submitted?id=<username>&p=<page>threads?id=<username>&p=<page>upvoted?id=<username>&p=<page>upvoted?id=<username>&comments=t&p=<page>favorites?id=<username>&p=<page>favorites?id=<username>&comments=t&p=<page>
Submission-list responses look like:
{
"user": "pg",
"page": 2,
"items": [
{
"id": 37144985,
"rank": 1,
"title": "Htmx is part of the GitHub Accelerator",
"url": "https://htmx.org/posts/2023-06-06-htmx-github-accelerator/",
"site": "htmx.org",
"score": 1109,
"by": "jjdeveloper",
"age": "on Aug 16, 2023",
"time": 1692181172,
"comments": 487,
"itemUrl": "item?id=37144985"
}
],
"nextPage": 3,
"nextUrl": "upvoted?id=pg&p=3"
}Comment-list responses look like:
{
"user": "pg",
"page": 1,
"items": [
{
"id": 37023160,
"by": "jph",
"age": "on Aug 6, 2023",
"time": 1691336757,
"text": "How to add a date to HN title?",
"textHtml": "How to add a date to HN title?",
"parentUrl": "item?id=37022911",
"contextUrl": "item?id=37022911#37023160",
"itemUrl": "item?id=37023160",
"story": {
"id": 37022911,
"title": "I went to 50 different dentists",
"url": "item?id=37022911"
}
}
],
"nextPage": 2,
"nextUrl": "upvoted?id=pg&comments=t&p=2"
}The real login test is opt-in so normal test runs do not require credentials.
cp test/.env.hn.example test/.env.hnFill in HN_USERNAME and HN_PASSWORD, then run:
pnpm testtest/.env.hn is ignored by git.
Create a KV namespace:
pnpm exec wrangler kv namespace create HN_SESSIONS
pnpm exec wrangler kv namespace create HN_SESSIONS --previewPut the returned ids in wrangler.toml, then set the production JWT secret:
pnpm exec wrangler secret put JWT_SECRET
pnpm deployThis is an unofficial Hacker News integration. The official Firebase API is read-only; logged-in actions go through the HN website and may change if Hacker News changes its form fields or cookie behavior.