Reddit has no native scheduling API — build your own cron job that calls POST /api/submit at the target time with kind=self (text post) or kind=link (URL post). Requires OAuth2 with submit scope. Free tier handles 60 RPM. Critical gotcha: RATELIMIT and ALREADY_SUB errors are returned inside 200 OK responses, not as HTTP error codes — you must parse the JSON body.
API Quick Reference
OAuth 2.0
60 requests/minute
JSON
Available
Understanding the Reddit API
The Reddit Data API is a REST API providing access to Reddit content, communities, and posting capabilities. Authentication uses OAuth2, and all API requests must go to oauth.reddit.com (not www.reddit.com) — the wrong base URL drops you to 10 RPM unauthenticated.
For post scheduling, the key endpoint is POST /api/submit. Reddit does not provide a native schedule parameter — you build scheduling yourself using a cron job, task queue (Celery, BullMQ), or a scheduled serverless function that fires at the target time and calls submit.
The free tier at 60 RPM is completely sufficient for scheduling. Unlike X/Twitter (which charges $0.015-$0.20 per post) or TikTok (which requires a 2-6 week audit), Reddit posting is free with no tier-based pricing. The only paid tier is the commercial enterprise agreement at $12,000/month for very high volume.
https://oauth.reddit.comSetting Up Reddit API Authentication
Reddit uses OAuth2 with different grant types for different app types. A script-type app (for your own bot) uses the password grant — simple but requires storing credentials. A web app uses authorization_code for multi-user scenarios. Access tokens expire after 1 hour; use duration=permanent with the web flow to get long-lived refresh tokens.
- 1Go to reddit.com/prefs/apps and click 'create another app'
- 2Select 'script' for a personal bot or 'web app' for a SaaS scheduler
- 3Set the redirect URI to http://localhost:8080 for script apps
- 4Note your client_id (below the app name) and client_secret
- 5POST to https://www.reddit.com/api/v1/access_token with Basic auth (client_id:client_secret)
- 6Include in request body: grant_type=password, username=your_reddit_username, password=your_password
- 7Set User-Agent: <platform>:<app-id>:<version> (by /u/<username>) — this format is mandatory
- 8Use the returned access_token as Authorization: Bearer <token> in all API calls to oauth.reddit.com
1import requests2import os34def get_access_token():5 CLIENT_ID = os.environ['REDDIT_CLIENT_ID']6 CLIENT_SECRET = os.environ['REDDIT_CLIENT_SECRET']7 USERNAME = os.environ['REDDIT_USERNAME']8 PASSWORD = os.environ['REDDIT_PASSWORD']9 USER_AGENT = f'python:com.example.scheduler:v1.0 (by /u/{USERNAME})'1011 r = requests.post(12 'https://www.reddit.com/api/v1/access_token',13 auth=requests.auth.HTTPBasicAuth(CLIENT_ID, CLIENT_SECRET),14 data={'grant_type': 'password', 'username': USERNAME, 'password': PASSWORD},15 headers={'User-Agent': USER_AGENT}16 )17 r.raise_for_status()18 return r.json()['access_token'], USER_AGENTSecurity notes
- •Store all credentials in environment variables — never hardcode CLIENT_ID, CLIENT_SECRET, or passwords
- •The User-Agent must follow <platform>:<app-id>:<version> (by /u/<username>) exactly — Reddit throttles non-compliant bots
- •Refresh tokens before the 1-hour expiry to avoid mid-job authentication failures
- •Use a dedicated bot account, not your personal Reddit account
- •For production schedulers, use the web app type with refresh tokens instead of hardcoded passwords
Key endpoints
/api/submitSubmits a new post to a subreddit. Use kind=self for text posts, kind=link for URL posts. Returns the new post's fullname and URL on success, or errors embedded in the 200 OK body.
| Parameter | Type | Required | Description |
|---|---|---|---|
kind | string | required | Post type: self (text), link (URL), image, video, videogif |
sr | string | required | Subreddit name (without r/ prefix) |
title | string | required | Post title, max 300 characters |
text | string | optional | Body text for self posts (Markdown supported) |
url | string | optional | URL for link posts |
flair_id | string | optional | Flair template ID from /r/{sr}/api/link_flair_v2 |
nsfw | boolean | optional | Mark post as NSFW |
spoiler | boolean | optional | Mark post as spoiler |
Request
1{"kind": "self", "sr": "python", "title": "Weekly discussion: best Python libraries 2026", "text": "What libraries have you discovered this week?", "nsfw": false, "spoiler": false}Response
1{"json": {"errors": [], "data": {"url": "https://www.reddit.com/r/python/comments/abc123/weekly_discussion_best_python_libraries_2026/", "drafts_count": 0, "id": "abc123", "name": "t3_abc123"}}}/api/needs_captchaChecks whether a CAPTCHA is required before posting. Returns true if the account is new or has low karma. If true, programmatic posting from new accounts may be blocked.
Response
1false/r/{subreddit}/api/flairselectApplies a flair to a recently submitted post. Call immediately after submit if the subreddit requires flair for posts to remain visible.
| Parameter | Type | Required | Description |
|---|---|---|---|
link | string | required | Fullname of the post (t3_xxx) |
flair_template_id | string | optional | UUID from the subreddit's flair list |
text | string | optional | Custom flair text if allowed by subreddit |
Request
1{"link": "t3_abc123", "flair_template_id": "uuid-here", "text": "Question"}Response
1{}Step-by-step automation
Authenticate and Build Headers
Why: All POST /api/submit calls require a valid user-context OAuth2 token — unauthenticated or app-only tokens cannot submit posts.
Authenticate using the password grant for script-type apps or the authorization_code flow for web apps. Build the headers dictionary you'll reuse for all API calls. Check token expiry before each post and refresh proactively at 50 minutes to avoid mid-schedule failures.
1curl -X POST https://www.reddit.com/api/v1/access_token \2 -u 'YOUR_CLIENT_ID:YOUR_CLIENT_SECRET' \3 -H 'User-Agent: script:com.example.scheduler:v1.0 (by /u/yourusername)' \4 -d 'grant_type=password&username=YOUR_USERNAME&password=YOUR_PASSWORD'Pro tip: Store the token in a module-level variable with its expiry time. Never fetch a new token on every API call — that wastes quota and slows down your scheduler.
Expected result: A headers object with Authorization and User-Agent ready for all subsequent API calls.
Load Scheduled Posts from Your Queue
Why: Your scheduler needs a data store for the post queue — this could be a JSON file, SQLite database, or a spreadsheet. Load posts that are due within the next poll interval.
Design a simple post record with: subreddit, title, kind (self/link), text or url, scheduled_time (ISO 8601), and status (pending/posted/failed). Query records where scheduled_time <= now AND status == pending. Sort by scheduled_time to post in order.
1# This step is data-layer logic, not an API call.2# Example: read from a JSON file3cat scheduled_posts.json | python3 -c "4import json, sys5from datetime import datetime, timezone6posts = json.load(sys.stdin)7now = datetime.now(timezone.utc).isoformat()8due = [p for p in posts if p['scheduled_time'] <= now and p['status'] == 'pending']9print(json.dumps(due, indent=2))10"Pro tip: Include a max_retries field in each post record. If a post fails with RATELIMIT, increment retries and re-schedule for 15 minutes later rather than dropping it.
Expected result: A list of post objects due for submission, ordered by scheduled time.
Submit Post via POST /api/submit
Why: The actual posting step — the API has no scheduled delivery, so you submit at the exact moment you want the post to appear.
POST to /api/submit with kind, sr (subreddit name), title, and either text (for self posts) or url (for link posts). Always check the JSON body for errors after getting a 200 response — Reddit embeds RATELIMIT and ALREADY_SUB errors in the success body. On ALREADY_SUB, the URL was already submitted to that subreddit; either use kind=self or a different URL.
1curl -X POST https://oauth.reddit.com/api/submit \2 -H 'Authorization: Bearer YOUR_ACCESS_TOKEN' \3 -H 'User-Agent: script:com.example.scheduler:v1.0 (by /u/yourusername)' \4 -H 'Content-Type: application/x-www-form-urlencoded' \5 -d 'kind=self&sr=python&title=Weekly+Discussion&text=Share+your+tips+this+week&nsfw=false'Pro tip: Set resubmit=true to allow link re-submission. Without this, re-posting a URL that was previously deleted returns ALREADY_SUB. Reddit still shows 'already submitted' warnings to users, but the post goes through.
Expected result: On success: json.errors is empty and json.data contains id (the post ID like 'abc123') and url (the full Reddit post URL).
Apply Flair if Required and Log Result
Why: Many subreddits require flair on posts — posts without required flair may be auto-removed by AutoModerator within seconds of posting.
If the subreddit requires flair, call GET /r/{subreddit}/api/link_flair_v2 first to get available flair template IDs, then include flair_id in the submit call or follow up with POST /r/{subreddit}/api/flairselect. Finally, update your post queue record with the Reddit post ID, URL, and posted timestamp.
1# Get available flair for a subreddit2curl -X GET 'https://oauth.reddit.com/r/python/api/link_flair_v2' \3 -H 'Authorization: Bearer YOUR_ACCESS_TOKEN' \4 -H 'User-Agent: script:com.example.scheduler:v1.0 (by /u/yourusername)'Pro tip: Cache subreddit flair lists locally — they rarely change and fetching them on every post wastes quota. Refresh the cache once per day.
Expected result: Post is visible in the subreddit with the correct flair applied. Queue record is updated with status=posted and the Reddit post ID.
Complete working code
This complete scheduler polls a SQLite queue every minute, submits posts that are due, handles RATELIMIT and ALREADY_SUB errors gracefully, and updates the queue with results. It refreshes the OAuth token automatically and logs all activity.
1import requests2import sqlite33import time4import os5import logging6from datetime import datetime, timedelta, timezone78logging.basicConfig(level=logging.INFO, format='%(asctime)s %(levelname)s %(message)s')9log = logging.getLogger('scheduler')1011USER_AGENT = f"python:com.example.scheduler:v1.0 (by /u/{os.environ['REDDIT_USERNAME']})"12token_cache = {'token': None, 'expires_at': datetime.now(timezone.utc)}1314def get_headers():15 if datetime.now(timezone.utc) >= token_cache['expires_at'] - timedelta(minutes=5):16 r = requests.post(17 'https://www.reddit.com/api/v1/access_token',18 auth=requests.auth.HTTPBasicAuth(19 os.environ['REDDIT_CLIENT_ID'], os.environ['REDDIT_CLIENT_SECRET']20 ),21 data={'grant_type': 'password',22 'username': os.environ['REDDIT_USERNAME'],23 'password': os.environ['REDDIT_PASSWORD']},24 headers={'User-Agent': USER_AGENT}25 )26 r.raise_for_status()27 data = r.json()28 token_cache['token'] = data['access_token']29 token_cache['expires_at'] = datetime.now(timezone.utc) + timedelta(seconds=data['expires_in'])30 return {'Authorization': f"Bearer {token_cache['token']}", 'User-Agent': USER_AGENT}3132def get_due_posts(db):33 now = datetime.now(timezone.utc).isoformat()34 cur = db.execute(35 "SELECT * FROM scheduled_posts WHERE scheduled_time <= ? AND status = 'pending' ORDER BY scheduled_time ASC",36 (now,)37 )38 cur.row_factory = sqlite3.Row39 return [dict(row) for row in cur.fetchall()]4041def submit_post(headers, post):42 payload = {'kind': post['kind'], 'sr': post['subreddit'],43 'title': post['title'], 'nsfw': False, 'resubmit': True}44 if post['kind'] == 'self':45 payload['text'] = post.get('text', '')46 else:47 payload['url'] = post['url']48 if post.get('flair_id'):49 payload['flair_id'] = post['flair_id']50 r = requests.post('https://oauth.reddit.com/api/submit', headers=headers, data=payload)51 r.raise_for_status()52 result = r.json()53 errors = result.get('json', {}).get('errors', [])54 if errors:55 raise Exception(f"{errors[0][0]}: {errors[0][1]}")56 return result['json']['data']5758def run_scheduler(db_path='posts.db'):59 db = sqlite3.connect(db_path)60 log.info('Scheduler started')61 while True:62 try:63 posts = get_due_posts(db)64 for post in posts:65 try:66 headers = get_headers()67 data = submit_post(headers, post)68 db.execute(69 "UPDATE scheduled_posts SET status='posted', reddit_post_id=? WHERE id=?",70 (data['id'], post['id'])71 )72 db.commit()73 log.info(f"Posted t3_{data['id']} to r/{post['subreddit']}: {data['url']}")74 time.sleep(2)75 except Exception as e:76 log.error(f"Failed post {post['id']}: {e}")77 db.execute(78 "UPDATE scheduled_posts SET status='failed', error_message=? WHERE id=?",79 (str(e), post['id'])80 )81 db.commit()82 except Exception as e:83 log.error(f'Scheduler loop error: {e}')84 time.sleep(60)8586if __name__ == '__main__':87 run_scheduler()Error handling
errors: [["RATELIMIT", "you are doing that too much. try again in 9 minutes.", "ratelimit"]]Per-subreddit posting frequency limit. Posting too many times within a short window triggers this. New accounts with low karma hit it more often.
Parse json.errors on every submit response. Extract the wait time from the error message and reschedule the post for after that duration. Do not retry immediately.
Reschedule the post for now + (extracted minutes + 2) minutes. Max 3 retries before marking as failed.
errors: [["ALREADY_SUB", "that link has already been submitted", "url"]]The URL you're trying to post as a link has already been submitted to that subreddit. Reddit prevents duplicate link posts within a subreddit.
Use kind=self instead and include the URL in the text body, or append a unique query parameter to the URL if you control it. Set resubmit=true to suppress this for re-submissions of your own deleted posts.
No retry — change the post type or URL and resubmit.
SUBREDDIT_NOTALLOWED or USER_REQUIREDThe account doesn't have permission to post in this subreddit — may require minimum karma, account age, or moderator approval for the bot.
Check the subreddit's posting requirements. Some require a minimum karma threshold or account age. Consider posting in a development/test subreddit first.
No retry — fix the account permissions issue.
Too Many RequestsGlobal OAuth rate limit exceeded (60 RPM). Too many API calls across all endpoints within the rate limit window.
Check X-Ratelimit-Remaining and X-Ratelimit-Reset headers. Reduce request frequency and add delays between API calls.
Wait until X-Ratelimit-Reset (seconds from now), then retry with exponential backoff.
Rate Limits for Reddit API
| Scope | Limit | Window |
|---|---|---|
| Global (with OAuth) | 60 requests | per minute |
| Global (without OAuth) | 10 requests | per minute |
| Per-subreddit post frequency | Variable — enforced via RATELIMIT in response body | Rolling (minutes to hours) |
1import time2import requests34def submit_with_retry(headers, payload, max_retries=3):5 for attempt in range(max_retries):6 r = requests.post('https://oauth.reddit.com/api/submit', headers=headers, data=payload)7 if r.status_code == 429:8 reset = int(r.headers.get('X-Ratelimit-Reset', 60))9 print(f'HTTP 429 — waiting {reset}s')10 time.sleep(reset + 1)11 continue12 r.raise_for_status()13 errors = r.json().get('json', {}).get('errors', [])14 if errors and errors[0][0] == 'RATELIMIT':15 wait = min(60 * (2 ** attempt), 600)16 print(f'RATELIMIT — waiting {wait}s (attempt {attempt+1})')17 time.sleep(wait)18 continue19 return r.json()20 raise Exception('Max retries exceeded')- Always use OAuth2 — unauthenticated posting is not supported and drops to 10 RPM anyway
- Parse json.errors in EVERY /api/submit response — HTTP 200 does not mean success on Reddit
- Add 2+ second delays between consecutive posts to the same subreddit
- Monitor X-Ratelimit-Remaining and pause when it approaches zero
- Schedule posts at least 1 minute apart to stay well within Reddit's anti-spam detection
Security checklist
- Store REDDIT_CLIENT_ID, REDDIT_CLIENT_SECRET, USERNAME, and PASSWORD in environment variables
- Use a dedicated bot account separate from any personal Reddit account
- Set a descriptive User-Agent with your contact info as required by Reddit's API terms
- Implement automatic token refresh before the 1-hour expiry to prevent mid-schedule failures
- Log all post attempts with timestamps, subreddits, and outcomes for audit trail
- Validate post content before submission to ensure it complies with target subreddit rules
- Never store Reddit passwords or tokens in source control or config files
Automation use cases
Weekly Community Discussion Posts
beginnerAutomatically post recurring weekly discussion threads to a community subreddit at consistent times without manual effort.
Multi-Subreddit Content Distribution
intermediateSchedule the same content (with subreddit-appropriate titles) to be posted across multiple relevant subreddits with time delays between each.
News or Product Update Announcements
intermediatePost new blog articles, product releases, or changelogs to relevant subreddits automatically when they're published in your CMS.
Drip Campaign for Product Launches
advancedSchedule a series of posts building up to a product launch across multiple subreddits, with analytics tracking engagement per post.
No-code alternatives
Don't want to write code? These platforms can automate the same workflows visually.
Zapier
Free tier (5 Zaps); Starter from $19.99/monthZapier's Reddit module supports creating new posts triggered by events in other apps (RSS feeds, Google Sheets rows, form submissions). Scheduling support requires pairing with Zapier's Schedule trigger.
- + No code needed
- + Many trigger options (RSS, Sheets, webhooks)
- + Reliable delivery
- - No native post scheduling with specific times
- - Task-based pricing adds up at scale
- - Limited error handling for Reddit-specific errors
Make
Free tier; Core from $9/monthMake's Reddit module can create posts with richer filtering and scheduling options than Zapier, including time-based scheduling with the Schedule module.
- + Better scheduling support than Zapier
- + Visual flow editor
- + More flexible filtering
- - Reddit module has limited operations
- - Per-operation pricing
- - No RATELIMIT body error handling
n8n
Free self-hosted; Cloud from €20/monthn8n's Reddit node supports submitting posts with a Cron trigger for scheduling. The HTTP Request node can be used for any Reddit API operation not covered natively.
- + Self-hostable (free)
- + Cron-based scheduling built-in
- + Full HTTP flexibility for advanced cases
- - More technical setup required
- - Reddit node limited to basic operations
- - Self-hosted maintenance burden
Best practices
- Parse json.errors in every /api/submit response — HTTP 200 does not guarantee the post was created
- Use fullname-based pagination (after=t3_xxx) for listing posts, never page numbers
- Store flair template IDs in your queue database rather than fetching them on every post
- Schedule posts at different times per subreddit to avoid looking like spam across communities
- Test with a private or development subreddit before going live with a production scheduler
- Check subreddit posting rules and required flair before adding it to your scheduler queue
- Implement a dry-run mode that logs what would be posted without actually calling the API
- Monitor your post's karma in the first hour — low initial engagement may indicate it was caught by spam filters
Ask AI to help
Copy one of these prompts to get a personalized, working implementation.
I'm building a Reddit post scheduler in Python using the OAuth2 API. I POST to https://oauth.reddit.com/api/submit and need to handle Reddit's unusual error pattern where RATELIMIT and ALREADY_SUB errors come back in the JSON body of a 200 OK response, not as HTTP error codes. Help me write a submit_post() function that: 1) properly checks json.errors after a 200 response, 2) raises specific exceptions for RATELIMIT (with the wait duration extracted from the error message) and ALREADY_SUB, 3) retries RATELIMIT after the indicated wait time, and 4) returns the post ID and URL on success.
Build a Reddit post scheduling dashboard. Features needed: a form to add scheduled posts (subreddit, title, kind: text/link, body text or URL, target datetime, optional flair), a calendar/timeline view showing scheduled posts, a status board showing pending/posted/failed posts with their Reddit URLs and karma scores, and a settings panel for API credentials. Use a clean dark UI with Reddit's orange accent color. The backend should be a Node.js API that manages a SQLite queue and submits posts via the Reddit API.
Frequently asked questions
Is the Reddit API free for scheduling posts?
Yes — the free tier supports 60 requests per minute with OAuth2, which is more than enough for scheduling. You can post to multiple subreddits daily without any cost. Commercial use at high scale requires an enterprise agreement at $12,000/month, but personal schedulers and small tools are completely free.
Does Reddit have a native scheduling feature in the API?
No. Reddit's API has no scheduled_time or publish_at parameter on /api/submit. You build scheduling yourself: store posts with their target times, run a polling loop (or cron job) that checks every minute, and call /api/submit when a post becomes due.
What happens when I hit the rate limit?
Two separate rate limits apply: global HTTP 429 when you exceed 60 RPM overall, and a per-subreddit RATELIMIT embedded in a 200 OK response body (errors: [["RATELIMIT", "try again in N minutes", "ratelimit"]]). Parse json.errors on every response. The per-subreddit limit is the more common issue for schedulers posting to active communities.
Why is my post not appearing in the subreddit after a successful API response?
Most likely: 1) AutoModerator removed it for missing required flair — check if the subreddit requires flair and add flair_id to your submit call. 2) The account is too new or has insufficient karma for that subreddit. 3) The post was caught by Reddit's spam filter and is pending manual review by moderators.
Can I schedule posts using PRAW (Python Reddit API Wrapper)?
Yes. PRAW simplifies authentication and posting with subreddit.submit(title, selftext=...) for text posts and subreddit.submit(title, url=...) for links. Install with pip install praw. PRAW handles rate limiting automatically and raises praw.exceptions.RedditAPIException for errors, making it easier to catch RATELIMIT and ALREADY_SUB errors.
What is the maximum number of posts I can schedule per day?
Reddit doesn't publish a hard daily posting limit per account, but the per-subreddit RATELIMIT throttles frequent posters. In practice, posting more than 4-6 times per day to the same subreddit triggers throttling. Spread posts across different subreddits and use a minimum 2-hour gap between posts to the same community.
Can RapidDev build a custom Reddit scheduling tool for my business?
Yes. RapidDev has built 600+ apps including content scheduling and social media automation platforms. We can build a production-ready Reddit scheduler with a full management dashboard, multi-subreddit support, flair handling, and analytics. Contact us for a free consultation.
Need this automated?
Our team has built 600+ apps with API automations. We can build this for you.
Book a free consultation