Why curl_cffi exists
Python's requests library is built on urllib3 → Python's ssl module → OpenSSL with Python's default cipher list. That list has been stable for years. Every commercial anti-bot has its JA3/JA4 hash catalogued. You can spoof every header, every cookie, every URL — the TLS handshake gives you away first.
curl-impersonate solves this by patching curl to use BoringSSL (Chrome's TLS library) with Chrome's exact cipher list, extension order, GREASE values, and HTTP/2 SETTINGS. The result is a JA4 indistinguishable from real Chrome. curl_cffi is the Python binding to that patched curl, exposed through a requests-compatible API.
What changes when you flip the switch
A single impersonate argument changes:
- TLS cipher suite list and order
- TLS extensions (ALPN, SNI, supported_groups, signature_algorithms, etc.) and their order
- GREASE values (Chrome's randomised dummy extensions)
- HTTP/2 SETTINGS frame contents (HEADER_TABLE_SIZE, INITIAL_WINDOW_SIZE, MAX_CONCURRENT_STREAMS)
- Header order and casing
Every one of those is fingerprinted by modern anti-bots. Forgetting any one (the most common mistake: spoofing User-Agent without TLS) flags you faster than no spoofing at all because the mismatch itself is a signal.
What curl_cffi is and is not for
Use it when: the target is protected by a TLS-fingerprinting WAF (Cloudflare without Turnstile, DataDome XHR endpoints, medium-strength Akamai), or you are tired of requests getting flagged by every commercial anti-bot. About 60–80% of protected targets are clear with curl_cffi + a residential proxy.
Do not use it when: the page requires JS execution (Cloudflare Turnstile, Akamai sensor.js, F5 Shape custom VM). For those you need a real browser — Camoufox, CloakBrowser, or a managed API.
