Create a Reddit Post Programmatically
Create a Reddit text, link, poll, image or video post through a REST call. Handles session cookies, CSRF tokens, media upload and subreddit rule rejections, and refuses unsupported post kinds by name instead of silently downgrading them.
/api/reddit/submit$0.012 / callCreate a post in a subreddit as the authenticated caller. Serves text (self),
link and poll posts.
Not yet verified end to end. The route is unit-tested and its request shape is derived from the live router, but no post has been created through it against a real Reddit account. A write that fails does so under your account, in a real subreddit. Talk to support before you build on this, and we will verify it with you.
Request Body
Cookies are passed as flat top-level fields.
| Field | Type | Required | Description |
|---|---|---|---|
subreddit | string | yes | Target subreddit name, without the r/ prefix |
kind | string | yes | self text, link URL, poll, or image / video / videogif for media |
title | string | yes | Post title |
text | string | no | Body text. Used when kind is self. |
url | string | no | Target URL. Used when kind is link. |
options | array | no | Poll choices. Required when kind is poll; at least two, distinct, non-empty. |
duration | integer | no | Poll length in whole days. Required when kind is poll. |
image_url | string | no | Public https URL of the image. Required when kind is image. |
video_url | string | no | Public https URL of the video. Required when kind is video or videogif. |
video_poster_url | string | no | Public https URL of a poster image. Required for video and videogif. |
nsfw | boolean | no | Mark the post NSFW |
spoiler | boolean | no | Mark the post a spoiler |
sendreplies | boolean | no | Send reply notifications to the author inbox |
flair_id | string | no | Subreddit flair template id; list them with GET /api/reddit/sub/:name/flairs |
flair_text | string | no | Flair text, where the subreddit allows it |
reddit_session | string | yes | Session cookie from /api/reddit/login |
loid | string | yes | Long-lived account identifier cookie |
token_v2, csrf_token, edgebucket, csv, session_tracker, pc | string | no | Any extra Reddit cookies as top-level fields, forwarded with the session. Optional; the login response returns the ones the account has. |
proxy | object | string | no | Sticky IP for this account, {server, username?, password?} or a URL string. Use the same proxy you passed to /login. Omit to use our pool. See Per-account proxies. |
locale / timezone | string | no | Browser-context identity, e.g. de-DE and Europe/Berlin. Match these to your proxy's country. Only applies where a real browser runs. Defaults en-US and America/New_York. |
Which kinds are served
self, link, poll, image, video and videogif. An unsupported kind is refused by
name with a 400, never silently downgraded to a text post. A caller who asked for one thing
and quietly got another has no way to know, and the result is published under their own name.
Image and video
You give us a URL and we fetch the media. That is the only shape that works for a large video, and you almost certainly already host the file somewhere.
{ "kind": "image", "subreddit": "test", "title": "My cat", "image_url": "https://example.com/cat.png" }A few things worth knowing before your first call:
- The URL must be publicly readable over https. We fetch it server-side, so anything behind a login, on a private network, or served over plain http is refused with a message saying so.
- A URL with no file extension is fine. Signed and CDN URLs usually have none, so the
Content-Typeyour server sends decides the media type, and the extension is only a fallback. urlis not the same field. Putting an image URL inurlasks for a link post pointing at the image, which is a different post. That combination is refused rather than guessed at.- A video needs a poster. Reddit will not accept one without it, so
video_poster_urlis required. We do not substitute a placeholder, because it would be published as yours. - Size limits are Reddit's. We do not invent our own, so an oversized file comes back with the
actual numbers:
the media is too large for Reddit: 31.4 MB against a limit of 20.0 MB.
Media posts are confirmed, not fire-and-forget
Reddit processes an upload asynchronously and its submit response carries no post id at all. We
hold your request open until Reddit confirms, so you get the same post_id and permalink as
every other kind rather than having to poll for it.
If processing is still running after 3 minutes you get a MEDIA_CONFIRM_TIMEOUT, and it says
plainly that the post was not rejected and may still appear. Check the account's recent
posts before submitting again, or you will post it twice.
Errors
| Status | Meaning |
|---|---|
400 | subreddit, kind or title missing, a kind this endpoint does not serve (UNSUPPORTED_POST_KIND), or a malformed field |
401 | Missing Bearer token |
402 | Insufficient credits |
403 | Invalid Bearer token |
429 | Rate limited |
502 | Reddit refused the post: a subreddit rule (NOT_ALLOWED_IN_SUBREDDIT), Reddit's own rate limit (REDDIT_RATELIMIT), a generic refusal (REDDIT_REFUSED), or a captcha demand (CAPTCHA_REQUIRED); or Reddit answered with a non-200 HTTP status such as a 429 throttle (REDDIT_HTTP_ERROR, with that status in upstream_status) |
503 | No session could be acquired from the pool before the timeout |
500 | Unexpected server error |
A write Reddit refused or throttled is billed at this endpoint's price. Reddit received the attempt and answered it, so a 502 carrying one of the Reddit codes above costs the same as a posted write, as it does on every other write endpoint. A 500 (our own failure) is not billed.
A subreddit rejection is a 502, not a 403. 403 means this API rejected your
Bearer token and never reached Reddit at all. That distinction matters when you write the
handler: a 502 carrying NOT_ALLOWED_IN_SUBREDDIT is Reddit's final answer about this
post, so retrying it reproduces the same refusal, while a 503 is our pool and is worth
retrying. Treating the subreddit refusal as a generic transport failure is the one
mistake that costs a real account a repeated rejected post.
A rule rejection carries Reddit's own machine-readable error in reddit_errors, so a
karma requirement, an account-age gate or a banned link domain is distinguishable from a
transport failure rather than collapsing into one opaque error. CAPTCHA_REQUIRED is
terminal for this endpoint by design: it does not solve captchas, so retrying the same
way will not help.
Independent third-party API for developers and researchers. Not affiliated with, endorsed by, or sponsored by Reddit, Inc.
Authenticated reads
Send your Reddit session as headers on the thirteen read endpoints that accept one, to see private, restricted and member-only subreddits, plus your own feed, saved, hidden and upvoted. Six of the thirteen need a session and cannot be served without one. Optional, additive, and existing calls are unchanged.
Scheduled Posts and Comments
Schedule a Reddit post or comment for a later time, list your scheduled actions and cancel one. Free to schedule; billed at the write price only when it runs.
