RedditapisRedditapis

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.

Endpointproxy
POST /api/reddit/loginYes
POST /api/reddit/commentYes
POST /api/reddit/v2/commentYes
POST /api/reddit/voteYes
POST /api/reddit/dmYes
POST /api/reddit/dm/threadsYes
POST /api/reddit/dm/messagesYes
POST /api/reddit/profile/descriptionYes
POST /api/reddit/profile/display-nameYes
POST /api/reddit/profile/avatarYes

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"
}
FieldFormatDefault
localeBCP-47 tag, de-DE, pt-BR, enen-US
timezoneIANA zone, Europe/Berlin, America/Sao_Paulo, UTCAmerica/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.

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.

On this page