Library / SDK
apify/impit avatar
apify/impit

impit: browser impersonation for HTTP requests from Rust, Node and Python

impit | rust library for browser impersonation

618 stars52 forksRustApache-2.0

At a glance

What is it?
impit is a Rust library that makes requests look like a real browser, wrapping reqwest with patched rustls and h2 so TLS fingerprints match. The catch is that it only builds with patched dependencies and an unstable reqwest feature flag, and it exposes 20 impersonation profiles against rivals that ship more.
Who is it for?
impit fits scraping work where a plain HTTP client gets blocked and where you want one Rust core shared by a Node or Python service rather than two separate implementations. It does not fit a project that wants a normal crates.io dependency, since the patched rustls and h2 plus the reqwest_unstable rustflag are mandatory and the build fails without them.
Can I use it commercially?
Yes. Apache-2.0 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 last received commits 1 day ago.
What is it written in?
Mainly Rust, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.

Editorial analysis

What impit impersonates and what it wraps

impit makes HTTP requests that look like they came from a real browser. The point is not user agent strings, which any client can copy, but the lower layer of the connection, where the TLS handshake and HTTP/2 settings are what many bot filters actually inspect.

Underneath, the project describes itself as a somewhat thin wrapper around reqwest. The substance comes from patched versions of rustls and other libraries, which is what allow the handshake to resemble a browser's. The stack is reqwest for the HTTP client, rustls for TLS, and tokio for the async runtime. HTTP/1.1, HTTP/2 and HTTP/3 are all supported.

The API surface stays small on purpose. You build an Impit instance from a builder, optionally attach a browser fingerprint, enable HTTP/3, and then call get with a URL. Proxies, custom timeouts and header overrides are also available, according to the project description.

The library is not the only artefact. The repository is a Cargo workspace with three members, impit, impit-node and impit-python, so the same Rust core is exposed to Node and Python callers.

The Rust API and where the fingerprint comes from

A request starts by constructing an Impit instance with a cookie jar type parameter and, optionally, a fingerprint taken from the built-in fingerprint database. The shortest useful program looks like this:

rust
use impit::cookie::Jar;
use impit::{impit::Impit, fingerprint::database as fingerprints};

#[tokio::main]
async fn main() {
    let impit = Impit::<Jar>::builder()
        .with_fingerprint(fingerprints::firefox_144::fingerprint())
        .with_http3()
        .build()
        .unwrap();

    let response = impit
        .get(String::from("https://example.com"), None, None)
        .await;

    match response {
        Ok(response) => {
            println!("{}", response.text().await.unwrap());
        }
        Err(e) => {
            println!("{:#?}", e);
        }
    }
}

Three details are worth pausing on. The fingerprint comes from a fingerprint database exposed as a module, so it is a compiled-in value rather than something fetched at runtime, and the example names a specific Firefox version. The jar type parameter selects the cookie store, with Jar as the shown case. The get call takes the URL plus two optional arguments, so extra request parameters such as headers or options are passed there rather than through the builder.

Because the whole call returns a Result, errors surface to the caller rather than being swallowed, and the example prints the debug representation on failure. The library is designed for a tokio runtime, since the entry point is a tokio main attribute.

The patched dependency chain is mandatory, not optional

This is the part that will decide whether you can use impit at all. The project is blunt about it: without the patched dependencies, the project won't build.

Adding impit to a Rust project means pointing at the git repository and patching three crates in your own Cargo.toml:

toml
[dependencies]
impit = { git="https://github.com/apify/impit.git", branch="master" }

[patch.crates-io]
rustls = { git="https://github.com/apify/rustls.git" }
h2 = { git="https://github.com/apify/h2.git" }

The workspace's own Cargo.toml shows how wide that patch set is in practice. It pins revs for h2, rustls, tower-http, hyper-util and a fork of reqwest itself, each pointing at a repository under the apify organisation. The release profile in the same file strips symbols, sets opt-level to 3, turns on lto and sets codegen-units to 1, which tells you the artefact size and build time are treated as shipping concerns.

