RedditapisRedditapis
Write

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.

POST/api/reddit/submit$0.012 / call

Create 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.

FieldTypeRequiredDescription
subredditstringyesTarget subreddit name, without the r/ prefix
kindstringyesself text, link URL, poll, or image / video / videogif for media
titlestringyesPost title
textstringnoBody text. Used when kind is self.
urlstringnoTarget URL. Used when kind is link.
optionsarraynoPoll choices. Required when kind is poll; at least two, distinct, non-empty.
durationintegernoPoll length in whole days. Required when kind is poll.
image_urlstringnoPublic https URL of the image. Required when kind is image.
video_urlstringnoPublic https URL of the video. Required when kind is video or videogif.
video_poster_urlstringnoPublic https URL of a poster image. Required for video and videogif.
nsfwbooleannoMark the post NSFW
spoilerbooleannoMark the post a spoiler
sendrepliesbooleannoSend reply notifications to the author inbox
flair_idstringnoSubreddit flair template id; list them with GET /api/reddit/sub/:name/flairs
flair_textstringnoFlair text, where the subreddit allows it
reddit_sessionstringyesSession cookie from /api/reddit/login
loidstringyesLong-lived account identifier cookie
token_v2, csrf_token, edgebucket, csv, session_tracker, pcstringnoAny extra Reddit cookies as top-level fields, forwarded with the session. Optional; the login response returns the ones the account has.
proxyobject | stringnoSticky 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 / timezonestringnoBrowser-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-Type your server sends decides the media type, and the extension is only a fallback.
  • url is not the same field. Putting an image URL in url asks 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_url is 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

StatusMeaning
400subreddit, kind or title missing, a kind this endpoint does not serve (UNSUPPORTED_POST_KIND), or a malformed field
401Missing Bearer token
402Insufficient credits
403Invalid Bearer token
429Rate limited
502Reddit 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)
503No session could be acquired from the pool before the timeout
500Unexpected 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.

On this page