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 goalExample questionBad queryBetter query
Competitive positioningWhy do teams leave Notion for Coda?notionswitching from notion
Feature gapsWhat do users want that our roadmap lacks?project managementwish notion had OR notion missing
Pricing sensitivityAre we expensive vs incumbents?expensivenotion too expensive
Launch monitoringDid v2.4 change sentiment?acmeacme v2.4 with timeframe=week

List 8–15 query variants per product. Group them:

  1. Brand — product name, common misspellings.
  2. Alternatives{product} alternative, best {category} tool.
  3. Switchingswitching from {product}, migrated from {product}, leaving {product}.
  4. 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.

Request
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:

ParameterValuesWhen to use
sortByrelevance, new, toptop + timeframe=year for battlecard fodder; new for launch week
timeframeday, week, month, year, allPair with sortBy=top; skip on new
cursoropaque string from data.page.nextCursorNext 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.

CategoryExample subredditsWhat you learn
B2B SaaSr/SaaS, r/startups, r/EntrepreneurPricing, churn, "what stack do you use"
Role-specificr/sysadmin, r/devops, r/productivityWorkflow constraints incumbents ignore
Product-specificr/Notion, r/ObsidianMD, r/selfhostedPower-user feature requests, workarounds
Verticalr/realestate, r/ecommerce, r/legaltechIndustry-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 frustrated or cancel inside r/YourProductSubreddit before searching your competitor's name globally.
  • Category without brandbest crm for small team inside r/smallbusiness surfaces options your brand might not appear in yet.
  • Sort by commentssort=comments surfaces 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 valueTypical research use
topHighest-engagement posts in the timeframe window
newFresh complaints right after a competitor launch
hotWhat the community is actively engaging with now
risingEarly 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.commentCount above 20
  • metrics.score above 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.

Example
typescript
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:

ColumnSource fieldWhy
Themeyour taxonomy"pricing", "performance", "missing feature" — manual or LLM-tagged
EvidenceurlLink back to the thread for quotes in decks
Weightscore, commentCountRank themes by engagement, not mention count alone
FreshnesscreatedAt / publishedAtDeprioritize 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/search with the subreddit from the URL.
  • Check timeframeday on a year-old thread returns nothing with sortBy=top.
  • Query length maxes at 512 characters; shorten long boolean-style strings.

not_found on subreddit or post lookups

  • Casing: Reddit treats Fitness and fitness differently. Copy the exact name from the address bar.
  • Deleted or quarantined content returns not_found at 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.id before counting mentions.
  • Cross-posts share text but have different IDs — dedupe on url or 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 capturedAt alongside score so analysts know the snapshot age.

lookup_failed or HTTP 503

  • Not charged. Retry with backoff; include meta.requestId from 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:

StepCallsCredits (approx.)
10 queries × 2 pages global search2020
5 subreddits × 1 scoped search55
15 high-signal comment pulls1515
Total4040

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} and switching 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 to for 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