# Ludax proxies in Python: guide for AI assistants

> Give this file to your AI assistant (ChatGPT, Claude, Cursor, Copilot) and it can write working Python code for Ludax Proxy. Human-readable version: https://ludaxproxy.com/docs/python

You are helping a developer route Python HTTP traffic through Ludax residential proxies. Follow the rules below exactly; they come from the gateways' behaviour, not from general proxy conventions. Ludax is an ordinary HTTP(S) proxy: there is no SDK, no client library and no API key.

## Connection string

```
http://<PREFIX>-<USERNAME>[-AE-<PROFILE>]:<PASSWORD>[-session-<TOKEN>-time-<MINUTES>[-sameip]][-country-<CC>[-state-<CODE>[-city-<NAME>]]]@<GATEWAY>
```

- **USERNAME** is the developer's account name from the dashboard (Generate), for example `lx_k3m9q2p7xa`. It is the same for every plan. Never invent one: ask the developer, or use a placeholder such as `YOUR_USERNAME`.
- **PASSWORD** is the plan's password from the same page: 16 hexadecimal characters, one per plan. Use a placeholder such as `YOUR_PASSWORD`, or read it from an environment variable.
- **PREFIX** picks the product: `resi` for Residential and Residential TLS, `premi` for Residential Premium and Residential Premium TLS.
- Routing options are appended to the **password**, in this order: the session block, then the location. Never put them in the username.
- Use the same URL for `http` and `https` (`proxies={"http": url, "https": url}` in requests).
- Percent-encode the username and password if the developer's values contain `@`, `:` or `/` (`urllib.parse.quote(value, safe="")`).

## Products and gateways in force

| Product | Prefix | HTTP and HTTPS | SOCKS5 | Browser TLS | Sticky session |
| --- | --- | --- | --- | --- | --- |
| Residential | `resi` | `gw.ludaxproxy.com:7777` | `gw.ludaxproxy.com:7575` | No | up to 1000 min |
| Residential TLS | `resi` | `gw.ludaxproxy.com:9999` | none | Yes | up to 1000 min |
| Residential Premium | `premi` | `gw.ludaxproxy.com:8080` | `gw.ludaxproxy.com:1080` | No | up to 1000 min |
| Residential Premium TLS | `premi` | `gw.ludaxproxy.com:8081` | none | Yes | up to 1000 min |

The port picks the product: use each product's own gateway. The browser profile in the username (`-AE-chrome154`) belongs on the TLS gateways only.

## IP rotation and sessions

- **No `-session-`:** every new TCP connection leaves from a new IP. Clients that keep connections open reuse the same IP: for rotation use `requests.get` (not a long-lived `requests.Session`), aiohttp with `TCPConnector(force_close=True)`, or httpx with `httpx.Limits(max_keepalive_connections=0)`.
- **`-session-<TOKEN>-time-<MINUTES>`:** the same IP for every request that sends the same token. TOKEN is 3 to 24 letters or digits (`secrets.token_hex(4)` works); a new token gives a new IP. MINUTES is 1 to 1000 on every product; Premium holds a session at least 3 minutes. Always send `-time-`: without it a session lasts 1 minute.
- **On the TLS gateways add `-sameip`** after the time (`-session-<TOKEN>-time-<MINUTES>-sameip`). Without it the session holds only the browser profile and the IP still changes on every request.
- A proxy session keeps the **IP**; a `requests.Session` (or one aiohttp or httpx client) keeps the **cookies**. A login or checkout flow needs both: one proxy session token and one HTTP session per flow.

## Location

- `-country-<CC>`: two-letter ISO code, lower case (`us`, `de`, `jp`). 137 countries on Residential, all 219 on Premium.
- `-state-<CODE>` after the country: a region code on Residential (`by` is Bavaria, `ny` is New York), the state's name without spaces on Premium (`bavaria`, `newyork`).
- `-city-<NAME>` after the state: lower case, no spaces (`munich`, `newyork`).
- Premium only: `-asn-<NUMBER>` for one ISP network and `-os-<DEVICE>` (`windows`, `android`, `ios`, `macos`) for the kind of device, last.
- If no exit is free in that city or region at that moment, the request falls back to the country instead of failing. Check where it landed with the whoami endpoint. Region, city and ISP codes for every country: https://ludaxproxy.com/locations

