Reddit API Per-Account Proxy and Geo Matching
Pin one sticky, geo-matched IP to each of your Reddit accounts. Which endpoints accept a proxy, what happens when you omit it, and how to keep login and your writes on the same IP.
Reddit weighs the IP an account acts from. An account that signed up and logs in from one country, then comments from a datacenter IP in another, is carrying a mismatch that is cheap for Reddit to spot and act on.
So every endpoint that acts as your account takes an optional proxy, and
you should pass the same one for the life of that account.
Which endpoints accept a proxy
Every write endpoint.
| Endpoint | proxy |
|---|---|
POST /api/reddit/login | Yes |
POST /api/reddit/comment | Yes |
POST /api/reddit/v2/comment | Yes |
POST /api/reddit/vote | Yes |
POST /api/reddit/dm | Yes |
POST /api/reddit/dm/threads | Yes |
POST /api/reddit/dm/messages | Yes |
POST /api/reddit/profile/description | Yes |
POST /api/reddit/profile/display-name | Yes |
POST /api/reddit/profile/avatar | Yes |
Public read endpoints (GET /api/reddit/*) never touch your account. Those are
served from our own pool of logged-in accounts, which is why they need no
cookies from you.
One exception, and it does not take a proxy yet. The own-account listings,
/api/reddit/user/{name}/upvoted, /saved, /hidden and /gilded, require
your reddit_session and loid, so they act as your account, but they always
egress through our pool. If those reads are geo-sensitive for you, say so and we
will thread a proxy through them.
The shape
Either form works on every endpoint above. One caveat on /login, noted below.
{
"proxy": {
"server": "http://host:port",
"username": "...",
"password": "..."
}
}{ "proxy": "http://host:port" }server is required in the object form. username and password are optional,
for an authenticated proxy. The string form takes the same credentials as
standard URL userinfo before the host.
Prefer the object form for an authenticated proxy. It keeps the credentials out of the URL, which means they stay out of anything that logs a URL on your side.
A bare host:port works, except on /login. It is treated as
http://host:port, which is what proxy vendors mean when they print it that way
on a dashboard.
/login is the one exception, and only on its default transport. With
method: "http" (the default) send a full http://host:port; the short form is
not parsed there and fails later with an unclear error rather than a 400. With
method: "browser" the short form works like everywhere else. If in doubt on
/login, always send the scheme.
http and https work everywhere. socks4, socks5 and socks5h are
accepted on the endpoints that use our HTTP transport; /login with
method: "browser" and /comment drive a real browser, which supports http,
https and socks5 only. If you are unsure, use an http proxy: it works on
every endpoint.
Use the same proxy for login and every write
The proxy is per request, not per account. We do not remember the one you used
at /login and reuse it later, so a call that omits proxy goes out through our
shared pool even when the login that produced those cookies did not.
That fallback is the geo mismatch this page exists to prevent. Pass the proxy on every call for that account:
{
"post_url": "https://www.reddit.com/r/example/comments/abc123/title/",
"text": "your comment",
"reddit_session": "...",
"loid": "...",
"proxy": { "server": "http://host:port", "username": "...", "password": "..." }
}Keep one proxy per account and keep it stable. A sticky residential or mobile IP in the country the account presents as is the point. Rotating IPs per request is the pattern you are trying to avoid, not a safety measure.
Your proxy is pinned across retries. When a call is retried internally, a
proxy you supplied is reused for every attempt, so a retry cannot silently move
the account to a different IP. Only when you omit proxy do we rotate our pool,
where a fresh egress per attempt is the point.
Match the browser's clock and language to the proxy
A proxy alone is only half of geo matching. On the endpoints that drive a real browser, that browser also reports a timezone and a language, and those travel in the same session as your proxy. An account acting from a German IP while its browser says New York and US English is carrying exactly the mismatch a proxy is meant to remove.
So /api/reddit/login (with method: "browser") and /api/reddit/comment
accept two more optional fields:
{
"proxy": { "server": "http://de-residential.example:1080" },
"locale": "de-DE",
"timezone": "Europe/Berlin"
}| Field | Format | Default |
|---|---|---|
locale | BCP-47 tag, de-DE, pt-BR, en | en-US |
timezone | IANA zone, Europe/Berlin, America/Sao_Paulo, UTC | America/New_York |
Both are optional and the defaults are unchanged, so an integration that sends
neither behaves exactly as it does today. A malformed value is a 400 rather
than a silent fallback, because falling back quietly would hand you the mismatch
you were trying to avoid.
These only apply where a browser actually runs. /vote, /dm, /v2/comment
and the profile endpoints use a direct HTTP transport with no browser context,
so there is no clock or language to set, and they do not accept these fields.
On /login the two fields are accepted only with method: "browser". Sending
them with the default method: "http" returns a 400 with
code: "FIELD_NOT_APPLICABLE" rather than a 200 that quietly drops them,
because a field that silently does nothing is worse than an error you can see.
Casing is fixed for you: utc and europe/berlin are accepted and normalised
to UTC and Europe/Berlin, and aliases resolve (GMT to UTC, Japan to
Asia/Tokyo). A numeric offset such as +05:30 is not a timezone (it has no
daylight-saving rules) and is refused.
If your accounts are geo-sensitive, set all three together and keep them consistent for the life of the account: proxy country, timezone, language.
Omitting it
Leave proxy out and the call goes through our shared pool. That is a perfectly
normal way to use the API, and it is the right default when the account is not
geo-sensitive. It is the wrong default when it is.
Send the proxy on the proxy field, not as a cookie
Session cookies are sent as flat top-level fields, so a top-level string named
something proxy-shaped would otherwise be forwarded to Reddit as a cookie, with
your proxy credentials in the value. We return a 400 instead:
{
"error": "proxyUrl cannot be sent as a top-level string. ...",
"code": "INVALID_FIELD",
"field": "proxyUrl"
}Any other top-level string is forwarded as a cookie, including names not in the
documented set, because /login returns whatever cookies Reddit set that day
and you should be able to pass them straight through. The documented names are
reddit_session, loid, token_v2, csrf_token, edgebucket, csv,
session_tracker and pc.
To be explicit about the cookie set, pass a cookies object. It is forwarded
untouched:
{
"post_url": "https://www.reddit.com/r/example/comments/abc123/title/",
"text": "your comment",
"cookies": { "reddit_session": "...", "loid": "...", "some_other_cookie": "..." }
}Independent third-party API for developers and researchers. Not affiliated with, endorsed by, or sponsored by Reddit, Inc.
Rate limits
There is no per-key request quota on the Reddit data endpoints. What a 429 actually means, what Retry-After tells you, and why a fixed client-side throttle does not help.
CommentHot
Post a comment on any Reddit thread programmatically. Handles reCAPTCHA, CSRF tokens, and rich-text encoding for you, just send post URL and text.
