← Back to dashboard
redditcommsfresh

Reddit Integration Guide

What is Reddit?

The real model

OAuth2 REST API at `oauth.reddit.com`: listen via listings/search, engage via `/api/comment` + `/api/submit`.

Mint a ~1h token at `/api/v1/access_token` (HTTP Basic `client_id:secret`; `client_credentials` for read-only, `password` for a script-app brand account), then call `oauth.reddit.com` with a **descriptive User-Agent** (generic ones get throttled) inside a **60 req/min** budget surfaced by `X-Ratelimit-*`. Fullnames matter: `t3_`=post, `t1_`=comment. For codeAmani this is the ingestion edge of voice-of-customer research — pull US-subreddit threads, cluster them with Claude into ranked pain-points to feed the roadmap, then engage value-first. No sockpuppets or vote manipulation: it's bannable and burns the codeAmani-Labs account.

Six Reddit primitives

One OAuth token, then read to listen and write to engage — all over `oauth.reddit.com`.

Text
██████╗ ███████╗██████╗ ██████╗ ██╗████████╗
██╔══██╗██╔════╝██╔══██╗██╔══██╗██║╚══██╔══╝
██████╔╝█████╗  ██║  ██║██║  ██║██║   ██║   
██╔══██╗██╔══╝  ██║  ██║██║  ██║██║   ██║   
██║  ██║███████╗██████╔╝██████╔╝██║   ██║   
╚═╝  ╚═╝╚══════╝╚═════╝ ╚═════╝ ╚═╝   ╚═╝   

Reddit Integration Guide

Focus: Use the Reddit Data API as a market-research and community-engagement channel — listen to what users in our target subreddits actually ask for, surface threads codeAmani can genuinely answer, and grow the codeAmani-Labs account as a trusted voice (not a billboard).

Overview

Reddit is a forum-of-forums: thousands of topic communities ("subreddits") where people ask real questions and complain about real problems in their own words. That makes it one of the highest-signal voice-of-customer sources on the open web — and a place to build reputation by being helpful before being promotional.

The Reddit Data API is a JSON/REST API authenticated with OAuth2. There is no required SDK — every endpoint is reachable with fetch — but snoowrap (Node) and praw (Python) wrap auth, rate limiting, and pagination for you.

The codeAmani loop is listen → analyse → engage: pull threads, cluster them with Claude into themes/pain-points that inform the roadmap, then reply with genuine value (and only sometimes a soft, rule-compliant mention).

Official Documentation


1. Register an app

At https://www.reddit.com/prefs/apps (logged in as codeAmani-Labs), create an app. The type decides the OAuth flow:

App typeUse it forGrant
scriptServer-side bot acting as one account (our brand account)password
web appActing as other users via OAuth consent; long-lived botsauthorization_code (+ refresh token)
installed appPublic clients (mobile/SPA), no secretinstalled_client

You receive a client ID (under the app name) and a client secret. Store both server-side only.

Bash
# .env.local — never commit, never ship to the browser
REDDIT_CLIENT_ID=...
REDDIT_CLIENT_SECRET=...
REDDIT_USERNAME=codeAmani-Labs
REDDIT_PASSWORD=...                 # only for the script-app password grant
# Descriptive User-Agent is MANDATORY — generic ones get throttled/blocked.
REDDIT_USER_AGENT="web:com.codeamanilabs.listener:v1.0 (by /u/codeAmani-Labs)"

User-Agent format (from the API rules): <platform>:<app ID>:<version> (by /u/<username>). Reddit aggressively rate-limits or blocks default library agents (axios/x.y, python-requests). Always send a unique, descriptive UA.


2. Get an OAuth2 token

The token endpoint is POST https://www.reddit.com/api/v1/access_token, authenticated with HTTP Basic (client_id:client_secret). Tokens last ~1 hour — cache and refresh before expiry.

Read-only "listening" token (application-only)

Best for the listen/analyse half of the loop — no account actions, just reads.

TypeScript
// lib/reddit-auth.ts
const TOKEN_URL = "https://www.reddit.com/api/v1/access_token";

