Skip to main content

arequest

Fast async HTTP client for Python with real browser fingerprints.

PyPI version Python versions License: MIT

arequest combines a requests-style API with curl-impersonate powered browser impersonation. Requests go out over asyncio through libcurl's multiplexed connection engine, carrying a byte-exact browser fingerprint on every layer:

Layer What matches a real browser
TLS ClientHello ciphers/extensions/order → JA3 / JA4 hashes
HTTP/2 SETTINGS, WINDOW_UPDATE, priorities → Akamai fingerprint
Headers Full sec-ch-ua, sec-fetch-*, header order and casing

Verified against tls.peet.ws - impersonating chrome produces Chrome's exact JA4 (t13d1516h2_8daaf6152771_d8a2da3f94cd) and Akamai fingerprints.


Why arequest

  • Undetectable by default - sessions impersonate the latest Chrome unless told otherwise
  • Requests-like syntax - Session, get/post/put/delete/..., familiar kwargs
  • Async-native - built on asyncio; no thread pools, no blocking calls
  • Fast - libcurl multi-connection pooling, keep-alive, HTTP/2 multiplexing
  • Stable under load - per-host connection limits, retries with backoff, rate limiting
  • Batteries included - cookies, redirects, proxies, streaming, auth, hooks

Installation

pip install arequest

Windows: for best performance, run your event loop with the selector policy: asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())


Quick Start

Simple request

import asyncio
import arequest

async def main():
    response = await arequest.get("https://httpbin.org/get")
    print(response.status_code)
    print(response.json())

asyncio.run(main())
import asyncio
import arequest

async def main():
    async with arequest.Session(impersonate="chrome") as session:
        response = await session.get("https://tls.peet.ws/api/all")
        data = response.json()
        print("JA4:", data["tls"]["ja4"])          # identical to real Chrome
        print("HTTP/2:", data["http_version"])     # h2

asyncio.run(main())

Concurrent requests

import asyncio
import arequest

async def main():
    async with arequest.Session() as session:
        urls = [f"https://httpbin.org/get?i={i}" for i in range(100)]
        responses = await session.bulk_get(urls)
        print(f"{sum(r.ok for r in responses)}/{len(responses)} succeeded")

asyncio.run(main())

Browser Impersonation

Pass an impersonate profile to make requests indistinguishable from that browser at the TLS and HTTP layers.

# Session-wide
session = arequest.Session(impersonate="chrome")

# Per-request override
await session.get(url, impersonate="safari184")

# Disable impersonation entirely
session = arequest.Session(impersonate=None)

List available profiles:

print(arequest.available_profiles())   # ('chrome', 'chrome100', ..., 'safari184', ...)

Aliases like "latest", "chrome_android", "safari_ios" also work.

Advanced fingerprint control

Power users can supply raw fingerprints instead of profiles:

response = await arequest.get(
    url,
    ja3="771,4865-4866-4867-49195-49199-...,0-23-65281-...,29-23-24,0",
    akamai="1:65536;2:0;4:6291456;6:262144|15663105|0|m,a,s,p",
    extra_fp={"tls_signature_algorithms": ["ecdsa_secp256r1_sha256", ...]},
)

Verify any setup against a live echo endpoint:

r = await arequest.get("https://tls.peet.ws/api/all")
r.json()["tls"]["ja3_hash"]      # server-observed JA3
r.json()["http2"]["akamai_fingerprint_hash"]

Usage Guide

All HTTP methods

async with arequest.Session() as s:
    await s.get(url)
    await s.post(url, json={"key": "value"})
    await s.put(url, data="raw body")
    await s.patch(url, json={"update": "field"})
    await s.delete(url)
    await s.head(url)
    await s.options(url)

Query params, headers, forms, files

await s.get(url, params={"page": 2, "limit": 10})
await s.get(url, headers={"Authorization": "Bearer <token>"})
await s.post(url, data={"username": "user", "password": "pass"})
await s.post(url, files={"upload": ("report.pdf", pdf_bytes, "application/pdf")})

Cookies

async with arequest.Session() as s:
    await s.get("https://httpbin.org/cookies/set/session/persisted")
    r = await s.get("https://httpbin.org/cookies")   # cookie sent automatically
    print(s.cookies)

Authentication

from arequest import BasicAuth, BearerAuth

await arequest.get(url, auth=BasicAuth("user", "pass"))
await arequest.get(url, auth=BearerAuth("<token>"))

