Reddit

Reddit hosts millions of media posts across thousands of subreddits. The xanax Reddit and AsyncReddit clients provide typed, paginated access to subreddit media feeds.

Note

Reddit requires OAuth2 client credentials (script app) for all API access. Register an application at reddit.com/prefs/apps and select the “script” type to obtain a client ID and secret.

Authentication

Pass your credentials directly or use environment variables:

from xanax.sources.reddit import Reddit

# Explicit credentials
client = Reddit(
    client_id="your-client-id",
    client_secret="your-client-secret",
    user_agent="python:myapp/1.0 (by u/yourusername)",
)

# From environment variables:
# REDDIT_CLIENT_ID, REDDIT_CLIENT_SECRET, REDDIT_USER_AGENT
client = Reddit()

Tokens are fetched automatically using the OAuth2 client_credentials flow and cached for up to 3600 seconds. Refresh is transparent — no action required.

Fetching media

iter_media() iterates over media posts (images, GIFs, videos) from a subreddit:

from xanax.sources.reddit.params import RedditParams
from xanax.sources.reddit.enums import RedditSort

for post in client.iter_media(RedditParams(subreddit="EarthPorn")):
    print(post.id, post.media_type, post.url)

Filter by media type

from xanax.enums import MediaType

# Images only
for post in client.iter_media(RedditParams(
    subreddit="EarthPorn",
    media_type=MediaType.IMAGE,
)):
    client.download(post, path=f"{post.id}.jpg")

Sort and time filter

from xanax.sources.reddit.enums import RedditSort, RedditTimeFilter

for post in client.iter_media(RedditParams(
    subreddit="pics",
    sort=RedditSort.TOP,
    time_filter=RedditTimeFilter.WEEK,
    limit=25,
)):
    print(post.title, post.score)

Downloading

download() returns raw bytes and optionally saves to disk:

post = next(client.iter_media(RedditParams(subreddit="EarthPorn")))

# Memory only
data: bytes = client.download(post)

# Save to disk
from pathlib import Path
client.download(post, path=Path(f"{post.id}.jpg"))

Note

Video posts (media_type=MediaType.VIDEO) download the video-only MP4 stream via fallback_url. Audio is a separate DASH stream — merging audio and video requires ffmpeg and is not handled by xanax.

Getting a single post

post = client.post("abc123")
if post:
    print(post.title, post.media_type, post.url)

Async client

import asyncio
from xanax.sources.reddit import AsyncReddit
from xanax.sources.reddit.params import RedditParams

async def main():
    async with AsyncReddit(
        client_id="your-client-id",
        client_secret="your-client-secret",
        user_agent="python:myapp/1.0 (by u/yourusername)",
    ) as client:
        async for post in client.aiter_media(RedditParams(subreddit="EarthPorn")):
            data = await client.download(post)

asyncio.run(main())

NSFW content

NSFW posts are excluded by default. To include them:

params = RedditParams(subreddit="pics", include_nsfw=True)

Rate limiting

Reddit enforces rate limits via X-Ratelimit-* response headers. Enable automatic retry:

client = Reddit(client_id="...", client_secret="...", user_agent="...", max_retries=3)

Post model reference

RedditPost contains:

Field

Type

Notes

id

str

Unique post ID

title

str

Post title

subreddit

str

Subreddit name

url

str

Direct media URL

media_type

MediaType

IMAGE, VIDEO, GIF, or ANY

permalink

str

Reddit page URL

score

int

Upvotes minus downvotes

upvote_ratio

float

Ratio of upvotes to total votes

author

str

Post author username

created_utc

datetime

Post creation time

is_nsfw

bool

NSFW flag

video_url

str | None

Fallback MP4 URL for video posts

gallery_index

int | None

Position within gallery (0-based)

See also