There is a second mandatory step. Your project has to be built with rustflags = "--cfg reqwest_unstable", otherwise the build fails, because impit relies on unstable reqwest features, namely http3 support, that stable reqwest does not expose.

The practical cost is a dependency tree you cannot get from a package registry, and a build that depends on git remotes being reachable. Anyone whose build has to be reproducible without network access to GitHub, or whose organisation forbids vendored forks, will hit a wall here.

impit in Node and Python, one core with two skins

The Node binding is the more developed of the two. The project points Node users at the impit-node folder, or at the npm package:

bash
npm install impit

The interface is the same as the native fetch, which means most existing code needs only the import changed:

typescript
import { Impit } from 'impit';

// Set up the Impit instance
const impit = new Impit({
    browser: "chrome", // or "firefox"
    proxyUrl: "http://localhost:8080",
    ignoreTlsErrors: true,
});

// Use the `fetch` method as you would with the built-in `fetch` function
const response = await impit.fetch("https://example.com");

console.log(response.status);
console.log(response.headers);
console.log(await response.text());
// console.log(await response.json());
// ...

The options object shows the practical surface: a browser name to impersonate, a proxy URL, and a switch to ignore TLS errors. The response is handled the way a fetch Response is handled, with status, headers and a body reader.

The Python side ships as impit-python, a separate released package from the Node one. The release history shows the two tracks moving independently, py-0.15.0 on 2026-10-13 and js-0.14.6 on 2026-10-15, with py-0.14.1 on 2026-09-07. That staggered versioning is a hint about how you should plan upgrades: the Node and Python packages are not released in lockstep, so a service that uses both can end up on different underlying versions.

Where impit sits in the request-per-second table

The project carries its own benchmark table, and it is worth reading closely rather than skimming, because the ranking is not the one most people expect. The measurement is the median of 11 runs of 2000 sequential requests to a local HTTP/2 server with 1 KiB JSON responses over one warm connection. It was run on linux-x64 on 2026-10-01, and the project invites you to rerun the benchmarks on your own hardware.

On the Python side, primp leads at 3972 req/s, rnet follows at 3400, impit is at 3047, curl_cffi at 2450, tls-client at 2331, and httpx without impersonation at 1554. On Node, impit sits at 1978 req/s, ahead of got-scraping at 1690 and well ahead of cycletls at 396, while undici without impersonation reaches 3157.

Two things fall out of that. First, impit is not the fastest option in either ecosystem, and the gap to the leaders is large enough to matter if throughput is your constraint. Second, the same table lists profiles per package, and here impit is the weakest of the impersonating libraries: 20 profiles against 75 for rnet, 51 for tls-client, 38 for curl_cffi and 3 for got-scraping. The profiling table and the speed table point in opposite directions, so which one matters depends on whether you chase throughput or browser coverage.

impit against curl_cffi, rnet and got-scraping

The alternatives differ in approach, not just in speed.

curl_cffi wraps libcurl in C and exposes 38 profiles. That gives it a broad fingerprint catalogue and long platform coverage, at the price of a 13.5 MB wheel against impit's 5.2 MB, and of a dependency on the libcurl C library in your Python environment.

rnet also has a Rust backend and reports 75 profiles, and it is faster than impit in the Python table at 3400 req/s. Its wheel is smaller at 3.7 MB. If you need both breadth and speed from a single Rust core, the table suggests rnet is ahead on both counts.

got-scraping takes a different route entirely. It uses Node.js TLS rather than a Rust core, exposes only 3 profiles, and installs at 4.8 MB, half the size of impit-node at 10.7 MB. That makes it the lighter option when you only need to look like a handful of browsers.

What impit itself offers against these is the workspace layout: one Rust core with a Node binding and a Python binding, the fetch compatible Node interface, and the HTTP/3 support that the other Rust entries in the table do not advertise.