## Browser TLS fingerprints (Residential TLS and Residential Premium TLS)

Python's `ssl` module (requests, aiohttp, httpx) sends a TLS handshake that anti-bot systems recognise. On the TLS gateways the gateway connects to the site with a real browser's handshake and HTTP/2 settings, so the developer keeps plain requests, aiohttp or httpx code. Do not suggest curl_cffi or tls-client for this.

- Pick the browser at the end of the **username**: `resi-lx_k3m9q2p7xa-AE-chrome154`.
- Profiles: `chrome154`, `edge148`, `firefox152`, `safari26_4`, `safariios26_2`, `opera131`, `okhttp5` and older versions of each (139 in all); `randomchrome`, `randomfirefox`, `randomsafari`, `randomedge`, `randomopera`, `randomokhttp` pick one build of that browser; `auto` picks any.
- `AE` sets a matching User-Agent and client hints for the developer: prefer it. `NAE` passes the developer's headers unchanged, so the User-Agent must come from the same browser and version as the profile.
- The TLS gateways re-sign HTTPS with the Ludax CA, so the client must trust it. Download it once: `curl -O https://ludaxproxy.com/downloads/ludax-ca.crt`. requests: `verify="ludax-ca.crt"`. httpx: `verify=ssl.create_default_context(cafile="ludax-ca.crt")`. aiohttp: `ssl=ssl.create_default_context(cafile="ludax-ca.crt")`. **Never** use `verify=False`.
- Apps with certificate pinning cannot use the TLS gateways; use the standard ones.
- A headless browser is still needed only when the page builds its content with JavaScript.

## SOCKS5

Residential `gw.ludaxproxy.com:7575` and Residential Premium `gw.ludaxproxy.com:1080`, with the same username and password. The TLS products have no SOCKS5. In requests install `requests[socks]` and use `socks5h://` so DNS is resolved at the exit, not on the developer's machine. For aiohttp and httpx prefer the HTTP gateway.

## Check the connection

`GET https://api.ludaxproxy.com/whoami` through the proxy returns JSON: `ip`, `country` (full name), `type` (`residential`, `datacenter` or `mobile`), `tls_profile` (the browser the handshake matches, for example `Chrome 154`, or `null` for plain Python) and `latency_ms`.

## Errors

| What the client sees | Meaning | What the code should do |
| --- | --- | --- |
| `407 Proxy Authentication Required` (requests: `ProxyError ... 407`) | Wrong username or password, a stray space or quote, or the plan's data is used up or expired | Stop. Never retry in a loop: 30 failed logins in 5 minutes block the client IP for 10 minutes |
| `403 Forbidden` with `X-Ludax-Blocked: blocklist` (requests: `Tunnel connection failed: 403`) | The site is on the plan's Block Domain list (dashboard, Generate → Block Domain) | Do not retry; remove the entry in the dashboard if the site is needed |
| `502 Bad Gateway` (requests: `Tunnel connection failed: 502`) | The exit dropped or could not reach the site | Retry with a short backoff; without a session token the retry gets a new IP |
| `CERTIFICATE_VERIFY_FAILED` / `SSLError` on a TLS gateway | The client does not trust the Ludax CA | Pass the CA as above; do not retry, do not disable verification |
| The same IP every time without a session | The client reuses one connection | Disable keep-alive as described under rotation |
| `Missing dependencies for SOCKS support` | requests lacks its SOCKS extra | `pip install "requests[socks]"` |

## Tested examples

The same code as on https://ludaxproxy.com/docs/python. Replace `lx_k3m9q2p7xa` and `YOUR_PASSWORD` with the developer's credentials.

### First request: check the connection

```python
# first_request.py
import requests

proxy = "http://resi-lx_k3m9q2p7xa:YOUR_PASSWORD-country-de@gw.ludaxproxy.com:7777"

r = requests.get(
    "https://api.ludaxproxy.com/whoami",
    proxies={"http": proxy, "https": proxy},
    timeout=30,
)
print(r.json())
```

