Skip to main content

reddigraph

Python client for ReddiGraph, the Reddit community intelligence API. Sync and async, fully typed, with retries and cursor pagination handled for you.

pip install reddigraph

Usage

from reddigraph import ReddiGraph

with ReddiGraph(api_key="rg_live_…") as api:
    page = api.subreddit_feed("technology", sort="top", time="week")
    for post in page.data:
        print(post.score, post.title)

api_key falls back to REDDIGRAPH_API_KEY, and base_url to REDDIGRAPH_BASE_URL and then to https://api.reddigraph.com — so only a self-hosted or staging deployment needs to pass it. The key is sent as Authorization: Bearer ….

Keep one client for the lifetime of your process — it reuses connections.

Async

AsyncReddiGraph mirrors ReddiGraph method for method:

from reddigraph import AsyncReddiGraph

async with AsyncReddiGraph(api_key="rg_live_…") as api:
    user = await api.user("spez")

Pagination

paginate follows the after cursor and yields items, not pages:

for post in api.paginate(api.subreddit_feed, "technology", sort="new", max_pages=5):
    print(post.title)

# async
async for post in api.paginate(api.subreddit_feed, "technology", max_pages=5):
    ...

Without max_pages it stops when the API reports no next page. Every other argument is forwarded to each call unchanged.

Errors

from reddigraph import NotFound, RateLimited, ReddiGraphError

try:
    api.post("t3_missing")
except NotFound:
    ...
except RateLimited as exc:
    time.sleep(exc.retry_after)
except ReddiGraphError as exc:
    print(exc.code, exc.status_code, exc.details)

InvalidInput, AuthenticationError, NotFound, RateLimited, UpstreamError and TransportError all derive from ReddiGraphError. When the server sends a request id it is kept in exc.details["request_id"] — quote it when reporting a problem.

Arguments that could not produce a valid request — an empty batch, a name with a slash in it — raise ValueError before anything is sent.

Retries

429, 500, 502, 503, 504 and connection failures are retried max_retries times (default 2). A Retry-After is obeyed as sent; everything else backs off exponentially with jitter, up to max_backoff seconds (default 20). Other 4xx responses are never retried.

A POST is the exception to the 5xx rule: a server error can mean the write landed and only the answer was lost, so replaying it would queue a second export or create a second monitor. Those are retried on a 429 — which is refused before any work is done — but never on a 5xx.

Methods

Method Returns
search_posts(query, subreddit, sort, time, after) PostList
search_subreddits(query, after) SubredditSearchList
search_users(query, after) UserSearchList
search_comments(query, after) CommentSearchList
trending_searches() TrendingList
subreddit_feed(subreddit, sort, time, after) PostList
popular_feed(sort, after) PostList
discover_communities(sort, after) Discovery
subreddit_about(subreddit) Subreddit
subreddit_rules(subreddit) RuleList
subreddit_styles(subreddit) SubredditStyles
subreddit_taxonomy(subreddit) TaxonomyList
subreddit_highlights(subreddit) HighlightList
subreddit_wiki_page(subreddit, page) WikiPage
post(post_id) Post
posts(post_ids) PostList
post_comments(post_id, sort, limit) CommentList
user(username) User
user_posts(username, sort, after) PostList
user_comments(username, sort, after) CommentList
user_trophies(username) TrophyList
analyze_subreddit(subreddit, window, deep) SubredditIntelligence
analyze_conversation(post_id, sort, limit) ConversationIntelligence
related_subreddits(subreddit, window, limit) RelatedSubreddits
compare_subreddits(subreddits, window) SubredditComparison
trends(subreddits, window, baseline, ...) Trends
subreddit_history(subreddit, days) HistorySeries
post_history(post_id, days) HistorySeries
topic_history(topic, days) HistorySeries
create_monitor(kind, name, ...) Monitor
monitors() / monitor(id) MonitorList / Monitor
update_monitor(id, **changes) Monitor
delete_monitor(id)
monitor_events(id, since, limit) MonitorEventList
add_webhook(id, url) Webhook
webhooks(id) / delete_webhook(id, hook_id) WebhookList / —
monitor_deliveries(id, limit) DeliveryList
create_export(kind, fmt, params) Export
exports() / export(id) ExportList / Export
download_export(id) bytes
resolve_url(url) ResolvedUrl
health() Health
ready() Health

Names accept the r/ and u/ prefixes; post ids accept t3_abc123 or abc123.

Intelligence

Four methods answer a question rather than return a page:

week = api.analyze_subreddit("kubernetes", window="7d")

# Read the sample before the numbers.
if week.sample.coverage < 1.0:
    print(f"only {week.sample.coverage:.0%} of the window was reached")