When impit is the wrong choice

There are clear cases for choosing something else.

If you need a standard dependency from a package registry, impit does not fit. It is not consumed as a normal crates.io crate, because the patched rustls, h2, tower-http, hyper-util and reqwest forks have to be wired into your build, and the reqwest_unstable flag has to be set. The project says plainly that without the patched dependencies the build fails.

If you need broad browser coverage, the profile count is the problem. Twenty profiles is less than half of what rnet or tls-client offer in the same comparison, so a site that filters on a browser you cannot impersonate will still block you.

If you need the highest throughput per core, impit is behind primp and rnet in the Python table and behind undici in the Node table.

There is also a category of task where impersonation is the wrong tool altogether. impit does not render pages, it does not run JavaScript, and it does not solve the challenge of a site that requires a real browser environment. The project makes no claim to do those things.

Licence, release cadence and what the workspace tells you

impit is released under the Apache-2.0 licence, with the terms in LICENSE.md. That licence permits commercial use and modification, requires that you preserve copyright and licence notices, and asks you to state notable changes when redistributing. That is a description of the terms, and your own review is what decides how the obligations apply to your use.

The repository is not archived and the last push was on 2026-10-22, which puts the work in the present week. Release activity follows the same pattern, with tagged packages for both bindings in the middle of October 2026.

The workspace Cargo.toml is the most informative single file in the tree. Three members, five patched crates pinned to specific revisions, and a release profile tuned for small, fast binaries tell you this is a project that has to be built from source rather than consumed as a stock package. There is also a benchmarks/ directory in the tree, the same one the project credits for the numbers above, plus.cargo/ and.devcontainer/ entries that indicate a pinned toolchain and a containerised development environment.

For an adopter, that means the upgrade cost sits in re-pinning those git revisions and revalidating the fingerprints, not in bumping a version number.

Editorial conclusion

impit fits scraping work where a plain HTTP client gets blocked and where you want one Rust core shared by a Node or Python service rather than two separate implementations. It does not fit a project that wants a normal crates.io dependency, since the patched rustls and h2 plus the reqwest_unstable rustflag are mandatory and the build fails without them. Before committing, check that the fingerprint you need is among the 20 profiles impit exposes, confirm you can carry patched git dependencies in your build, and read the Apache-2.0 licence in LICENSE.md to see what the obligations are when you redistribute the library.

Frequently asked questions

How do I add impit to a Rust project?

Add impit as a git dependency and patch rustls and h2 to the apify forks in your Cargo.toml, then build with rustflags = "--cfg reqwest_unstable". The project states that without the patched dependencies the build fails, and that the unstable flag is required for http3 support.

How many browser profiles does impit support compared with other libraries?

The comparison table in the README lists 20 profiles for impit, against 75 for rnet, 51 for tls-client, 38 for curl_cffi and 3 for got-scraping. impit also exposes HTTP/3 support, which the other entries in that table do not advertise.

Is impit faster than curl_cffi or got-scraping?

Not according to the benchmark in the README, measured on 2026-10-01 as the median of 11 runs of 2000 sequential requests to a local HTTP/2 server. There impit reached 3047 req/s in Python against 2450 for curl_cffi, and 1978 req/s in Node against 1690 for got-scraping, while primp led the Python table at 3972.

Can I use impit from Node.js with the standard fetch interface?

Yes. Install the package with npm install impit, construct an Impit instance with options such as browser, proxyUrl and ignoreTlsErrors, and call impit.fetch. The interface is documented as the same as the native fetch.

What licence is impit released under?

impit is released under the Apache-2.0 licence, and the full text is in the LICENSE.md file at the repository root. The terms cover commercial use, modification and redistribution with notice and change requirements.

Official sources

  1. apify/impit on GitHub
  2. License: Apache-2.0
  3. Project website
  4. README
  5. Releases
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.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/apify-impit.svg)](https://hysenlabs.com/projects/apify-impit)

Was this useful?