Fixes every tool returning Bootstrap could not obtain session cookies.
- Replaced
undici with impit as the HTTP client. Reddit now fingerprints the
caller: from the same residential IP, curl gets 200 and undici gets 403 on
every attempt, so the cookie bootstrap could never succeed. impit
impersonates a real Chrome signature and the bootstrap works again.
- A
200 response carrying the "Welcome to Reddit" HTML interstitial is now
treated as a dead session — the cookies are dropped and the session rotated,
instead of spending the remaining retries on a session Reddit already refused.
- Redirects are no longer followed: a
302 to /login means the session was
refused, and is now retried on a fresh sticky IP.
Initial public release.
- Apify Standby MCP server exposing seven Reddit tools over Streamable HTTP (
/mcp).
- JSON-RPC 2.0, protocol version
2025-06-18.
- Tools:
search_reddit, get_subreddit_posts, get_post_with_comments, get_user_posts, get_user_comments, get_subreddit_info, get_trending_subreddits.
- Residential proxy (Apify Proxy
RESIDENTIAL group) with fallback to default Apify Proxy.
- Puppeteer-extra + stealth against
old.reddit.com JSON endpoints; images/fonts/CSS/media blocked via request interception.
- Pay-per-event pricing:
$0.02 per successful tool call. initialize, tools/list, ping and notifications are free.
GET / returns a server manifest listing the tools (useful for debugging MCP client integrations).
Rebuilt on Reddit's public Atom feeds. Same 7 tools, same names, same inputs.
Reddit closed anonymous access to *.json on every host and restricted new Data API
registrations to moderation use cases. The impit + sticky-residential + cookie bootstrap
that worked in August now fails on every attempt: there is no challenge to pass.
lib/reddit-fetch.js rewritten: fetches /.rss feeds, no cookies, no bootstrap, no
sticky sessions. 429 is treated as a pace limit with exponential backoff, not a block.
- Residential proxy demoted to fallback. The feeds answer 200 from datacenter IPs;
residential was ~80% of the cost and bought nothing.
- New
lib/atom.js: minimal Atom parser shared by the tools.
search_reddit now passes type=link. Without it Reddit's search feed also returns
communities (t5_), which arrived as posts with no author and the subreddit name in
the title field.
- Vote data (
score, upvoteRatio, numComments) and moderation flags are null, never
0, so a caller can tell "unknown" from "zero". Every tool description says so, since
an MCP client picks tools by reading them.
get_subreddit_info now serves what the feed header carries (name, title, description,
icon, URL, last activity); subscriber and active-user counts are null — Reddit does
not publish them any more.
get_post_with_comments requires subreddit (the comment feed is per-subreddit) and
reports commentSortHonoured: false, since the feed serves one fixed order.
Opt-in HTML route that fills in the engagement fields. The Atom feeds carry no
vote data, so score, upvoteRatio and numComments had been null since 2.0.0.
This release recovers them from Reddit's rendered pages, without giving up the feeds
as the base route.
- New
lib/session.js, lib/htmlRoute.js, lib/shreddit.js and lib/richData.js.
The session pool minted by reddit-session-refresher is read from its Key-Value
Store by ID (REDDIT_SESSION_STORE_ID): a run under LIMITED_PERMISSIONS
cannot resolve a named store it did not create.
- Reddit's budget is 225 requests per 10 minutes and it is counted PER SESSION,
not per IP (both halves measured 2026-09-02). More proxies do not raise the
ceiling; more sessions do. The route paces itself, reads the remaining budget off
every response, brakes before a session runs out and rotates to the next one.
- 🔑 A 200 proves nothing on this route. Three response shapes were measured and
are told apart by size plus the presence of the element we came for: ~8.4 KB with
no
<shreddit-post> is the JavaScript challenge, ~190 KB is the IP block page,
and only 500 KB–1.2 MB with the marker is a real page. The middle one looks
substantial and would sail past a naive size check.
- Promoted items (
<shreddit-ad-post>, is-ad, and friends) are never parsed as
organic posts or comments.
selftext and comment bodies no longer carry Reddit's feed trailer
("  submitted by /u/author [link] [comments]"). New lib/atomText.js cuts it
off and decodes numeric entities, instead of merely stripping tags.
- Own proxy read from
REDDIT_PROXY_URL, so it never lands in the repo. A
proxyConfiguration set on the Actor still takes precedence.
get_subreddit_info gains weeklyActiveUsers. It is deliberately not called
subscribers: it reads 292,024 for r/Drizzy but 3,599,935 for r/explainlikeimfive,
which has well over 20 million subscribers.
- Everything here is best-effort. If the session pool is unavailable the tools keep
working on the feeds alone and say so in the log, with the concrete reason.
Two bugs found by calling all seven tools for real against 2.1.2, plus two smaller
fixes. The first two are also shipped on their own as 2.0.3, because they are in the
build customers are running.
- 🔴 A 429 no longer kills the whole tool call. On the feed route a rate limit
threw a bare error that
runTool turned into isError: true; three calls died
that way in a single validation pass. Now Retry-After is honoured when Reddit
sends one, the fallback ladder carries ±30 % jitter so parallel containers stop
retrying in lockstep, and rate-limit retries get their own budget instead of
eating the attempts reserved for transport errors. Only once all of that is spent
does it throw, and what it throws says it is a pace limit worth retrying.
- 🔴 Partial results are kept instead of discarded. If a rate limit lands part
way through a paginated feed, the pages already fetched are returned with
partial: true and a notice telling the caller to retry for the rest. Only a
failure on the first page still raises, because then there is nothing to hand
back.
- 🔴
get_user_comments returned created: null on every comment.
/user/<name>/comments/.rss entries carry <updated> and no <published>,
and the transform read only published. It now falls back to updated for both
comments and posts. edited deliberately still reads the raw pair, so a missing
published cannot be misreported as an edit.
get_subreddit_info.url returned the feed's alternate link with our own paging
parameter glued on (.../r/Drizzy/?limit=1) as if it were the subreddit's
canonical URL. The query and fragment are now stripped.
- Test suite grown to 60 checks, including a live one that stands up a local server
answering a real 429 over a real socket.