Custom schemes: subclass arequest.AuthBase and implement apply(request).

Proxies

session = arequest.Session(proxies={"https": "http://proxy:8080"})
# or per-request
await session.get(url, proxy="socks5://user:pass@host:1080")

Environment proxies (HTTP_PROXY, HTTPS_PROXY, NO_PROXY) are honored by default.

Retries with backoff

session = arequest.Session(retries=3, backoff=0.5)
# or fine-grained control
from arequest import RetryPolicy

policy = RetryPolicy(
    total=5,                      # max retries per request
    backoff_factor=0.5,           # exponential base delay
    status_forcelist=frozenset((429, 500, 502, 503, 504)),
)
session = arequest.Session(retries=policy)

Retries honor Retry-After headers and only replay idempotent methods by default.

Rate limiting

session = arequest.Session(rate_limit=20.0, rate_limit_per_host=10.0)

Self-healing requests

Sessions automatically recover from common transient failures:

session = arequest.Session(
    # stale pooled keep-alive slots are silently re-dialled (default on)
    # disable with stale_retry=False
    block_rotation=True,                 # rotate identity on WAF challenge pages
    host_circuit_breaker=(5, 60.0),      # 5 failures -> 60s cool-down per host
    block_private_networks=True,         # SSRF guard (see below)
)
  • Stale-connection repair: a quiet server-side close mid-pool no longer surfaces as an error on idempotent requests - one transparent re-attempt fixes it without touching retry budgets.
  • Block rotation: small 403 responses carrying challenge markers (captcha, Cloudflare, DataDome, Kasada...) get exactly one re-attempt with a rotated User-Agent / proxy before normal handling resumes. The rotated identity is visible on response.request_info.headers.
  • Circuit breaker: flapping hosts are short-circuited locally so failing backends fail fast instead of burning timeouts.
  • SSRF guard (block_private_networks=True): rejects requests and redirect targets pointing at private/reserved networks - loopback, RFC1918, link-local cloud metadata endpoints (169.254.169.254), carrier-grade NAT - a defense-in-depth against attacker-controlled redirects reaching internal services. Off by default (local development uses 127.0.0.1); enable for anything fetching user-supplied URLs.

Streaming responses

async with arequest.Session(stream=True) as s:
    async with await s.get(large_file_url) as r:
        async for chunk in r.aiter_content(chunk_size=65536):
            process(chunk)

Bounded concurrent fetching

# Yields responses as they complete, never more than 20 in flight
async for r in session.iter_fetch(urls, max_concurrency=20):
    print(r.status_code)

WebSockets

async with arequest.Session(impersonate="chrome") as session:
    handle = await session.ws_connect("wss://example.com/socket")
    async with handle as ws:
        await ws.send_json({"type": "subscribe"})
        while True:
            message = await ws.recv_json()
            handle_message(message)

WebSocket connections carry the same browser fingerprint as HTTP requests.

Proxy pools

pool = arequest.ProxyPool(
    ["socks5://user:pass@p1:1080", "http://p2:8080", "http://p3:8080"],
    strategy="round_robin",   # or "random" / "failover"
    cooldown=300.0,           # seconds to skip a failing proxy
)
session = arequest.Session(proxy_pool=pool)

print(pool.status())   # {'socks5://***@p1:1080': True, 'http://p2:8080': False, ...}

Failed proxies are automatically put on cooldown and retried later. Credentials embedded in proxy URLs are masked in status() output so pools can be logged safely.

Human-like browsing

session = arequest.Session(
    impersonate="chrome",
    realistic_headers=True,     # coherent browser header set on every request
    user_agent_rotation="auto", # rotate UA strings derived from the profile
    think_time=(0.5, 2.0),      # random human-like pause between requests
)
  • realistic_headers=True layers browser headers (Accept-Language, Sec-Fetch-*, Sec-CH-UA, ...) onto each request without overriding headers you set explicitly.
  • user_agent_rotation also accepts a single UA string or a list of them: user_agent_rotation=["UA-1", "UA-2"].
  • think_time also accepts a fixed number of seconds: think_time=1.0.
  • Combine with rate_limit for hard throughput caps plus human-like pacing.

Session persistence

# Save cookies + settings to resume later
await session.save("state.json")

# Restore exactly where you left off
session = await arequest.Session.load("state.json")

Redirect control

