Proxies
When to use a proxy, what formats are accepted, residential vs datacenter trade-offs, and how to keep cf_clearance valid.
Proxies
Pass a proxy via the proxy field in your /solve body. The solver uses it for the entire solve, so the resulting token/cookies/headers reflect that egress IP.
Accepted formats
The server validates against this regex:
(http|https|socks4|socks5)://([user:pass@])?host:port| Scheme | Example |
|---|---|
http | http://host:8080 |
http | http://user:pass@host:8080 |
https | https://user:pass@host:8443 |
socks4 | socks4://host:1080 |
socks5 | socks5://user:pass@host:1080 |
Validation also enforces:
- Port between 1 and 65535.
- The host must be public — proxies pointing at loopback, private (RFC1918), link-local, or
.local/.internalhosts are rejected with400. - Max length 512 chars.
When to use a proxy
| Scenario | Proxy? |
|---|---|
| Cloudflare Challenge target | Required |
| Turnstile on a geo-blocked page | Recommended |
| Kasada on a geo-blocked target | Recommended |
You'll replay cf_clearance with a specific IP | Required, matched |
| You'll replay headers from a specific country | Recommended, matched |
| You're saturating an IP-based per-source rate limit | Required (rotate) |
For Challenge, the proxy is mandatory because the issued cf_clearance is bound to its egress IP. Replaying it from any other IP is rejected by Cloudflare.
Residential vs datacenter
| Factor | Residential | Datacenter |
|---|---|---|
| IP reputation | High — owned by real ISPs | Lower — known hosting ranges |
| Detection risk | Low | Higher (varies by provider) |
| Speed | Variable | Fast, consistent |
| Cost | Higher (per GB) | Lower (per IP / per GB) |
| Best for | Heavily protected sites | High-volume, lighter protection |
Default to datacenter for cost. Switch to residential only when you measure elevated failure rates on the target site.
Examples
import requests
requests.post(
"https://api.nslsolver.com/solve",
headers={"X-API-Key": "YOUR_KEY"},
json={
"type": "turnstile",
"site_key": "0x4AAAAAAA",
"url": "https://example.com",
"proxy": "http://user:[email protected]:8080",
},
timeout=120,
)await fetch("https://api.nslsolver.com/solve", {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-API-Key": process.env.NSL_API_KEY,
},
body: JSON.stringify({
type: "challenge",
url: "https://example.com/protected",
proxy: "socks5://user:[email protected]:1080",
}),
});curl -X POST https://api.nslsolver.com/solve \
-H "Content-Type: application/json" \
-H "X-API-Key: $NSL_API_KEY" \
-d '{
"type": "challenge",
"url": "https://example.com/protected",
"proxy": "http://user:[email protected]:8080"
}'Keeping cf_clearance valid
Cloudflare binds the clearance cookie to the egress IP and the User-Agent used during the solve. To keep it usable:
- Send the solve through your real proxy (the same one your downstream traffic goes through).
- Capture the returned
user_agent. - On the downstream request: set the
cf_clearancecookie, use that exactUser-Agent, and route through the same proxy.
A mismatch on any of those three points invalidates the cookie immediately.
Common 400 messages
{ "success": false, "error": "proxy format must be protocol://[user:pass@]host:port" }
{ "success": false, "error": "proxy port must be 1-65535" }
{ "success": false, "error": "proxy must not target internal addresses" }Fix the URL and resend.
Rotation
For high volume, rotate across a pool of proxies. The simplest approach is round-robin:
from itertools import cycle
proxies = cycle([
"http://u:p@proxy1:8080",
"http://u:p@proxy2:8080",
"http://u:p@proxy3:8080",
])
def solve_payload(site_key, url):
return {
"type": "turnstile",
"site_key": site_key,
"url": url,
"proxy": next(proxies),
}For Challenge solves you'll need to track which proxy issued each clearance cookie, since replaying requires the same IP.