### A new IP on every request

```python
# rotate.py
import requests

USER = "resi-lx_k3m9q2p7xa"
PASS = "YOUR_PASSWORD-country-us"
proxy = f"http://{USER}:{PASS}@gw.ludaxproxy.com:7777"

for i in range(5):
    # requests.get opens a new connection each time, so each call gets a new IP
    r = requests.get("https://api.ludaxproxy.com/whoami",
                     proxies={"http": proxy, "https": proxy}, timeout=30)
    print(i, r.json()["ip"])
```

### The same IP and the same cookies for a whole flow (login, checkout)

```python
# sticky_loop.py
import secrets
import requests

USER = "resi-lx_k3m9q2p7xa"
PASS = "YOUR_PASSWORD"

for job in range(3):
    token = secrets.token_hex(4)  # a new random proxy session = a new IP for this job
    proxy = f"http://{USER}:{PASS}-session-{token}-time-10-country-us@gw.ludaxproxy.com:7777"

    # requests.Session keeps the site's cookies (your login) from one step to the next
    with requests.Session() as s:
        s.proxies = {"http": proxy, "https": proxy}
        ips = [s.get("https://api.ludaxproxy.com/whoami", timeout=30).json()["ip"]
               for step in ("login", "search", "checkout")]
    print(f"job {job} (session {token}): {ips}")
```

### Country, region and city

```python
# geo.py
import requests

def proxy(password):
    url = f"http://resi-lx_k3m9q2p7xa:{password}@gw.ludaxproxy.com:7777"
    return {"http": url, "https": url}

# a country
requests.get("https://api.ludaxproxy.com/whoami", proxies=proxy("YOUR_PASSWORD-country-jp"), timeout=30)

# a region and a city in it (Bavaria, Munich)
requests.get("https://api.ludaxproxy.com/whoami",
             proxies=proxy("YOUR_PASSWORD-country-de-state-by-city-munich"), timeout=30)

# everything together: one IP in New York for 30 minutes
requests.get("https://api.ludaxproxy.com/whoami",
             proxies=proxy("YOUR_PASSWORD-session-ny01-time-30-country-us-state-ny-city-newyork"), timeout=30)
```

### Async with aiohttp, a new IP per request

```python
# async_aiohttp.py
import asyncio
import aiohttp

PROXY = "http://resi-lx_k3m9q2p7xa:YOUR_PASSWORD-country-us@gw.ludaxproxy.com:7777"
URLS = [f"https://api.ludaxproxy.com/whoami?page={n}" for n in range(50)]

async def fetch(session, url):
    async with session.get(url, proxy=PROXY) as r:
        return (await r.json())["ip"]

async def main():
    # limit=10: ten requests in flight; force_close: a new connection, so a new IP, each time
    connector = aiohttp.TCPConnector(limit=10, force_close=True)
    timeout = aiohttp.ClientTimeout(total=30)
    async with aiohttp.ClientSession(connector=connector, timeout=timeout) as session:
        ips = await asyncio.gather(*(fetch(session, url) for url in URLS))
    print(len(ips), "requests from", len(set(ips)), "different IPs")

asyncio.run(main())
```

### Async with HTTPX, a new IP per request

```python
# async_httpx.py
import asyncio
import httpx

PROXY = "http://resi-lx_k3m9q2p7xa:YOUR_PASSWORD-country-us@gw.ludaxproxy.com:7777"
URLS = [f"https://api.ludaxproxy.com/whoami?page={n}" for n in range(50)]

async def main():
    # max_connections=10: ten in flight; no keep-alive: a new connection, so a new IP, each time
    limits = httpx.Limits(max_connections=10, max_keepalive_connections=0)
    async with httpx.AsyncClient(proxy=PROXY, limits=limits, timeout=30) as client:
        responses = await asyncio.gather(*(client.get(url) for url in URLS))
    ips = [r.json()["ip"] for r in responses]
    print(len(ips), "requests from", len(set(ips)), "different IPs")

asyncio.run(main())
```

### SOCKS5 with remote DNS

