+ 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 from praw.ini, the constructor, or praw_* environment variables. Environment variables override praw.ini.
  • The host (oauth_url, reddit_url) comes only from praw.ini or 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 as client_secret or password, so use whichever your deployment already sets.
  • praw_client_secret: PRAW requires a value; any string works.
  • praw_user_agent: any value works.
  • oauth_url and reddit_url: where PRAW sends requests. Both point at FetchLayer.

Check for an old praw_client_id. Because environment variables win over praw.ini, a deployment that still exports your old Reddit client ID will keep sending it, even if praw.ini says 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.ini on Windows), as above;
  • or set PRAW_ALLOW_ENDPOINT_OVERRIDE=1 in 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 callCost
subreddit("x").hot() / .new() / .top() / .rising() / .controversial()1 credit per 25 posts
subreddit("all") and front-page listings1 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 .comments1 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:

  1. Pass the limit you need. PRAW asks for 100 items per page by default, which is 4 credits. limit=25 is 1.
  2. 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_sort and comment_limit are not applied.
  • Some fields are sent empty: upvote_ratio is None, awards are 0 or [], edited on comments is False, and a community’s sidebar description and accounts_active are None.
  • subscribers is None for NSFW communities. A redditor’s created_utc is 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 seeFix
ResponseException: received 401 HTTP response on the first requestThe key is wrong or was revoked. Check client_id.
A warning about endpoint overridesYour 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.comoauth_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/prawAdd the trailing slash to oauth_url: https://api.fetchlayer.dev/v1/praw/.
Read timed out on a very slow networkPRAW waits 16 seconds per response and ignores its own timeout option. Set the environment variable PRAWCORE_TIMEOUT=120.
403 with a reason about creditsOut of credits. Top up or move to a plan.
403 on submit, reply, upvoteWrites aren’t supported; the API is read-only.
429Your plan’s rate limit. PRAW waits for Retry-After automatically.
503A 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.