+ Tutorial
Migrate PRAW to FetchLayer: praw.ini and praw.Reddit()
Keep your PRAW code after Reddit's API closes. Copy-paste praw.ini and praw.Reddit() configs, Async PRAW, plain HTTP, costs per call, and error fixes.
Written by Alex P.
- PRAW
- praw.ini
- PRAW migration
- PRAW alternative
- asyncpraw
- reddit API python
- PRAW without reddit app
- reddit oauth_url
Reddit is closing its public Data API: new access requests stop on October 31, 2026, and public access ends in March 2027 (Reddit’s announcement; the background is in Is PRAW Shutting Down?). If your PRAW code reads Reddit, you don’t have to rewrite it. Point PRAW at FetchLayer’s PRAW-compatible API and the same code keeps running.
This guide has the copy-paste configs, what each PRAW call costs, and fixes for the errors you might hit. Rolling it out across a production codebase? See Keep Your PRAW Code for the audit and deployment checklist.
Step 1: Get an API key
Sign up and create a key. It looks like ss-.... You get 30 free requests and don’t need a card. You don’t need a Reddit app, a Reddit account or a Reddit password.
Step 2: Point PRAW at FetchLayer
PRAW takes settings from two places, with one important split:
- Credentials (
client_id,client_secret,user_agent,username,password) can come frompraw.ini, the constructor, orpraw_*environment variables. Environment variables overridepraw.ini. - The host (
oauth_url,reddit_url) comes only frompraw.inior the constructor. Environment variables alone can’t change where PRAW sends requests.
Pick whichever of these three fits how you already deploy.
Option A: credentials in env vars, host in a 3-line praw.ini (no code change)
This is the setup most teams prefer: the secret stays in your environment or secret store, and the file holds no credentials, so it can be committed.
export praw_client_id=ss-your-fetchlayer-key
export praw_client_secret=unused
export praw_user_agent=my-app
Then create praw.ini in your user configuration directory (~/.config/praw.ini on Linux and macOS, %APPDATA%\praw.ini on Windows):
[DEFAULT]
oauth_url=https://praw.fetchlayer.dev
reddit_url=https://praw.fetchlayer.dev
What each piece does:
praw_client_id: your FetchLayer key, the only credential. The key also works asclient_secretorpassword, so use whichever your deployment already sets.praw_client_secret: PRAW requires a value; any string works.praw_user_agent: any value works.oauth_urlandreddit_url: where PRAW sends requests. Both point at FetchLayer.
Check for an old
praw_client_id. Because environment variables win overpraw.ini, a deployment that still exports your old Reddit client ID will keep sending it, even ifpraw.inisays otherwise. Replace the variable’s value with your FetchLayer key.
Option B: everything in praw.ini (no code change)
If you’d rather keep it all in one file, in the same user configuration directory:
[DEFAULT]
oauth_url=https://praw.fetchlayer.dev
reddit_url=https://praw.fetchlayer.dev
client_id=ss-your-fetchlayer-key
client_secret=unused
Option C: praw.Reddit(...) (one line)
If you build the client in code:
import praw
reddit = praw.Reddit(
client_id="ss-your-fetchlayer-key",
client_secret="unused",
user_agent="my-app",
oauth_url="https://praw.fetchlayer.dev",
reddit_url="https://praw.fetchlayer.dev",
)
Remove username and password if you had them; they aren’t needed.
The endpoint-override warning
If praw.ini sits in your working directory (next to the script) and sets oauth_url or reddit_url, PRAW prints a security warning, because a planted config file there could redirect your credentials. Avoid it in either of these ways:
- put the file in the user configuration directory (
~/.config/praw.ini, or%APPDATA%\praw.inion Windows), as above; - or set
PRAW_ALLOW_ENDPOINT_OVERRIDE=1in the environment if the file has to live next to your code.
One host for everything
If your network only allows one API host, https://api.fetchlayer.dev/v1/praw/ works the same way. Keep the trailing slash on oauth_url, or PRAW drops the path:
oauth_url=https://api.fetchlayer.dev/v1/praw/
reddit_url=https://api.fetchlayer.dev/v1/praw/
Step 3: Run your existing code
Nothing else changes:
import praw
reddit = praw.Reddit(user_agent="my-app")
# Subreddit listings
for submission in reddit.subreddit("python").top(time_filter="week", limit=25):
print(submission.score, submission.num_comments, submission.title)
# A post with its comment tree
submission = reddit.submission(url="https://www.reddit.com/r/Python/comments/1xehnro/")
submission.comments.replace_more(limit=32)
for comment in submission.comments.list():
print(" " * comment.depth, comment.author, comment.body[:80])
# Search
for submission in reddit.subreddit("all").search("fastapi vs django", limit=25):
print(submission.subreddit, submission.title)
# A user's history
for comment in reddit.redditor("spez").comments.new(limit=25):
print(comment.subreddit, comment.body[:80])
Async PRAW
Async PRAW reads the same praw.ini:
import asyncio
import asyncpraw
async def main():
async with asyncpraw.Reddit(user_agent="my-app") as reddit:
subreddit = await reddit.subreddit("python")
async for submission in subreddit.hot(limit=25):
print(submission.score, submission.title)
asyncio.run(main())
Step 4: Check it works
python -c "import praw; print(next(praw.Reddit(user_agent='test').subreddit('python').hot(limit=1)).title)"
If it prints a title, you’re done. If it doesn’t, see Troubleshooting.
What works, and what it costs
Credits are the same as our REST API. One credit is one page of results.
| PRAW call | Cost |
|---|---|
subreddit("x").hot() / .new() / .top() / .rising() / .controversial() | 1 credit per 25 posts |
subreddit("all") and front-page listings | 1 credit per 25 posts |
subreddit("x").search(...) | 1 credit per page of results (7 to 18 posts a page, so 25 results is usually 3) |
reddit.submission(id) / submission(url=...) with .comments | 1 credit (post and comment tree) |
submission.comments.replace_more(limit=n) | 1 credit per “load more comments” expanded |
submission.comments.list() | free |
reddit.info(fullnames=[...]) | 1 credit per request (up to 100 ids) |
reddit.info(subreddits=[...]) | 1 credit per community |
Subreddit attributes (subscribers, created_utc, …) | 1 credit |
Redditor attributes (link_karma, comment_karma, …) | 1 credit |
redditor("x").submissions.* / .comments.* (new, hot, top) | 1 credit per 25 items |
subreddit.stream.submissions() | 4 credits for the first poll, then 1 credit per poll |
Two habits that save credits:
- Pass the
limityou need. PRAW asks for 100 items per page by default, which is 4 credits.limit=25is 1. - Bound
replace_more().replace_more(limit=None)on a huge thread expands every “load more”, and each one is billed. PRAW’s default stops after 32; choose a limit that fits.
At pay-as-you-go rates ($1.99 per 1,000 credits), a 100-post listing costs under a cent. Plans are on the pricing page.
Read-only
These answer 403 (prawcore.exceptions.Forbidden):
- voting, submitting, commenting, editing, saving and subscribing;
- messages and modmail;
- moderation;
- anything about a signed-in account (
reddit.user.me(), the inbox, preferences).
Wikis, multireddits and subreddit.stream.comments() aren’t available, and neither are the user overview listing, controversial on user listings, or reddit.info(url=...). Use reddit.submission(url=...) instead of reddit.info(url=...).
Known differences from Reddit’s API
- Comments arrive as Reddit shows them in place, about 4 levels deep, with “load more comments” and “continue this thread” points as
MoreComments.replace_more()loads them. - Comments come in Reddit’s default order;
comment_sortandcomment_limitare not applied. - Some fields are sent empty:
upvote_ratioisNone, awards are0or[],editedon comments isFalse, and a community’s sidebar description andaccounts_activeareNone. subscribersisNonefor NSFW communities. A redditor’screated_utcis the day the account was created, at 00:00 UTC.
The complete list is in the PRAW documentation.
Not Python? Plain HTTP works too
The PRAW-compatible API answers ordinary HTTP with Reddit-shaped JSON (a Listing with t3 children and an after cursor):
curl -H "Authorization: Bearer ss-your-fetchlayer-key" \
"https://praw.fetchlayer.dev/r/python/hot?limit=100"
That’s 4 credits for 100 posts. It works from any language that has an HTTP client. If you’d rather have flat, documented JSON than Reddit’s nested format, use the REST API instead. Node.js users: see PRAW Alternative for JavaScript.
Troubleshooting
| What you see | Fix |
|---|---|
ResponseException: received 401 HTTP response on the first request | The key is wrong or was revoked. Check client_id. |
| A warning about endpoint overrides | Your praw.ini sits next to the script. Move it to the user config directory, or set PRAW_ALLOW_ENDPOINT_OVERRIDE=1. |
| Requests still go to reddit.com | oauth_url / reddit_url were set as environment variables, which PRAW ignores. Put them in praw.ini or the constructor. |
Requests fail with api.fetchlayer.dev/v1/praw | Add the trailing slash to oauth_url: https://api.fetchlayer.dev/v1/praw/. |
Read timed out on a very slow network | PRAW waits 16 seconds per response and ignores its own timeout option. Set the environment variable PRAWCORE_TIMEOUT=120. |
| 403 with a reason about credits | Out of credits. Top up or move to a plan. |
403 on submit, reply, upvote | Writes aren’t supported; the API is read-only. |
| 429 | Your plan’s rate limit. PRAW waits for Retry-After automatically. |
| 503 | A brief outage. PRAW retries by itself. |
Frequently Asked Questions
Do I need a Reddit account or Reddit API app?
No. Your FetchLayer key is the only credential. You don’t need username, password or a Reddit app.
Will my PRAW code need changes?
Not for reading. Listings, submissions, comment trees, search, redditors and communities return the same PRAW objects. Code that posts, votes or moderates gets a 403.
Does replace_more(limit=None) return every comment?
It expands every “load more” it finds, one credit each, and loads essentially the whole thread, large ones included. The count can sit a little below num_comments, because Reddit counts removed comments it doesn’t show. A big thread can mean a few dozen expansions, so set a limit if you don’t need every reply.
Does it work with Async PRAW?
Yes, with the same praw.ini.
Can I switch back to Reddit’s API?
Yes. Delete the oauth_url and reddit_url lines and PRAW goes back to Reddit’s defaults. Your code doesn’t change either way.