Python · setup guide

aiohttp proxy setup

An aiohttp proxy goes on each request with proxy=, or since 3.11 once on the ClientSession, with the credentials in the proxy URL. What decides rotation is not the proxy but the connector: aiohttp keeps connections alive, and a kept-alive tunnel keeps its address.
You will need

Residentialresi.proxymonkey.io:8000

ISP or datacenterIP:PORT

CredentialsUSER:PASS

Copy your own from the dashboard, which also lists the host and port for every order. Where it differs from this page, the dashboard is right.

Install

Python 3.9 or newer. Session-level proxy= needs aiohttp 3.11 or later.

terminal
pip install aiohttp

Rotating residential

force_close=True on the connector closes each connection once its response is read, so every request opens a new tunnel through the residential gateway and leaves from a new address. The three lines should all differ.

rotate.py
import asyncio

import aiohttp

PROXY = "http://USER:[email protected]:8000"


async def main():
    connector = aiohttp.TCPConnector(force_close=True)
    timeout = aiohttp.ClientTimeout(total=30)
    async with aiohttp.ClientSession(connector=connector, timeout=timeout) as session:
        for _ in range(3):
            async with session.get("https://httpbin.org/ip", proxy=PROXY) as resp:
                print((await resp.json())["origin"])


asyncio.run(main())

A static datacenter or ISP IP

On a static datacenter IP you want keep-alive, so leave force_close off and set the proxy once on the session. limit=8 caps the connections in flight while gather works through the list. For an ISP order, use the host and port from your dashboard.

static.py
import asyncio

import aiohttp

PROXY = "http://USER:PASS@IP:PORT"
URLS = ["https://httpbin.org/ip", "https://httpbin.org/headers"]


async def fetch(session, url):
    async with session.get(url) as resp:
        return resp.status, (await resp.text())[:80]


async def main():
    connector = aiohttp.TCPConnector(limit=8)
    timeout = aiohttp.ClientTimeout(total=30, sock_connect=10)
    async with aiohttp.ClientSession(proxy=PROXY, connector=connector, timeout=timeout) as session:
        for status, text in await asyncio.gather(*(fetch(session, url) for url in URLS)):
            print(status, text)


asyncio.run(main())

Keeping one identity

With the default connector, a session reuses idle connections, so requests to one host share a tunnel and its exit address until the connection closes. The session's cookie jar persists across all of them. If a site ties cookies to an IP, run that flow on a static IP or a residential sticky session, whose setting for your account is in the dashboard, rather than on the rotating gateway. Libraries built on aiohttp behave the same way underneath; discord.py is one, and Discord bots with discord.py shows where its proxy goes.

Specific to aiohttp

Things worth knowing

aiohttp proxy authentication in a header

Credentials in the proxy URL work on every version. aiohttp 3.14 deprecates proxy_auth=aiohttp.BasicAuth(...) and warns you towards a Proxy-Authorization header built with encode_basic_auth, passed as proxy_headers on each request; the session does not take it. SOCKS5 is not built in; the separate aiohttp-socks package adds a connector for it.

auth.py, aiohttp 3.14 or later
from aiohttp import ClientSession, encode_basic_auth

PROXY_HEADERS = {"Proxy-Authorization": encode_basic_auth("USER", "PASS")}


async def main():
    async with ClientSession() as session:
        async with session.get(
            "https://httpbin.org/ip",
            proxy="http://resi.proxymonkey.io:8000",
            proxy_headers=PROXY_HEADERS,
        ) as resp:
            print(await resp.text())

Cap concurrency at the connector

gather over a few thousand URLs schedules them all at once. TCPConnector(limit=...) caps open connections, 100 by default, and limit_per_host caps them per target. Every retry of a blocked page is metered, so a lower cap that avoids blocks is usually the cheaper one.

Count the bytes on the wire

We bill request bytes plus response bytes, headers included. aiohttp decompresses bodies by default, so switch that off to see the size that crossed the wire. Expect our figure slightly higher: the request and status lines and TLS overhead are real traffic too.

count.py
async def wire_bytes(url):
    async with aiohttp.ClientSession(auto_decompress=False) as session:
        async with session.get(url, proxy=PROXY) as resp:
            body = await resp.read()
            sent = sum(len(k) + len(v) + 4 for k, v in resp.request_info.headers.items())
            received = sum(len(k) + len(v) + 4 for k, v in resp.raw_headers) + len(body)
            return sent + received
When it breaks

Common errors and fixes

  • ClientHttpProxyError: 407, message='Proxy Authentication Required'

    Why
    The gateway refused the CONNECT for an HTTPS URL: wrong credentials, or a special character in the password that broke the proxy URL.
    Fix
    Quote the credentials with urllib.parse.quote(value, safe=''), or send them in proxy_headers so the URL carries none.
  • ConnectionTimeoutError, or a bare TimeoutError with no message

    Why
    The request outlived its ClientTimeout. The default allows five minutes in total, so without your own limit a stalled residential exit holds a connection slot for a long time.
    Fix
    Set ClientTimeout(total=30, sock_connect=10) on the session, catch asyncio.TimeoutError and aiohttp.ClientError, and retry. With force_close on the rotating gateway, the retry leaves from a new address.
  • ClientConnectorCertificateError with CERTIFICATE_VERIFY_FAILED

    Why
    The target's certificate or your trust store. aiohttp runs TLS with the target inside the tunnel; the proxy is not part of it.
    Fix
    Update certifi, or pass ssl= an SSLContext with your CA loaded. ssl=False is for testing only.
  • TypeError: ClientSession.__init__() got an unexpected keyword argument 'proxy'

    Why
    Session-level proxy= arrived in aiohttp 3.11.
    Fix
    Upgrade aiohttp, or pass proxy= on each request instead.

A failed connection that moved no data is not billed. Retries you send are billed like any other request, and each shows as its own line in your usage log.

The community layer

aiohttp still misbehaving?

Paste the error and the few lines that set up the proxy into Discord, with the password taken out. Someone there has seen it before.

Join the Discord

4,200+monkeys in the Discord

  • Help from humans

    Post your error, get an answer. Usually in minutes, usually from someone who has hit the same wall.

  • A status bot that tells on us

    Pool health, incidents and maintenance posted automatically. Including the bad days.

  • Deals and free traffic

    Bonus GB drops, early access to new pools, and the occasional giveaway for a good bug report.

Join the Discord4,200+ monkeys, free to lurk