await s.get(url, allow_redirects=False)       # don't follow
await s.get(url, max_redirects=5)             # custom limit
r.history                                     # intermediate responses

Timeouts

await s.get(url, timeout=5.0)                       # total seconds
await s.get(url, timeout=(3.0, 10.0))               # connect, read

Error handling

try:
    r = await s.get("https://httpbin.org/status/404")
    r.raise_for_status()
except arequest.ClientError as e:
    print(f"client error: {e.status_code}")
except arequest.ServerError as e:
    print(f"server error: {e.status_code}")
except arequest.TimeoutError:
    print("timed out")
except arequest.ConnectionError:
    print("connection failed")

Exception hierarchy: RequestError → TransportError (ConnectionError, TimeoutError, ProxyError, SSLError) / HTTPError (ClientError, ServerError) / InvalidURL / TooManyRedirects / ImpersonationError.


API Overview

Response

r.status_code      # int
r.ok               # bool - status < 400
r.headers          # case-insensitive dict
r.content          # bytes
r.text             # str
r.json()           # parsed body
r.encoding         # detected / forced encoding
r.url              # final URL after redirects
r.elapsed          # seconds
r.cookies          # cookies received with this response
r.history          # redirect chain
r.is_redirect      # bool - 3xx with Location header
r.attempts         # attempts used (retries included)
r.raise_for_status()
r.aclose()         # release body / connection early
r.aiter_content()  # async streaming
r.aiter_lines()    # async line iterator

Session options

session = arequest.Session(
    headers={"User-Agent": "my-app"},   # merged over impersonation defaults
    timeout=30.0,
    connector_limit=100,
    connector_limit_per_host=0,
    verify=True,
    impersonate="chrome",
    http_version="auto",                # auto | h1 | h2 | h3
    retries=0,
    backoff=None,
)

Every option can be overridden per request.

Top-level helpers

arequest.request(method, url, ...), get, post, put, patch, delete, head, options, aclose() - each uses an implicit per-loop session.


Performance Notes

  • Connections are pooled per origin and reused across requests (keep-alive).
  • HTTP/2 is negotiated automatically where supported; one multiplexed connection serves many concurrent requests.
  • Tune connector_limit / connector_limit_per_host for your workload.
  • On Linux/macOS, pip install arequest[uvloop] speeds up the event loop.

Benchmarks live in tests/benchmarks/.


Development

git clone https://github.com/abhrajyoti-01/arequest.git
cd arequest
pip install -e .[dev]

pytest                # run tests
ruff check src/ tests/
ruff format src/      # format

License

MIT - see LICENSE.

Author

Abhra - @abhrajyoti-01

Release files for arequest 2.4.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for arequest 2.4.1
File Size Uploaded
arequest-2.4.1.tar.gz 62.2 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for arequest 2.4.1
File Interpreter ABI Platform
arequest-2.4.1-py3-none-any.whl Python 3 none any Details

Total release size: 98.1 kB

Release files / arequest-2.4.1.tar.gz

Download URL arequest-2.4.1.tar.gz
Size 62.2 kB
Tags Source
SHA-256 checksum
How to use checksums
3cf1eb00a0fb74d1c05b0d23b67d6bfc7458cfc338935f84c8cbd135bfd087f6
BLAKE2b-256 checksum
How to use checksums
2882e3a4dabbf9c34ef1525357aa304ac15335604118417ac3968e947e98f0ce
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 25, 2026.

Transparency log

Release files / arequest-2.4.1-py3-none-any.whl

Download URL arequest-2.4.1-py3-none-any.whl
Size 35.9 kB
Tags Python 3
SHA-256 checksum
How to use checksums
9a247ec5cf5c2a5083d07405a197aea18bc1aceee1b9dc87fd52900bc80dae9a
BLAKE2b-256 checksum
How to use checksums
c98d542960c3d0ce436ddb33d7d93f2fed76fbee2bc1238e23b8c3320cc2d2cc
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 25, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

2.4.1 This release

2 release files

2.3.0

2 release files

2.2.0

2 release files

2.0.0

2 release files

1.2.0

2 release files

1.1.0

2 release files

1.0.9

2 release files

1.0.8

2 release files

1.0.7

2 release files

1.0.6

2 release files

1.0.5

2 release files

1.0.4

2 release files

1.0.3

2 release files

1.0.2

2 release files

1.0.1

2 release files

1.0.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page