requests routes traffic through a proxy when you pass a proxies dictionary keyed by target scheme: {"http": ..., "https": ...} (requests docs: proxies). Credentials go inline in the proxy URL (http://user:pass@host:port). Set it once on a Session if you want every call in a run to use it.
The minimal working example
Both keys point at the same proxy. The key is the scheme of the destination, not the scheme spoken to the proxy, so an HTTP proxy that tunnels HTTPS still uses the http:// prefix in the value.
import requests
proxy = "http://<list-username>:<list-password>@proxy.voidmob.com:10000"
proxies = {"http": proxy, "https": proxy}
r = requests.get("https://ipinfo.io/json", proxies=proxies, timeout=30)
print(r.status_code, r.json())
On VoidMob that username and password come from a proxy list, created on the order page or with POST /v1/proxies/:id/lists. The list carries its own geo target and rotation rule, so your Python code stays the same when you change either one. Port 10000 is the per-list port.
A 200 with an IP that is not yours means the proxy is live. A 407 means auth failed, a 502/504 usually means the upstream device dropped, and a connection timeout at this stage is nearly always a wrong port or a firewall in front of your machine.
Authentication and special characters
Inline credentials are parsed as a URL, which is where most auth bugs come from. If a password contains @, :, /, ? or #, percent-encode it before interpolation:
from urllib.parse import quote
user = quote("<username>", safe="")
password = quote("<password>", safe="")
proxy = f"http://{user}:{password}@proxy.voidmob.com:10000"
An unencoded @ typically makes the URL parser read part of your password as the host, and the failure surfaces as a connection or name-resolution error that looks nothing like an auth problem.
Environment variables are the alternative: HTTP_PROXY, HTTPS_PROXY and NO_PROXY are picked up automatically. They are convenient for one-off scripts and dangerous in production, because anything you forgot to override inherits them.
Sessions: set the proxy once
A Session reuses connections, keeps cookies, and holds default proxies and headers for every request made through it. For anything longer than a single call, use one.
import requests
s = requests.Session()
s.proxies.update({"http": proxy, "https": proxy})
s.headers.update({"User-Agent": "Mozilla/5.0 (Linux; Android 14; Pixel 8)"})
s.get("https://example.com/login")
s.post("https://example.com/login", data={"u": "...", "p": "..."})
Two things about sessions that bite people:
- A
proxiesargument on an individual call merges over the session defaults for that call only. session.trust_env = Falsestopsrequestsfrom readingHTTP_PROXY/NO_PROXYand your.netrc. Set it when you want the code to be the only source of truth.
SOCKS5 with requests
SOCKS support is an extra dependency:
pip install "requests[socks]"
Then use the socks5h:// scheme so DNS resolves on the proxy side, not on your machine (socks5:// leaks lookups to your local resolver):
import os
socks_port = os.environ["VOIDMOB_SOCKS_PORT"] # from your order credentials
proxy = f"socks5h://<username>:<password>@proxy.voidmob.com:{socks_port}"
proxies = {"http": proxy, "https": proxy}
All VoidMob plans support HTTP and SOCKS5, but the SOCKS port is issued per order with your credentials, so read it from the order rather than hardcoding a number. If you want the protocol trade-offs, SOCKS5 vs HTTP vs HTTPS covers latency and feature differences.
Rotation: per request or per session
Rotation is a property of the proxy, not of requests. You choose it in two places.
| Approach | Proxy lists | Flex username |
|---|---|---|
| Where it is configured | Dashboard or API, per list | Underscore parameters in the username |
| Per-request rotation | rotation_period_seconds = 0 (default) | Omit the _s_ parameter |
| Sticky | rotation_period_seconds = -1, or a timer in seconds | _s_<id> plus optional _ttl_<n> |
| Geo targeting | country, region, city, isp, zip fields | _c_US, _city_New-York and similar suffixes |
| Best for | Most Python workloads, one credential per job | Many short-lived identities in one process |
Per-request rotation means every session.get() can exit from a different device, which is what you want for independent page fetches. Anything with a login, a cart or a multi-step form needs one IP for the whole flow. For that, build the proxy URL per worker:
def sticky_proxy(session_id, country="US", ttl="10m"):
# username/password from POST /v1/proxies/:id/flex_credentials
user = f"<username>_c_{country}_s_{session_id}_ttl_{ttl}"
url = f"http://{user}:<password>@proxy.voidmob.com:10092"
return {"http": url, "https": url}
worker = requests.Session()
worker.proxies.update(sticky_proxy("run-17"))
Same _s_ value, same exit IP until the TTL ends (without a TTL, after 60 minutes of inactivity). A new string gets a new device. Pair one requests.Session with one proxy session ID and never share either across accounts. The sticky session explainer covers how long to hold a window per workload.
The failure mode nobody documents: silent bypass
The most common "my proxy is not working" report in Python is traffic that never entered the proxy at all. Four causes, in order of frequency:
NO_PROXYis set (often to*or a domain you are targeting) andtrust_envis stillTrue.requestsskips the proxy for matching hosts without raising anything.- You passed
proxiestorequests.get()but reuse a module-levelSessionelsewhere that has no proxies set. Half your calls go direct. - A library under you opens its own connection.
urllib3called directly,httpx, a gRPC client or a headless browser will not read yourrequestsconfig. - Retries re-resolve the identity. With per-request rotation, an automatic retry exits from a different device, so a mid-flow retry can look like a session hijack to the target. Retry at the job level, not inside a sticky flow.
The cheap guard is to assert the exit IP inside the same session you are about to work with:
ip = s.get("https://ipinfo.io/json", timeout=20).json()["ip"]
assert ip != MY_REAL_IP, "request bypassed the proxy"
Check what IP you are exiting from
You can also paste a proxy string into the Proxy Validator to see its type and location before writing any code, and check the exit against the IP Blacklist Checker if a target starts returning captchas.
When requests is the wrong tool
requests sends no JavaScript and presents a Python TLS stack, so sites that fingerprint the handshake can classify the client regardless of IP quality. If your target renders content client-side or challenges the TLS layer, move to a browser and keep the same proxy string: see Playwright proxy setup or Selenium proxy setup. For a worked Python pipeline that combines the HTTP layer with mobile IPs, scraping Instagram profiles with Python walks through the full flow.
Testing the same request from the shell first is often faster than debugging Python; the flags are in the curl proxy guide. Sizing and IP type decisions for crawlers live in mobile proxies for web scraping.
1How do you pass a proxy in requests in Python?
Build a dictionary keyed by destination scheme and pass it as proxies=: requests.get(url, proxies={'http': 'http://user:[email protected]:10000', 'https': 'http://user:[email protected]:10000'}). Set the same dict on a Session to apply it to every call.
2Which is better, urllib or requests?
requests for application code: it handles connection pooling, sessions, cookies, proxy auth and JSON decoding in a few lines. urllib is in the standard library and is worth using only when you cannot add a dependency, or when you need low-level control over the connection that requests deliberately hides.
3What is a proxy request?
A request your client sends to an intermediary server, which then forwards it to the destination on your behalf and returns the response. The destination sees the proxy's IP address and network characteristics instead of yours.
4What is the difference between a proxy and an API?
An API is an endpoint that returns structured data from a service you are allowed to query. A proxy is transport: it changes the network path and source IP of whatever request you make. They solve different problems, and a scraping API is simply someone else's proxy plus parsing wrapped in an endpoint.
5How do I set a proxy for pip?
Pass it per command with pip install --proxy http://user:pass@host:port package, or set it permanently with pip config set global.proxy http://user:pass@host:port. pip also honours the HTTP_PROXY and HTTPS_PROXY environment variables.
If the Python job is an AI agent rather than a script, the same lists and sessions are reachable through the VoidMob MCP server, so the agent can create and rotate its own proxy access.
Run your Python jobs on real 4G/5G devices
Pay-per-GB mobile pools with per-request or sticky rotation, or a dedicated device you rotate on demand.