Reddit Product Research with an API (2026)
Search public Reddit posts for competitor mentions, switching intent, and feature requests — normalized JSON you can pipe into spreadsheets, dashboards, or AI summaries.
Reddit threads surface unfiltered switching stories, competitor comparisons, and feature gaps that never make it into sales calls. The hard part is collecting those threads at scale without maintaining Reddit's auth and pagination quirks yourself.
This guide walks through a research sprint — the same sequence product teams run manually, but with API calls you can schedule, dedupe, and hand to a PM spreadsheet. We use a fictional competitor check on project-management tools as the running example; swap in your product name and subreddit list.
The short version
GET /v1/reddit/search with a query parameter returns public posts as JSON. Paginate with cursor, scope to subreddits when you know the community, pull comments on high-signal threads, and merge into the research tool you already use.
You'll need an API key. Try a search in the Playground.
Why Reddit beats surveys for switching stories
Surveys tell you what people think they would do. Reddit threads often describe what they did — including the messy middle: trial extensions, half-migrated teams, pricing surprises, integrations that broke on day three.
Three patterns show up repeatedly in product research:
- Switching language — "moved from Asana to Linear because…" gives you the reason, not a 1–5 satisfaction score.
- Niche job context — r/sysadmin and r/selfhosted discuss the same category (monitoring, backups, auth) with completely different constraints than r/startups.
- Long-form self-posts — the post body plus top comments can read like a mini case study; worth archiving when you build battlecards.
Product teams use Reddit for positioning checks, roadmap validation, and churn post-mortems. The API work is mostly collection — your judgment still picks which threads matter.
Phase 0: Write the question before the query
Before touching code, write one sentence your sprint must answer. Vague goals produce vague queries.
| Research goal | Example question | Bad query | Better query |
|---|---|---|---|
| Competitive positioning | Why do teams leave Notion for Coda? | notion | switching from notion |
| Feature gaps | What do users want that our roadmap lacks? | project management | wish notion had OR notion missing |
| Pricing sensitivity | Are we expensive vs incumbents? | expensive | notion too expensive |
| Launch monitoring | Did v2.4 change sentiment? | acme | acme v2.4 with timeframe=week |
List 8–15 query variants per product. Group them:
- Brand — product name, common misspellings.
- Alternatives —
{product} alternative,best {category} tool. - Switching —
switching from {product},migrated from {product},leaving {product}. - Pain —
{product} slow,{product} pricing,{product} support.
You will run these in Phase 6. First, cast a wide net globally, then narrow to subreddits.
Phase 1: Global keyword sweep
Start with site-wide search. This finds threads you would not guess from subreddit names alone — cross-posts, niche communities, old threads that still rank.
curl -sS \
-H "x-api-key: $SOCIALFETCH_API_KEY" \
-G "https://api.socialfetch.dev/v1/reddit/search" \
--data-urlencode "query=best project management software" \
--data-urlencode "sortBy=top"Parameters worth tuning:
| Parameter | Values | When to use |
|---|---|---|
sortBy | relevance, new, top | top + timeframe=year for battlecard fodder; new for launch week |
timeframe | day, week, month, year, all | Pair with sortBy=top; skip on new |
cursor | opaque string from data.page.nextCursor | Next page — do not construct manually |
Full spec: Reddit search.
A single page is rarely enough. Paginate until data.page.hasMore is false or you hit your credit budget:
import { SocialFetchClient } from "@socialfetch/sdk";
const client = new SocialFetchClient({
apiKey: process.env.SOCIALFETCH_API_KEY!,
});
const posts = [];
let cursor: string | undefined;
do {
const result = await client.reddit.search({
query: "switching from notion",
sortBy: "top",
timeframe: "year",
cursor,
});
if (!result.ok) {
console.error(result.error.code, result.error.requestId);
break;
}
posts.push(...(result.value.data.posts ?? []));
cursor = result.value.data.page.nextCursor ?? undefined;
} while (cursor);
console.log(posts.length, "posts collected");Check data.page.hasMore — do not infer completion from data.totalResults alone; that field counts the current page.
Phase 2: Map the subreddits that matter
Global search is noisy. The second pass targets communities where your buyers actually post.
| Category | Example subreddits | What you learn |
|---|---|---|
| B2B SaaS | r/SaaS, r/startups, r/Entrepreneur | Pricing, churn, "what stack do you use" |
| Role-specific | r/sysadmin, r/devops, r/productivity | Workflow constraints incumbents ignore |
| Product-specific | r/Notion, r/ObsidianMD, r/selfhosted | Power-user feature requests, workarounds |
| Vertical | r/realestate, r/ecommerce, r/legaltech | Industry-specific objections |
Discovery trick: run global search once, tally post.subreddit frequencies, and promote the top five into your scoped list. A PM tool might surface r/Notion, r/productivity, and r/SaaS — but r/ADHD or r/GradSchool can dominate for certain personas.
Validate a subreddit exists before building cron jobs:
curl -sS \
-H "x-api-key: $SOCIALFETCH_API_KEY" \
"https://api.socialfetch.dev/v1/reddit/subreddits?subreddit=SaaS"Reference: Get subreddit. Subscriber counts and descriptions help you prioritize where a thread's upvotes actually mean something.
Phase 3: Search inside one community
When you know the subreddit, scoped search cuts noise. Use GET /v1/reddit/subreddits/search — different route from global search, with sort options like comments for threads where the argument lives in replies.
curl -sS \
-H "x-api-key: $SOCIALFETCH_API_KEY" \
-G "https://api.socialfetch.dev/v1/reddit/subreddits/search" \
--data-urlencode "subreddit=SaaS" \
--data-urlencode "query=notion alternative" \
--data-urlencode "sort=top" \
--data-urlencode "timeframe=year"TypeScript equivalent:
const result = await client.reddit.searchSubreddit({
subreddit: "SaaS",
query: "notion alternative",
sort: "top",
timeframe: "year",
});
if (result.ok) {
for (const post of result.value.data.posts ?? []) {
console.log(post.title, post.metrics?.score);
}
}Reference: Subreddit search.
Query tactics that work in practice:
- Drop the brand in pain threads — search
frustratedorcancelinside r/YourProductSubreddit before searching your competitor's name globally. - Category without brand —
best crm for small teaminside r/smallbusiness surfaces options your brand might not appear in yet. - Sort by comments —
sort=commentssurfaces debate threads; pair with Phase 5 comment pulls.
Phase 4: Browse a subreddit feed
Sometimes you do not have a keyword — you want "what is hot in r/selfhosted this month." List the feed directly:
curl -sS \
-H "x-api-key: $SOCIALFETCH_API_KEY" \
"https://api.socialfetch.dev/v1/reddit/subreddits/selfhosted/posts?sort=top&timeframe=month"sort value | Typical research use |
|---|---|
top | Highest-engagement posts in the timeframe window |
new | Fresh complaints right after a competitor launch |
hot | What the community is actively engaging with now |
rising | Early signal before a thread hits front page |
Reference: Subreddit posts.
Subreddit names must match Reddit's casing — SaaS not saas. The path parameter accepts r/ prefix or a full subreddit URL.
Filter client-side: keep posts where title or text matches your keyword list, or where metrics.commentCount exceeds a threshold (debate threads tend to carry switching rationale).
Phase 5: Pull comment threads
Titles lie (or undersell). Comments hold objections, workarounds, and "we tried X and went back to Y."
Pass a post URL to the comments endpoint:
curl -sS \
-H "x-api-key: $SOCIALFETCH_API_KEY" \
-G "https://api.socialfetch.dev/v1/reddit/posts/comments" \
--data-urlencode "url=https://www.reddit.com/r/SaaS/comments/example/"Paginate comment pages the same way as search — data.page.nextCursor until hasMore is false. Top-level comments often include nested replies; flatten or walk the tree depending on your analysis tool.
const threadUrl = "https://www.reddit.com/r/SaaS/comments/abc123/example_thread/";
const comments = [];
let cursor: string | undefined;
do {
const result = await client.reddit.listPostComments({ url: threadUrl, cursor });
if (!result.ok || result.value.data.lookupStatus !== "found") break;
comments.push(...(result.value.data.comments ?? []));
cursor = result.value.data.page.nextCursor ?? undefined;
} while (cursor);Reference: Reddit post comments. For scoring comment text at scale, see the sentiment guide.
Pull comments selectively — not every search hit deserves a comment page. Heuristics that save credits:
metrics.commentCountabove 20metrics.scoreabove 50 for niche subreddits (adjust per community size)- Title matches a switching phrase from Phase 0
Phase 6: Batch the query matrix
Run your query variants and merge results. Dedupe by post.id or canonical url before analysis.
import { SocialFetchClient } from "@socialfetch/sdk";
const client = new SocialFetchClient({
apiKey: process.env.SOCIALFETCH_API_KEY!,
});
const product = "notion";
const queries = [
`${product} alternative`,
`switching from ${product}`,
`best ${product} competitor`,
];
const mentions = [];
for (const query of queries) {
const result = await client.reddit.search({ query, sortBy: "top", timeframe: "year" });
if (!result.ok) continue;
for (const post of result.value.data.posts ?? []) {
mentions.push({
query,
title: post.title,
subreddit: post.subreddit,
score: post.metrics?.score,
url: post.url,
});
}
}
console.log(mentions.length, "threads to review");Extend the loop: add timeframe, paginate inner loops when hasMore is true, and write meta.requestId on every row. If a thread's score looks wrong in your spreadsheet, support can trace the exact lookup.
For weekly monitoring, wrap the batch in a cron or QStash job — same pattern as the social listening guide, but with Reddit query matrices instead of brand keywords across platforms.
Store rows your PM can sort
Normalize API responses into flat rows your team already uses — Airtable, Postgres, Google Sheets, Notion database:
{
"capturedAt": "2026-06-30T14:00:00.000Z",
"query": "switching from notion",
"subreddit": "SaaS",
"title": "Finally moved our team off Notion — here's what broke",
"score": 847,
"commentCount": 132,
"url": "https://www.reddit.com/r/SaaS/comments/...",
"requestId": "req_01example",
"creditsCharged": 1
}Suggested columns for a research board:
| Column | Source field | Why |
|---|---|---|
| Theme | your taxonomy | "pricing", "performance", "missing feature" — manual or LLM-tagged |
| Evidence | url | Link back to the thread for quotes in decks |
| Weight | score, commentCount | Rank themes by engagement, not mention count alone |
| Freshness | createdAt / publishedAt | Deprioritize stale threads unless evergreen |
Export to CSV, or pipe title + bodyText + top comment texts into an LLM summary with the thread URL as citation. Keep requestId in the warehouse — it is your audit trail.
Troubleshooting
Empty results but the thread exists in a browser
- Global search may not surface every post. Try scoped
subreddits/searchwith the subreddit from the URL. - Check
timeframe—dayon a year-old thread returns nothing withsortBy=top. - Query length maxes at 512 characters; shorten long boolean-style strings.
not_found on subreddit or post lookups
- Casing: Reddit treats
Fitnessandfitnessdifferently. Copy the exact name from the address bar. - Deleted or quarantined content returns
not_foundat lookup time — not a billing bug. - Pass the full
https://www.reddit.com/r/.../comments/...URL to comments, not a short link or old.reddit redirect stripped of path segments.
Duplicate threads in merged results
- The same post appears under multiple queries. Dedupe on
post.idbefore counting mentions. - Cross-posts share text but have different IDs — dedupe on
urlor normalized permalink if you want one row per discussion.
Scores and counts look stale
- Each response is point-in-time. Re-fetch before quoting metrics in a board deck.
- Store
capturedAtalongsidescoreso analysts know the snapshot age.
lookup_failed or HTTP 503
- Not charged. Retry with backoff; include
meta.requestIdfrom the failed attempt if you contact support. - Bursting hundreds of parallel requests is unnecessary — sequential pagination with modest concurrency is fine.
Rate limits
- No hard rate cap on metered routes; your credit balance is the practical limit. Staying under ~500 concurrent requests is a courtesy, not a ceiling.
Billing and honest boundaries
Each search page, subreddit feed page, and comment page is a separate credit (typically 1 per successful request). A completed lookup with zero posts still ran upstream and is billed. You are never charged for pre-send validation errors, lookup_failed, or 503 temporarily_unavailable.
Rough sprint math for planning:
| Step | Calls | Credits (approx.) |
|---|---|---|
| 10 queries × 2 pages global search | 20 | 20 |
| 5 subreddits × 1 scoped search | 5 | 5 |
| 15 high-signal comment pulls | 15 | 15 |
| Total | 40 | 40 |
Public data only — you remain responsible for lawful use under Terms. See Credits.
What you can build
- Competitive battlecards — weekly pull of
alternative to {product}andswitching from {product}with top comment quotes attached. - Feature prioritization — rank request themes by upvote-weighted frequency inside product-specific subreddits.
- Launch monitoring — alert when mention volume spikes after a release (
sortBy=new,timeframe=week). - Churn autopsy feed — tag threads matching
cancel,refund,moved tofor CS and product sync. - Persona research — same product name searched across r/sysadmin vs r/marketing to compare job-to-be-done language.
Next steps: Playground · Reddit API reference · Pricing