export async function getAppToken(): Promise<string> {
  const basic = Buffer.from(
    `${process.env.REDDIT_CLIENT_ID}:${process.env.REDDIT_CLIENT_SECRET}`
  ).toString("base64");

  const res = await fetch(TOKEN_URL, {
    method: "POST",
    headers: {
      Authorization: `Basic ${basic}`,
      "Content-Type": "application/x-www-form-urlencoded",
      "User-Agent": process.env.REDDIT_USER_AGENT!,
    },
    // Confidential clients (script / web app) use client_credentials.
    // Public "installed" apps use grant_type=https://oauth.reddit.com/grants/installed_client&device_id=...
    body: new URLSearchParams({ grant_type: "client_credentials" }),
  });

  if (!res.ok) throw new Error(`Reddit token failed: ${res.status}`);
  const { access_token } = (await res.json()) as { access_token: string };
  return access_token;
}

Posting token (script app, password grant)

Use this to act as codeAmani-Labs (comment / submit). The password grant only works for the developer account that owns a script-type app.

TypeScript
// Swap the body for the password grant; everything else is identical.
body: new URLSearchParams({
  grant_type: "password",
  username: process.env.REDDIT_USERNAME!,
  password: process.env.REDDIT_PASSWORD!,
}),

For a robust long-running bot prefer a web app + authorization_code flow with duration=permanent to obtain a refresh token, so you never store the account password. The password grant is the quickest path for a single brand account.


3. Authenticated requests → oauth.reddit.com

Once you hold a token, all API calls go to https://oauth.reddit.com (not www.reddit.com), with a bearer token and your UA. Modhashes are not needed under OAuth.

TypeScript
// lib/reddit.ts
const API = "https://oauth.reddit.com";

async function redditGet(path: string, token: string) {
  const res = await fetch(`${API}${path}`, {
    headers: {
      Authorization: `bearer ${token}`,
      "User-Agent": process.env.REDDIT_USER_AGENT!,
    },
  });
  // Respect the budget — see §5.
  logRateLimit(res.headers);
  if (!res.ok) throw new Error(`Reddit GET ${path}${res.status}`);
  return res.json();
}

Listen: newest posts in a subreddit

TypeScript
// GET /r/:subreddit/new  — limit ≤ 100, paginate with `after`
const data = await redditGet("/r/SaaS/new?limit=50", token);
const posts = data.data.children.map((c: any) => ({
  fullname: c.data.name,        // e.g. "t3_abc123" — the post's fullname
  title: c.data.title,
  body: c.data.selftext,
  url: `https://reddit.com${c.data.permalink}`,
  score: c.data.score,
  numComments: c.data.num_comments,
}));

Listen: search for threads we can answer

TypeScript
// Search ALL of Reddit
await redditGet(`/search?q=${encodeURIComponent("m-pesa integration nextjs")}&sort=new&limit=25`, token);

// Search WITHIN one subreddit (restrict_sr=true)
await redditGet(`/r/Kenya/search?q=mpesa+api&restrict_sr=true&sort=relevance&limit=25`, token);

// Find relevant communities to monitor
await redditGet(`/subreddits/search?q=saas&sort=relevance&limit=10`, token);

Engage: comment on a thread

thing_id is the fullname of the parent (t3_ = post, t1_ = comment). Requires the submit scope and a posting token.

TypeScript
async function redditPost(path: string, token: string, form: Record<string, string>) {
  const res = await fetch(`https://oauth.reddit.com${path}`, {
    method: "POST",
    headers: {
      Authorization: `bearer ${token}`,
      "Content-Type": "application/x-www-form-urlencoded",
      "User-Agent": process.env.REDDIT_USER_AGENT!,
    },
    body: new URLSearchParams(form),
  });
  if (!res.ok) throw new Error(`Reddit POST ${path}${res.status}`);
  return res.json();
}

// Reply to a post
await redditPost("/api/comment", token, {
  api_type: "json",
  thing_id: "t3_abc123",
  text: "Here's how we solved the STK Push idempotency problem…", // raw markdown
});

Engage: submit a self-post

TypeScript
// kind=self for a text post; kind=link with `url` for a link post.
await redditPost("/api/submit", token, {
  api_type: "json",
  sr: "kenya",
  kind: "self",
  title: "We open-sourced a Daraja M-Pesa helper for Next.js",
  text: "After shipping a few M-Pesa flows, here's what we learned…",
});