print(week.activity.posts_per_hour, week.engagement.median_post_score)
for topic in week.content.emerging_topics:
    print(topic.topic, topic.growth_ratio, topic.unique_authors)

Every one of them carries window, generated_at and sample. A metric that could not be measured is None next to a stated reason — never 0. Percentiles are None below five observations; growth_ratio is None for a topic with no baseline, which is what is_new is for.

analyze_subreddit(deep=False) skips op_reply_rate, the one metric costing an extra call per sampled post.

related_subreddits scores candidates on several signals and tells you which ones it could measure:

for row in api.related_subreddits("kubernetes").data:
    print(row.subreddit, row.similarity, row.measured_signals)

A similarity resting on one signal is a weaker claim than the same number resting on four. Aggregate audience overlap is withheld below 25 distinct authors on either side, with the reason in row.signals.audience_overlap_withheld_because.

Full definitions: docs/METRICS.md.

Monitors and webhooks

monitor = api.create_monitor("keyword", name="Brand watch", query="reddigraph")
hook = api.add_webhook(monitor.id, "https://example.com/hooks/reddigraph")
print(hook.secret)   # shown once, here, and never again — store it now

Verify each delivery with the shipped helper rather than reimplementing it. The two easy mistakes — comparing signatures with ==, and ignoring the timestamp — are both silent, and both are handled below:

from reddigraph import verify_webhook

@app.post("/hooks/reddigraph")
async def hook(request):
    raw = await request.body()      # raw bytes; a re-encoded dict will not match
    if not verify_webhook(
        secret=SECRET,
        body=raw,
        signature=request.headers["X-ReddiGraph-Signature"],
        timestamp=request.headers["X-ReddiGraph-Timestamp"],
    ):
        return Response(status_code=401)

Events carry a deterministic id. Deduplicate on it — the same id is never sent twice by the same monitor, and it is stable across our redeploys.

Exports

job = api.create_export("subreddit_posts", "parquet", {"subreddit": "python"})
while api.export(job.id).status in {"queued", "running"}:
    time.sleep(5)

done = api.export(job.id)
if done.truncated:
    print(f"{done.rows} of {done.rows_available} rows — your plan's ceiling applied")

The file is available for 24 hours; the job record outlives it, so arriving late gets an explanation rather than a 404. download_export fetches it in one piece:

open("posts.parquet", "wb").write(api.download_export(job.id))

For a dataset too large to hold in memory, stream done.download_url yourself instead.

Notes

  • Comment lists are flat, ordered depth-first: rebuild the tree from depth and parent_id, and use has_more to spot a branch Reddit truncated.
  • discover_communities returns no posts — recommended communities and topic lists only. It replaces the old explore_feed, which the name misdescribed.
  • Search results are lighter than the dedicated endpoints, and they say so in their own types: UserSearchResult and SubredditSearchResult document every field the pane does not return. A false on one of those means unknown, not no — fetch user() or subreddit_about() for the real value.
  • Unknown fields are preserved, so a newer server cannot break an older client.
  • posts() is capped by your plan, not by the SDK: 10 ids on basic, 25 on pro, 50 on ultra, 100 on mega. Over the ceiling the server answers InvalidInput with plan, limit and received in exc.details.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

reddigraph-1.0.0.tar.gz (29.2 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

reddigraph-1.0.0-py3-none-any.whl (32.7 kB view details)

Uploaded Python 3

File details

Details for the file reddigraph-1.0.0.tar.gz.

File metadata

  • Download URL: reddigraph-1.0.0.tar.gz
  • Upload date:
  • Size: 29.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/7.0.0 CPython/3.14.2

File hashes

Hashes for reddigraph-1.0.0.tar.gz
Algorithm Hash digest
SHA256 681fe2b22bf325e8de375713db37a5de5b92062f930be84cb774c17c4762abce
MD5 8734aea74172d7ba3044f57deefa5bdd
BLAKE2b-256 44320004370bf242137cff63e97a97eb355d0c73d0c60f205676463a87877608

See more details on using hashes here.

File details

Details for the file reddigraph-1.0.0-py3-none-any.whl.

File metadata

  • Download URL: reddigraph-1.0.0-py3-none-any.whl
  • Upload date:
  • Size: 32.7 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/7.0.0 CPython/3.14.2

File hashes

Hashes for reddigraph-1.0.0-py3-none-any.whl
Algorithm Hash digest
SHA256 c985b54eac7a915d7c47016ff46581f9bc31fa1ad5c25e5b9f7086b5f00b4610
MD5 cd4f2c80a81d557b88cc2b4a6f57e205
BLAKE2b-256 cf5ab247e0553325a1b3c2c97adf0952554354f6f90c82d393010172a61aa3fc

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page