curl_cffi: a Python HTTP client that impersonates browser TLS and HTTP/2 fingerprints
Python binding for curl-impersonate fork via cffi. A http client that can impersonate browser tls/ja3/http2 fingerprints.
At a glance
- What is it?
- curl_cffi wraps a curl-impersonate fork through cffi, so requests-style Python code can present a browser's JA3 and HTTP/2 fingerprint. It is the right tool for TLS-level blocks, and the wrong one for JavaScript challenges.
- Who is it for?
- Adopt curl_cffi when a site blocks your Python client at the TLS or HTTP/2 layer and you want to keep a requests-like API: install it with pip install curl_cffi, pass impersonate="chrome" on the first request, and confirm the returned ja3n_hash matches what you expect. Do not adopt it if the block is a JavaScript challenge or a CAPTCHA, because the README points to brimp for JavaScript challenges and the impersonation is transport-level only.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository received new commits within the last day.
- What is it written in?
- Mainly Python, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 2, 2026, and from our analysis. They are not legal advice.
Editorial analysis
The block that has nothing to do with your headers
A scraper can send a perfect User-Agent, correct Accept-Language, and cookies from a real session, and still get a 403 on the first request. The reason is usually below HTTP: the TLS ClientHello and the HTTP/2 frame ordering carry a fingerprint. The README states the target audience plainly: "If you are blocked by some website for no obvious reason, you can give curl_cffi a try." That is the whole pitch, and it is narrower than "a better requests".
The project is a Python binding for a curl-impersonate fork maintained under the same author, lexiforest/curl-impersonate, built through cffi. Because the impersonation lives in the patched libcurl rather than in Python, the fingerprint you present is the one the bundled curl build produces, not something reassembled in pure Python. The topics list on the repository names the scope directly: ja3, ja4, http2-fingerprint, http3-fingerprints, quic-fingerprints.
Who it is for: engineers writing Python scrapers or API clients against sites that fingerprint at the transport layer, and teams that already know requests and do not want to relearn an API. The README notes the library "Mimics the requests API, no need to learn another one."
How curl_cffi produces a browser fingerprint
There are two layers in the package. The low-level layer exposes curl itself through cffi bindings, and the high-level layer wraps that in a requests-like interface. The cffi module is declared in setup.py as cffi_modules=["scripts/build.py:ffibuilder"], which is where the compiled extension is generated.
The impersonate parameter is the switch. When you pass impersonate="chrome", the bundled library applies the TLS extensions, cipher ordering and HTTP/2 settings that the corresponding browser build uses. The README's own example prints the response from tls.browserleaks.com/json and shows a ja3n_hash field in the output, which is the observable result of that switch.
On top of the transport layer, the library advertises HTTP/2 and HTTP/3 support, with the README noting HTTP/3 support since v0.11.4 and HTTP/3 proxy plus fingerprints since v0.15.0. It also lists websocket support and native retry, both of which requests lacks. The comparison table in the README places curl_cffi alongside httpx and pycurl for HTTP/2, and alone among the listed clients for fingerprints.
One architectural consequence worth naming: because the impersonation is compiled in, adding a new browser version means shipping new binaries, not editing a Python file. The README's recent highlights mention support for the new algorithm and extensions in Chrome 150/152, which is the kind of change that arrives through a release rather than a config edit.
Installing curl_cffi and checking the fingerprint you actually send
The README's install path is a single pip command, and it states this works on Linux, macOS and Windows out of the box because the wheels are pre-compiled. There is no build step for the normal case.
pip install curl_cffi --upgradeOn macOS, the README also documents a Homebrew route through the author's tap.
brew install lexiforest/tap/curl-cffiIf you want a beta, the README gives pip install curl_cffi --upgrade --pre. Building from a GitHub checkout is a different path: clone the repository, run make preprocess, then pip install . The Makefile shows what preprocess does: it downloads the curl source archive, unpacks the curl-impersonate patches, applies curl.patch, and runs autoreconf -fi. Expect that route to need a working autotools toolchain.
For a first real check, the README's requests-like example is the shortest useful test. It calls an endpoint that echoes your TLS fingerprint back, so you can see whether impersonation took effect.
import curl_cffi
r = curl_cffi.get("https://tls.browserleaks.com/json", impersonate="chrome")
print(r.json())The response body is JSON containing a ja3n_hash field among others. Compare that hash against the same endpoint fetched from a real Chrome build, or against the value you get with a different impersonate target. If the hash is identical across two different impersonate values, the switch is not taking effect and you should check the version you installed.
Since v0.15 the package also ships a CLI called curl-cffi, which the README suggests for debugging a URL with the --impersonate option.
curl-cffi get tls.browserleaks.com/json --impersonate chromeThe README notes the CLI can serve as a web_fetch replacement for agents, and that the name is awkward to type, suggesting an alias. That CLI is the fastest way to confirm a fingerprint without writing a script.
Where curl_cffi stops: JavaScript, CAPTCHAs and the beta label
Impersonating TLS does not execute JavaScript. If a site issues a challenge that must run in a browser to produce a cookie, curl_cffi will receive the challenge page and nothing more. The README is explicit about the boundary: it points readers to brimp for solving JavaScript challenges, describing it as a lightweight browser that works like curl_cffi with JavaScript enabled. The commercial sponsors listed in the README sell token endpoints for Akamai, DataDome, Kasada and Incapsula, and the README frames them as "the missing piece" when TLS fingerprinting alone is not enough. That framing is the project's own admission of the limit.
The second caveat is maturity. pyproject.toml carries the classifier "Development Status :: 4 - Beta", and the most recent release listed is v0.16.4b1, a beta tag. The stable line is v0.16.3. If your deployment policy excludes pre-release dependencies, pin to the stable version rather than letting pip resolve to a beta.
The third is the Python floor. The README states Python 3.10 is the minimum since v0.14, and pyproject.toml sets requires-python = ">=3.10". On 3.9 or older this is not an option at all.
Finally, the impersonation is a moving target. Browser TLS stacks change, and the library has to follow. A fingerprint that works today can be stale after a browser update, which is a maintenance cost you inherit, not one you can configure away.
curl_cffi compared with httpx and tls_client
The closest general-purpose alternative is httpx. It supports HTTP/2 and both sync and async, and its API is well documented. The difference is that httpx does not impersonate fingerprints, which the README's comparison table marks with a cross for httpx in the fingerprints row. If your target does not fingerprint the TLS layer, httpx is the simpler dependency: it is pure Python, so there is no compiled extension in your build, and no browser version to track. Choosing curl_cffi for a site that never inspected your ClientHello means carrying a native dependency for nothing.
The other common comparison is tls_client, which is not covered in the README here, so treat that comparison as one to verify yourself rather than take on faith.
Against pycurl, the difference is the API and the packaging. pycurl is also a libcurl binding and also supports HTTP/2, but the README notes that for pycurl HTTP/3 is usually disabled at compile time by default, and pycurl does not impersonate fingerprints. curl_cffi's claim in the README is that it is "Much faster than requests/httpx, on par with aiohttp/pycurl", with a link to a benchmark directory in the repository. That claim is the project's own; the benchmark directory is where you would check the methodology before repeating it.
Against plain curl-impersonate, the difference is language. curl-impersonate is a curl distribution; curl_cffi is the Python binding to it. If you are writing Python, the binding is what gives you the requests-like API and asyncio support with proxy rotation on each request.
Maintenance, licensing and what an upgrade costs
The repository is not archived, and the last push was on 2026-09-20, two days before this writing. Releases are frequent: v0.16.3 on 2026-09-02, v0.16.3b1 on 2026-09-01, and v0.16.4b1 on 2026-09-20. The pattern of beta tags followed by stable tags suggests the project ships changes through a pre-release channel first, so a team that only tracks stable releases will see a lag behind the newest browser support.
The upgrade cost is mostly in the native layer. Because wheels are pre-compiled and abi3-tagged back to cp310, a normal pip install curl_cffi --upgrade is enough on CPython. The setup.py comments explain that the wheel tag is forced to cp310/abi3 except on Android and on free-threaded builds, which keep their original tags. If you are on a free-threaded Python build, expect the wheel situation to differ from the standard one.
Licensing is MIT, stated in both the README and pyproject.toml, with license-files = ["LICENSE"]. That is permissive and compatible with commercial use in the ordinary sense. The thing to actually check is not the Python package but the bundled libcurl and the curl-impersonate patches it is built from; the Makefile pins VERSION := 2.2.3 for the curl-impersonate fork and CURL_VERSION := curl-8_22_0 for upstream curl. Those upstream projects carry their own licences, and you should read them rather than rely on the MIT label on the wrapper. This is not legal advice; it is the set of files to hand to whoever reviews dependencies.
Support is split. The README directs commercial enquiries to impersonate.pro, and community channels are a Telegram group and a Discord server linked at the top of the README.
Editorial conclusion
Adopt curl_cffi when a site blocks your Python client at the TLS or HTTP/2 layer and you want to keep a requests-like API: install it with pip install curl_cffi, pass impersonate="chrome" on the first request, and confirm the returned ja3n_hash matches what you expect. Do not adopt it if the block is a JavaScript challenge or a CAPTCHA, because the README points to brimp for JavaScript challenges and the impersonation is transport-level only. Before committing, verify three things against your own target: which impersonate value the current release supports for the browser you need, whether your proxy setup is HTTP or SOCKS, and whether your Python version is 3.10 or newer, since that is the floor since v0.14.
Frequently asked questions
What is curl_cffi?
It is a Python binding for a curl-impersonate fork, built through cffi. Unlike pure Python clients such as requests or httpx, it can impersonate browsers' TLS/JA3 and HTTP/2 fingerprints, and it mimics the requests API.
How to install curl_cffi?
The README gives pip install curl_cffi --upgrade, which it says works on Linux, macOS and Windows out of the box. On macOS there is also a Homebrew route, brew install lexiforest/tap/curl-cffi, and beta releases install with the --pre flag.
How to use curl_cffi?
The README's requests-like example calls curl_cffi.get with an impersonate parameter, for example impersonate="chrome", against https://tls.browserleaks.com/json, and prints the JSON response. There is also a CLI called curl-cffi, added in v0.15, which takes a --impersonate option for debugging a URL.
What are the key differences between curl_cffi and aiohttp?
The README's comparison table lists both as supporting async, but only curl_cffi is marked as supporting fingerprints. The README also claims curl_cffi is much faster than requests and httpx and on par with aiohttp and pycurl, pointing to a benchmark directory in the repository.
curl_cffi vs httpx: which should I pick?
The README's table shows httpx supports HTTP/2 and both sync and async, but not fingerprints, while curl_cffi supports fingerprints as well. If your target does not inspect the TLS layer, httpx avoids a compiled native dependency; if it does, curl_cffi is the one with the impersonate parameter.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/lexiforest-curl-cffi)