Pre-validate before submitting. GET /api/v1/{subreddit}/post_requirements returns mod rules (min/max title length, required flair, blacklisted words, allowed domains). Check it first to avoid auto-removals — and to respect the community.


4. snoowrap quickstart (Node)

For most app code the wrapper is faster than hand-rolling auth + pagination. snoowrap handles tokens, the 60/min budget, and listings.

Bash
npm install snoowrap
TypeScript
import Snoowrap from "snoowrap";

const r = new Snoowrap({
  userAgent: process.env.REDDIT_USER_AGENT!,
  clientId: process.env.REDDIT_CLIENT_ID!,
  clientSecret: process.env.REDDIT_CLIENT_SECRET!,
  username: process.env.REDDIT_USERNAME!,
  password: process.env.REDDIT_PASSWORD!,
});

// Listen
const newPosts = await r.getSubreddit("SaaS").getNew({ limit: 50 });
const hits = await r.search({ query: "mpesa api", sort: "new", time: "week" });

// Engage
await r.getSubmission("abc123").reply("Genuinely useful answer here…");

snoowrap is in low-maintenance mode but remains the de-facto Node wrapper. For pure data-collection / analysis pipelines, PRAW (pip install praw) is the better-maintained Python option and pairs naturally with pandas/Claude for theme clustering.


5. Rate limits & resilience

OAuth clients get ~60 requests/minute, averaged over a rolling 10-minute window. Every response carries the budget — read it and back off rather than hammering:

TypeScript
function logRateLimit(h: Headers) {
  const remaining = Number(h.get("x-ratelimit-remaining") ?? "60");
  const reset = Number(h.get("x-ratelimit-reset") ?? "0"); // seconds until window reset
  if (remaining < 5) {
    // Sleep until the window resets instead of risking a 429.
    console.warn(`Reddit budget low: ${remaining} left, reset in ${reset}s`);
  }
}
HeaderMeaning
X-Ratelimit-UsedRequests used this window
X-Ratelimit-RemainingRequests left this window
X-Ratelimit-ResetSeconds until the window resets

On 429, honour X-Ratelimit-Reset (or Retry-After) and retry with backoff. Cache tokens (~1 h) and listing results — listening doesn't need to be real-time.


codeAmani notes

Security

  • All credentials (client_id, client_secret, account password / refresh token) live server-side only — in .env.local / Vercel env vars, never in a client bundle or a NEXT_PUBLIC_* var. Do Reddit calls from API routes or background jobs.
  • Never log tokens or passwords. Treat the refresh token like a password.

Culture & ToS — this is the part that matters most

  • The brief is to "promote without explicitly saying so." On Reddit that means be genuinely helpful first; a relevant link is welcome only when it actually answers the question. Overt marketing, repeated drops of the same link, and thin self-promo get removed and can ban the account.
  • No sockpuppets, no vote manipulation, no astroturfing — all are site-wide-rule violations and the fastest way to lose the codeAmani-Labs account. One authentic account, real participation.
  • Respect each subreddit's self-promotion rules and Reddiquette (a common norm: keep self-promotion well under ~10% of your activity). Read the sidebar/rules before posting; use post_requirements to pre-check.
  • The Reddit Data API Terms govern commercial and bulk use — register your app, stay within rate limits, and store only the data you need. Don't redistribute scraped user content.

AI routing (ties to the AI Routing Policy)

  • Pipe collected titles/bodies/comments into Claude for sentiment, theme clustering, and pain-point extraction — turn raw threads into a ranked list of "what users keep asking for." Use OpenAI structured output if you want strict JSON tags per thread. This is the bridge from listening to roadmap.

Market fit (US-first, Kenya per-project)

  • codeAmani is US-first, so default monitoring to US-relevant communities — e.g. r/SaaS, r/smallbusiness, r/Entrepreneur, r/webdev — to learn what US consumers/SMBs want from AI tooling.
  • For Kenya-targeted projects, monitor r/Kenya, r/Nairobi, and fintech/dev threads to validate M-Pesa / low-bandwidth assumptions straight from users.