Reddit API documentation
The base URL is https://api.apixstar.com. Every endpoint is a GET that returns JSON. The Reddit object you want goes in the path, and query parameters only filter or page the result.
Authentication
Send an API key in the Authorization header of every request. Keys start with xstar_. You create them in the console, and each one is shown once.
Authorization: Bearer xstar_...
A key in the query string is not accepted. A revoked or unknown key returns INVALID_API_KEY and the request never reaches Reddit.
Endpoints
Name the community, user, post or comment in the path. Do not pass a Reddit URL or permalink as a parameter.
| Endpoint | Returns |
|---|---|
GET /reddit/v1/r/{community} | Community listing, hot by default |
GET /reddit/v1/r/{community}/{sort} | Listing sorted by new, hot, top, rising, or controversial |
GET /reddit/v1/r/{community}/comments/{id} | Post thread with comments |
GET /reddit/v1/r/{community}/search | Search inside one community |
GET /reddit/v1/u/{username} | User profile |
GET /reddit/v1/u/{username}/submitted | Posts submitted by a user |
GET /reddit/v1/posts/{id} | One post by id, with comments |
GET /reddit/v1/comments/{id} | One comment by id |
GET /reddit/v1/search | Search across Reddit |
Post and comment ids work with or without their t3_ and t1_ prefixes.
Responses and billing
A successful response has ok: true, the result in data and request details in meta. Read meta.billing for what the request cost and the balance that remains.
Reading a post with its comments, a comment or a user profile costs $0.0007 per request. A listing or search costs $0.0007 per 10 results returned, rounded up per page, so a full page of 100 results costs $0.007. A search that includes comments counts posts and comments together.
Empty pages and failed requests are free. New accounts start with $0.50. The pricing page lists the price of every endpoint.
You can send an optional X-Request-ID header. It is scoped to your account. Sending the same id again returns HTTP 409 without a second upstream request or charge, so a retry cannot bill you twice. Use a new id for each new request.
How to read the examples
Each endpoint below has a complete JSON example you can expand. It is based on a successful live request, with text, arrays and balances shortened or replaced by illustrative values. Each example shows one result.
data.postsis an array for listings and searches. A read returns one object indata.post,data.commentordata.user.idis the bare id andfullnameaddst3_for a post ort1_for a comment. Either form works in the read endpoints.createdAtis Unix time in seconds.meta.tsis Unix time in milliseconds.data.nextCursoris optional. When it is present, send it back ascursorwithout interpreting it. Stop when it is absent or null.- A post's
flairmay be null.commentCountis the total on the post, not the number of comments in this response.lockedandarchivedare optional booleans. Optionalmediaentries have atype(image or video) and aurl, and videos may addposterUrlandhlsUrl. meta.billing.resultCountcounts the posts and comments returned by a listing, or one resource for a read.billingUnitsis the number of units charged.unitPrice,chargedandbalanceRemainingare USD strings with four decimal places.pricingIdandversionidentify the pricing rule.
Communities
GET /reddit/v1/r/{community} lists posts in a community, hot first. Add /new, /hot, /top, /rising or /controversial to choose the order. The top listing covers the past week.
| Query | Required | Meaning |
|---|---|---|
limit | no | Page size, 1 to 100. The default is 10. |
cursor | no | The nextCursor value from the previous page. Pass it unchanged. |
minScore | no | Leave out posts below this score. |
maxAgeHours | no | Leave out posts older than this many hours. |
excludeAuthors | no | Author names to leave out, separated by commas. |
Filters apply to each page after it is fetched, so a page can hold fewer than limit results, or none, and still return nextCursor. Pass that value back unchanged to continue with the same filters. Stop when nextCursor is absent, not when a page is short or empty.
curl "https://api.apixstar.com/reddit/v1/r/typescript/new?limit=10" \ -H "Authorization: Bearer YOUR_API_KEY"
GET /reddit/v1/r/{community} response JSON
HTTP 200. The values are illustrative. Field names and structure match a tested response.
{
"ok": true,
"data": {
"posts": [
{
"id": "1wxjay5",
"fullname": "t3_1wxjay5",
"title": "Showcase Thread",
"body": "Example content.",
"author": "AutoModerator",
"community": "Python",
"url": "https://www.reddit.com/r/Python/comments/1wxjay5/showcase_thread/",
"permalink": "https://www.reddit.com/r/Python/comments/1wxjay5/showcase_thread/",
"score": 17,
"upvoteRatio": 0.96,
"commentCount": 39,
"createdAt": 1791129917,
"isRemoved": false,
"isSelf": true,
"stickied": true,
"flair": "Showcase",
"platform": "reddit"
}
],
"nextCursor": "t3_1wxjay5"
},
"meta": {
"method": "feed",
"path": "/reddit/v1/r/Python",
"ts": 1791549435096,
"billing": {
"pricingId": "community.hot",
"version": "usd-v2",
"resultCount": 1,
"billingUnits": 1,
"unitPrice": "0.0007",
"charged": "0.0007",
"balanceRemaining": "9.9993",
"currency": "USD"
}
}
}
GET /reddit/v1/r/{community}/{sort} response JSON
HTTP 200. The example uses new. The other sorts return the same shape, and meta.path and meta.billing.pricingId reflect the sort you chose.
{
"ok": true,
"data": {
"posts": [
{
"id": "1x16rfy",
"fullname": "t3_1x16rfy",
"title": "Friday Daily Thread: r/Python Meta and Free-Talk Fridays",
"body": "Example content.",
"author": "AutoModerator",
"community": "Python",
"url": "https://www.reddit.com/r/Python/comments/1x16rfy/friday_daily_thread_rpython_meta_and_freetalk/",
"permalink": "https://www.reddit.com/r/Python/comments/1x16rfy/friday_daily_thread_rpython_meta_and_freetalk/",
"score": 3,
"upvoteRatio": 1,
"commentCount": 0,
"createdAt": 1791504032,
"isRemoved": false,
"isSelf": true,
"stickied": true,
"flair": ":pythonLogo: Daily Thread",
"platform": "reddit"
}
],
"nextCursor": "t3_1x16rfy"
},
"meta": {
"method": "feed",
"path": "/reddit/v1/r/Python/new",
"ts": 1791549438295,
"billing": {
"pricingId": "community.new",
"version": "usd-v2",
"resultCount": 1,
"billingUnits": 1,
"unitPrice": "0.0007",
"charged": "0.0007",
"balanceRemaining": "9.9993",
"currency": "USD"
}
}
}
Users
GET /reddit/v1/u/{username} returns a user profile. GET /reddit/v1/u/{username}/submitted lists that user's posts, newest first, and takes the paging parameters. /posts is an alias for /submitted.
| Query | Required | Meaning |
|---|---|---|
limit | no | Page size, 1 to 100. The default is 10. |
cursor | no | The nextCursor value from the previous page. Pass it unchanged. |
curl "https://api.apixstar.com/reddit/v1/u/spez/submitted?limit=25" \ -H "Authorization: Bearer YOUR_API_KEY"
GET /reddit/v1/u/{username} response JSON
HTTP 200. The profile is in data.user. avatarUrl may be null.
{
"ok": true,
"data": {
"user": {
"username": "spez",
"totalKarma": 941030,
"linkKarma": 184536,
"commentKarma": 756494,
"createdAt": 1118030400,
"isSuspended": false,
"avatarUrl": "https://styles.redditmedia.com/t5_3k30p/styles/profileIcon_uj015iwx9s7g1.png?width=256&height=256&crop=256:256,smart&s=aa819b0964f6e3019e769d1cc8ede7318c3869a3",
"platform": "reddit"
}
},
"meta": {
"method": "read",
"path": "/reddit/v1/u/spez",
"ts": 1791549483437,
"billing": {
"pricingId": "read.user",
"version": "usd-v1",
"resultCount": 1,
"billingUnits": 1,
"unitPrice": "0.0007",
"charged": "0.0007",
"balanceRemaining": "9.9993",
"currency": "USD"
}
}
}
GET /reddit/v1/u/{username}/submitted response JSON
HTTP 200. Posts are in data.posts and paging uses data.nextCursor. The /posts alias has the same shape and price, and meta.path records the path you requested.
{
"ok": true,
"data": {
"posts": [
{
"id": "1vgbkge",
"fullname": "t3_1vgbkge",
"title": "Modernizing Reddit’s infrastructure with you",
"body": "Example content.",
"author": "spez",
"community": "u_spez",
"url": "https://www.reddit.com/r/u_spez/comments/1vgbkge/modernizing_reddits_infrastructure_with_you/",
"permalink": "https://www.reddit.com/r/u_spez/comments/1vgbkge/modernizing_reddits_infrastructure_with_you/",
"score": 349,
"upvoteRatio": 0.84,
"commentCount": 477,
"createdAt": 1785945836,
"isRemoved": false,
"isSelf": true,
"stickied": false,
"flair": null,
"platform": "reddit"
}
],
"nextCursor": "t3_1vgbkge"
},
"meta": {
"method": "feed",
"path": "/reddit/v1/u/spez/submitted",
"ts": 1791549487156,
"billing": {
"pricingId": "user.feed",
"version": "usd-v2",
"resultCount": 1,
"billingUnits": 1,
"unitPrice": "0.0007",
"charged": "0.0007",
"balanceRemaining": "9.9993",
"currency": "USD"
}
}
}
Posts and comments
GET /reddit/v1/posts/{id} returns one post with its comments. GET /reddit/v1/r/{community}/comments/{id} returns the same thread when you also know the community. GET /reddit/v1/comments/{id} returns a single comment.
| Query | Required | Meaning |
|---|---|---|
includeChildren | no | Set to false to read a post without its comments. |
commentLimit | no | Most comments to return with a post. The default is 50 and the maximum is 200. |
curl "https://api.apixstar.com/reddit/v1/posts/1abc2de?commentLimit=200" \ -H "Authorization: Bearer YOUR_API_KEY"
GET /reddit/v1/posts/{id} response JSON
HTTP 200. The post is in data.post. comments holds the comments returned with it, not the whole thread, and is left out when includeChildren=false. The example shows one comment.
{
"ok": true,
"data": {
"post": {
"id": "1wxjay5",
"fullname": "t3_1wxjay5",
"title": "Showcase Thread",
"body": "Example content.",
"author": "AutoModerator",
"community": "Python",
"url": "https://www.reddit.com/r/Python/comments/1wxjay5/showcase_thread/",
"permalink": "https://www.reddit.com/r/Python/comments/1wxjay5/showcase_thread/",
"score": 18,
"upvoteRatio": 1,
"commentCount": 39,
"createdAt": 1791129917,
"isRemoved": false,
"isSelf": true,
"stickied": true,
"flair": "Showcase",
"platform": "reddit",
"comments": [
{
"id": "pdtyaj5",
"fullname": "t1_pdtyaj5",
"body": "Example content.",
"author": "fykup",
"community": "Python",
"parentId": "t3_1wxjay5",
"postId": "t3_1wxjay5",
"score": 7,
"createdAt": 1791130136,
"permalink": "https://www.reddit.com/r/Python/comments/1wxjay5/showcase_thread/pdtyaj5/",
"platform": "reddit"
}
]
}
},
"meta": {
"method": "read",
"path": "/reddit/v1/posts/1wxjay5",
"ts": 1791549474887,
"billing": {
"pricingId": "read.post",
"version": "usd-v1",
"resultCount": 1,
"billingUnits": 1,
"unitPrice": "0.0007",
"charged": "0.0007",
"balanceRemaining": "9.9993",
"currency": "USD"
}
}
}
GET /reddit/v1/r/{community}/comments/{id} response JSON
HTTP 200. The same post shape is returned in data.post, with the optional comments. This endpoint bills as read.thread.
{
"ok": true,
"data": {
"post": {
"id": "1wxjay5",
"fullname": "t3_1wxjay5",
"title": "Showcase Thread",
"body": "Example content.",
"author": "AutoModerator",
"community": "Python",
"url": "https://www.reddit.com/r/Python/comments/1wxjay5/showcase_thread/",
"permalink": "https://www.reddit.com/r/Python/comments/1wxjay5/showcase_thread/",
"score": 16,
"upvoteRatio": 0.92,
"commentCount": 39,
"createdAt": 1791129917,
"isRemoved": false,
"isSelf": true,
"stickied": true,
"flair": "Showcase",
"platform": "reddit",
"comments": [
{
"id": "pdtyaj5",
"fullname": "t1_pdtyaj5",
"body": "Example content.",
"author": "fykup",
"community": "Python",
"parentId": "t3_1wxjay5",
"postId": "t3_1wxjay5",
"score": 7,
"createdAt": 1791130136,
"permalink": "https://www.reddit.com/r/Python/comments/1wxjay5/showcase_thread/pdtyaj5/",
"platform": "reddit"
}
]
}
},
"meta": {
"method": "read",
"path": "/reddit/v1/r/Python/comments/1wxjay5",
"ts": 1791549467917,
"billing": {
"pricingId": "read.thread",
"version": "usd-v1",
"resultCount": 1,
"billingUnits": 1,
"unitPrice": "0.0007",
"charged": "0.0007",
"balanceRemaining": "9.9993",
"currency": "USD"
}
}
}
GET /reddit/v1/comments/{id} response JSON
HTTP 200. The comment is in data.comment. postId is the fullname of its post. parentId names either the post (t3_) or a parent comment (t1_).
{
"ok": true,
"data": {
"comment": {
"id": "pdtyaj5",
"fullname": "t1_pdtyaj5",
"body": "Example content.",
"author": "fykup",
"community": "Python",
"parentId": "t3_1wxjay5",
"postId": "t3_1wxjay5",
"score": 6,
"createdAt": 1791130136,
"permalink": "https://www.reddit.com/r/Python/comments/1wxjay5/showcase_thread/pdtyaj5/",
"platform": "reddit"
}
},
"meta": {
"method": "read",
"path": "/reddit/v1/comments/pdtyaj5",
"ts": 1791549477235,
"billing": {
"pricingId": "read.comment",
"version": "usd-v1",
"resultCount": 1,
"billingUnits": 1,
"unitPrice": "0.0007",
"charged": "0.0007",
"balanceRemaining": "9.9993",
"currency": "USD"
}
}
}
Search
GET /reddit/v1/search searches all of Reddit. GET /reddit/v1/r/{community}/search searches inside one community. Both take the same parameters.
| Query | Required | Meaning |
|---|---|---|
q | yes | The search text. |
sort | no | relevance, hot, top, new or comments. The default is relevance. |
timeRange | no | hour, day, week, month, year or all. The default is week, so use all to search all time. t is accepted as an alias. |
limit | no | Page size, 1 to 100. The default is 10. |
cursor | no | The nextCursor value from the previous page. Pass it unchanged. |
includeComments | no | Set to true to search comments as well as posts. The default is false. |
To page through results, keep the query, community, sort, time range and includeComments the same and pass each nextCursor back as cursor. Stop when a response has no nextCursor.
curl "https://api.apixstar.com/reddit/v1/r/typescript/search?q=bun&limit=10" \ -H "Authorization: Bearer YOUR_API_KEY"
GET /reddit/v1/search response JSON
HTTP 200. The values are illustrative. Field names and structure match a tested response.
{
"ok": true,
"data": {
"posts": [
{
"media": [
{
"type": "image",
"url": "https://i.redd.it/v4aw0rruy0th1.jpeg"
}
],
"id": "1wvpka8",
"fullname": "t3_1wvpka8",
"title": "They say that, after humiliation, comes riches, power and glory.",
"body": "Example content.",
"author": "Suspicious-Ad1320",
"community": "Btechtards",
"url": "https://i.redd.it/v4aw0rruy0th1.jpeg",
"permalink": "https://www.reddit.com/r/Btechtards/comments/1wvpka8/they_say_that_after_humiliation_comes_riches/",
"score": 3488,
"upvoteRatio": 0.98,
"commentCount": 206,
"createdAt": 1790934195,
"isRemoved": false,
"isSelf": false,
"stickied": false,
"flair": "Serious",
"platform": "reddit"
}
],
"nextCursor": "t3_1wvpka8"
},
"meta": {
"method": "search",
"path": "/reddit/v1/search",
"ts": 1791549495180,
"billing": {
"pricingId": "search.global",
"version": "usd-v2",
"resultCount": 1,
"billingUnits": 1,
"unitPrice": "0.0007",
"charged": "0.0007",
"balanceRemaining": "9.9993",
"currency": "USD"
}
}
}
GET /reddit/v1/r/{community}/search response JSON
HTTP 200. The values are illustrative. Field names and structure match a tested response.
{
"ok": true,
"data": {
"posts": [
{
"id": "1wwv97o",
"fullname": "t3_1wwv97o",
"title": "My Python magic is gone",
"body": "Example content.",
"author": "RedYad2",
"community": "Python",
"url": "https://www.reddit.com/r/Python/comments/1wwv97o/my_python_magic_is_gone/",
"permalink": "https://www.reddit.com/r/Python/comments/1wwv97o/my_python_magic_is_gone/",
"score": 837,
"upvoteRatio": 0.89,
"commentCount": 296,
"createdAt": 1791054523,
"isRemoved": false,
"isSelf": true,
"stickied": false,
"flair": "Discussion",
"platform": "reddit"
}
],
"nextCursor": "t3_1wwv97o"
},
"meta": {
"method": "search",
"path": "/reddit/v1/r/Python/search",
"ts": 1791549479540,
"billing": {
"pricingId": "search.community",
"version": "usd-v2",
"resultCount": 1,
"billingUnits": 1,
"unitPrice": "0.0007",
"charged": "0.0007",
"balanceRemaining": "9.9993",
"currency": "USD"
}
}
}
When includeComments=true
Both search endpoints then return a data.posts array and a data.comments array. Together they hold at most limit items, and either can be empty. Comments have the same fields as data.comment above. data.partial is a boolean. data.errors is an array that contains posts_unavailable or comments_unavailable when that half failed, and is empty otherwise.
A partial response can still have ok: true, and it is billed for the results it returned. If both halves fail, the request returns an error. Pass data.nextCursor back unchanged to continue. It can hold separate positions for posts and comments.
Rate limits
Every account can send 30 requests per second, shared across all of its API keys and regardless of top-up amount. The counter resets each second. Responses carry X-RateLimit-Limit and X-RateLimit-Remaining. Going over the limit returns RATE_LIMITED with Retry-After: 1.
Errors
A failed request has ok: false and an error.code. Failed requests are never charged.
| Code | HTTP | Meaning |
|---|---|---|
INVALID_API_KEY | 401 | The Authorization: Bearer header is missing, the key has an unsupported prefix, or the key is unknown or revoked. |
ACCOUNT_FROZEN | 403 | The account is frozen or inactive. Contact support to restore access. |
INSUFFICIENT_BALANCE | 402 | The balance cannot cover the largest possible charge for this request. Reddit is not called. |
RATE_LIMITED | 429 | The account exceeded 30 requests per second, shared across its API keys. Wait for the time in Retry-After. |
INVALID_PATH | 400 | The path is not a supported resource, or a search is missing q. |
INVALID_LIMIT | 400 | For listings and searches, limit must be a single integer from 1 to 100. Empty, repeated, fractional and out-of-range values are rejected. |
INVALID_REQUEST | 400 | An upstream parameter validation failed. Check error.details.issues when present and correct the indicated parameters. |
IDEMPOTENCY_CONFLICT | 409 | An X-Request-ID was reused with different parameters or a different API key in the same account. Use a new ID for a different request. |
REQUEST_IN_PROGRESS | 409 | A request with the same X-Request-ID still has an active reservation. This duplicate does not call Reddit or charge again. Wait for the original request to finish. |
REQUEST_ALREADY_PROCESSED | 409 | The X-Request-ID belongs to a completed, failed or expired request. The previous response is not replayed, and this duplicate does not call Reddit or charge again. After a confirmed failure, use a new ID to retry. |
RESERVATION_NOT_ACTIVE | 409 | The billing reservation expired or was released before settlement. This attempt is not charged. Retry as a new request with a new X-Request-ID. |
ORIGIN_RESULT_INVALID | 502 | The upstream response has an invalid result shape or exceeds the requested result limit. The request is not charged. Retry later; contact support if it persists. |
PRICING_UNAVAILABLE | 503 | Pricing for this endpoint is temporarily unavailable. Reddit is not called. Retry later. |
REDDIT_RATE_LIMITED | 429 | Reddit answered with a rate limit. Wait for Retry-After when present before retrying. |
REDDIT_TIMEOUT | 504 | Reddit did not answer in time. Retry later; use a new X-Request-ID after this confirmed failure. |
REDDIT_HTTP_ERROR | 502 or upstream status | Reddit returned an error or a blocked page. The upstream HTTP status is forwarded when available. |
REDDIT_UNAVAILABLE | 502 or 503 | The upstream reader is down, busy or cooling down. Retry later; use a new X-Request-ID after this confirmed failure. |
INTERNAL_ERROR | 500 | An unexpected server error occurred. Contact support if it persists. If the original outcome is uncertain, do not automatically use a new request ID: it can create another billable request. |
Error response example
HTTP 400 for a listing limit that is out of range. An error response has ok: false and error, and may add error.details. Do not expect data or meta.billing on a failure.
{
"ok": false,
"error": {
"code": "INVALID_LIMIT",
"message": "limit must be one integer from 1 to 100"
}
}
Want to try a request before writing code? The playground runs any endpoint with one of your keys. For agents, see the MCP server.