Skip to Content
ProvidersReddit

Reddit

Category: Social Publishing
Integration type: Tenant OAuth (stored in integrations table)
External API: Reddit OAuth2 API v1


Purpose

Reddit integration enables community engagement and organic brand presence in relevant subreddits. Use cases:

  • Social Post Writer — generates Reddit-native posts (Ask Me Anything prompts, resource shares, discussions) that match subreddit culture
  • Topic Researcher — reads subreddit discussions to surface trending topics in a client’s niche
  • Social Publisher — posts approved content to the tenant’s Reddit account

Reddit requires a distinctly different content strategy to other platforms — promotional content is actively penalised by communities. The agent’s system prompt is adjusted for Reddit’s culture (value-first, community-native).

Important: Reddit posts must be submitted under the tenant’s own Reddit account (not the platform’s). The platform facilitates posting on behalf of the tenant; it does not maintain a shared Reddit account.


Config Structure

OAuth flow

scope: submit,read,identity Stored in integrations: provider: 'reddit' api_key: encrypt(refresh_token) metadata: { redditUsername: 'acme_plumbing_official', accessToken: string, tokenExpiresAt: string, // Reddit tokens expire after 1 hour }

Reddit access tokens are short-lived (1 hour). The tool layer refreshes using the stored refresh token before each call.

Platform app credentials (env vars)

REDDIT_CLIENT_ID=xxxxxxxxxxxxxxxxxxxxxxx REDDIT_CLIENT_SECRET=xxxxxxxxxxxxxxxxxxxxxxxxxxxx REDDIT_USER_AGENT=Leadmetrics/1.0 by /u/leadmetrics_bot

The User-Agent header is required by Reddit’s API. It must follow the format platform/version by /u/username. Using a generic UA results in rate limiting or bans.


Integration Pattern

Token refresh + post submission

class RedditTool { private baseUrl = 'https://oauth.reddit.com'; constructor( private clientId: string, private clientSecret: string, private refreshToken: string, private userAgent: string, ) {} private async getAccessToken(): Promise<string> { const response = await axios.post( 'https://www.reddit.com/api/v1/access_token', new URLSearchParams({ grant_type: 'refresh_token', refresh_token: this.refreshToken, }), { auth: { username: this.clientId, password: this.clientSecret }, headers: { 'User-Agent': this.userAgent }, }, ); return response.data.access_token; } private async headers() { const token = await this.getAccessToken(); return { Authorization: `bearer ${token}`, 'User-Agent': this.userAgent, }; } async submitPost(options: { subreddit: string; // Without r/ prefix, e.g. "plumbing" title: string; // Max 300 characters kind: 'self' | 'link'; text?: string; // For kind='self' (text post) url?: string; // For kind='link' flair?: string; // Flair text (if subreddit requires it) nsfw?: boolean; spoiler?: boolean; }): Promise<{ postId: string; postUrl: string }> { const body: any = { sr: options.subreddit, kind: options.kind, title: options.title, resubmit: false, nsfw: options.nsfw ?? false, spoiler: options.spoiler ?? false, }; if (options.kind === 'self') body.text = options.text ?? ''; if (options.kind === 'link') body.url = options.url; if (options.flair) body.flair_text = options.flair; const response = await axios.post( `${this.baseUrl}/api/submit`, new URLSearchParams(body), { headers: await this.headers() }, ); const json = response.data.json; if (json.errors?.length) { throw new Error(`Reddit submit error: ${json.errors.map((e: any) => e[1]).join(', ')}`); } const postData = json.data; return { postId: postData.id, postUrl: postData.url, }; } async getSubredditInfo(subreddit: string): Promise<SubredditInfo> { const response = await axios.get( `${this.baseUrl}/r/${subreddit}/about.json`, { headers: await this.headers() }, ); const data = response.data.data; return { name: data.display_name, subscribers: data.subscribers, activeUsers: data.active_user_count, rules: data.rules ?? [], flairRequired: data.link_flair_required ?? false, description: data.public_description, }; } async getHotPosts(subreddit: string, limit = 10): Promise<RedditPost[]> { const response = await axios.get( `${this.baseUrl}/r/${subreddit}/hot.json`, { headers: await this.headers(), params: { limit }, }, ); return response.data.data.children.map((c: any) => ({ id: c.data.id, title: c.data.title, score: c.data.score, comments: c.data.num_comments, author: c.data.author, url: c.data.url, selftext: c.data.selftext, flair: c.data.link_flair_text, })); } async verify(): Promise<void> { const response = await axios.get(`${this.baseUrl}/api/v1/me`, { headers: await this.headers(), }); if (!response.data.name) throw new Error('Reddit authentication failed'); } }

Topic research from Reddit

The Topic Researcher agent can pull hot posts from relevant subreddits to discover trending topics:

// Inside topic-researcher worker const subreddits = tenantProfile.relevantSubreddits ?? ['business', clientNiche]; const allPosts = await Promise.all( subreddits.map(sub => redditTool.getHotPosts(sub, 20)), ); // Flatten and pass titles to agent as topic inspiration context const topicContext = allPosts.flat().map(p => `- ${p.title} (${p.score} upvotes)`).join('\n');

Reddit Rate Limits

LimitValue
OAuth API calls60 requests/minute per OAuth client
Post submissions1 post per 10 minutes per account
Account karma thresholdSome subreddits require minimum karma — check rules

The Social Calendar Planner caps Reddit at 2–4 posts per week per account to avoid appearing spammy. Each submission is reviewed in HITL before posting.


Test Cases

Unit tests (packages/tools/src/reddit.test.ts)

TestApproach
submitPost() POSTs to /api/submit with form-encoded bodyMock axios.post; assert URLSearchParams body and Authorization header
submitPost() throws when json.errors non-emptyMock { json: { errors: [['SUBREDDIT_NOEXIST', 'subreddit not found']] } }
submitPost() returns postId and postUrl from responseMock { json: { data: { id: 'abc', url: 'https://reddit.com/r/...' } } }
getSubredditInfo() parses subscriber countMock response; assert numeric subscribers
getHotPosts() maps posts correctlyMock children array; assert titles and scores
Token refresh called before each requestAssert getAccessToken called; assert Authorization: bearer <token>
verify() throws on auth failureMock 401; assert throws

© 2026 Leadmetrics — Internal use only