```python
# socks5h.py
import requests  # pip install "requests[socks]"

# Residential Premium over SOCKS5 (Residential: gw.ludaxproxy.com:7575)
proxy = "socks5h://premi-lx_k3m9q2p7xa:YOUR_PASSWORD-country-us@gw.ludaxproxy.com:1080"

r = requests.get("https://api.ludaxproxy.com/whoami",
                 proxies={"http": proxy, "https": proxy}, timeout=30)
print(r.json())
```

### Browser TLS fingerprint with requests

```python
# tls_check.py
import requests

USER = "resi-lx_k3m9q2p7xa-AE-chrome154"  # + the browser to look like
PASS = "YOUR_PASSWORD-country-us"
proxy = f"http://{USER}:{PASS}@gw.ludaxproxy.com:9999"  # the Residential TLS port

r = requests.get(
    "https://api.ludaxproxy.com/whoami",
    proxies={"http": proxy, "https": proxy},
    verify="ludax-ca.crt",  # curl -O https://ludaxproxy.com/downloads/ludax-ca.crt
    timeout=30,
)
print(r.json()["tls_profile"])
```

### Browser TLS fingerprint with aiohttp

```python
# tls_aiohttp.py
import asyncio
import ssl
import aiohttp

PROXY = "http://resi-lx_k3m9q2p7xa-AE-chrome154:YOUR_PASSWORD-country-us@gw.ludaxproxy.com:9999"
SSL = ssl.create_default_context(cafile="ludax-ca.crt")  # trust the Ludax CA

async def main():
    async with aiohttp.ClientSession(timeout=aiohttp.ClientTimeout(total=30)) as session:
        async with session.get("https://api.ludaxproxy.com/whoami", proxy=PROXY, ssl=SSL) as r:
            print((await r.json())["tls_profile"])

asyncio.run(main())
```

### Browser TLS fingerprint with HTTPX

```python
# tls_httpx.py
import ssl
import httpx

PROXY = "http://resi-lx_k3m9q2p7xa-AE-chrome154:YOUR_PASSWORD-country-us@gw.ludaxproxy.com:9999"
ctx = ssl.create_default_context(cafile="ludax-ca.crt")  # trust the Ludax CA

with httpx.Client(proxy=PROXY, verify=ctx, timeout=30) as client:
    print(client.get("https://api.ludaxproxy.com/whoami").json()["tls_profile"])
```

### Retries: what to retry and what never to retry

```python
# retry.py
import time
import requests

def get(url, proxy, tries=4):
    """GET through Ludax: retry a dropped exit, stop at once on 407 and certificate errors."""
    proxies = {"http": proxy, "https": proxy}
    for attempt in range(1, tries + 1):
        try:
            r = requests.get(url, proxies=proxies, timeout=30)
            if r.status_code != 502:
                return r  # a 407 comes back here too: a retry cannot fix credentials
            error = RuntimeError(f"502 Bad Gateway on attempt {attempt}")
        except requests.exceptions.SSLError:
            raise  # on the TLS gateways: pass verify="ludax-ca.crt"
        except requests.exceptions.ProxyError as e:
            if "407" in str(e):
                raise  # wrong username or password, or no data left on the plan
            error = e  # Tunnel connection failed: 502
        except (requests.exceptions.ConnectionError, requests.exceptions.Timeout) as e:
            error = e  # the exit dropped mid-request
        if attempt < tries:
            time.sleep(2 ** (attempt - 1))  # 1, 2, 4 seconds; no wait after the last try
    raise RuntimeError(f"{url}: no answer after {tries} tries") from error
```

## Plans and help

- Prepaid, no subscription: 1 GB of Residential Proxies costs $0.99; 1 GB of Residential TLS Proxies costs $1.49. The smallest plan is the test; there is no free trial. Every plan: https://ludaxproxy.com/pricing
- Credentials: dashboard, Generate (https://ludaxproxy.com/dashboard).
- Full reference: https://ludaxproxy.com/docs (gateways, credential syntax, TLS profiles, status codes). Whole site for AI: https://ludaxproxy.com/llms.txt
- Support: Telegram https://t.me/LudaxProxy, email support@ludaxproxy.com.
