tlshttp
Modern Python wrapper for tls-client with an httpx-like API and browser TLS fingerprinting.
Features
- httpx-like API - Familiar
Client/AsyncClientpattern - Browser TLS fingerprinting - 50+ profiles (Chrome, Firefox, Safari, Opera)
- Auto-download binaries - No manual setup required
- True async support - Using anyio (works with asyncio and trio)
- Fixes common wrapper bugs:
cookies.clear()actually works- No memory leaks (proper cleanup with context managers)
- Case-insensitive headers (RFC 7230 compliant)
Installation
pip install tlshttp
Or with uv:
uv add tlshttp
Quick Start
Synchronous
import tlshttp
with tlshttp.Client() as client:
response = client.get("https://httpbin.org/get")
print(response.json())
Asynchronous
import asyncio
import tlshttp
async def main():
async with tlshttp.AsyncClient() as client:
response = await client.get("https://httpbin.org/get")
print(response.json())
asyncio.run(main())
Client Configuration
client = tlshttp.Client(
# Browser fingerprint profile
profile="chrome_120", # Default
# Request defaults
timeout=30.0,
follow_redirects=True,
# Proxy support
proxy="http://user:pass@proxy.example.com:8080",
# TLS settings
verify=True, # Verify SSL certificates
http2=True, # Enable HTTP/2
# Default headers for all requests
headers={"User-Agent": "MyApp/1.0"},
# Default cookies
cookies={"session": "abc123"},
# Base URL for relative paths
base_url="https://api.example.com",
)
HTTP Methods
All standard HTTP methods are supported:
with tlshttp.Client() as client:
# GET
response = client.get("https://httpbin.org/get")
# POST with JSON
response = client.post(
"https://httpbin.org/post",
json={"key": "value"}
)
# POST with form data
response = client.post(
"https://httpbin.org/post",
data={"username": "john", "password": "secret"}
)
# PUT
response = client.put(
"https://httpbin.org/put",
json={"updated": True}
)
# PATCH
response = client.patch(
"https://httpbin.org/patch",
json={"field": "new_value"}
)
# DELETE
response = client.delete("https://httpbin.org/delete")
# HEAD
response = client.head("https://httpbin.org/get")
# OPTIONS
response = client.options("https://httpbin.org/get")
Request Parameters
Query Parameters
response = client.get(
"https://httpbin.org/get",
params={"page": 1, "limit": 10}
)
# Requests: https://httpbin.org/get?page=1&limit=10
Headers
response = client.get(
"https://httpbin.org/headers",
headers={"Authorization": "Bearer token123"}
)
JSON Body
response = client.post(
"https://httpbin.org/post",
json={"message": "Hello", "count": 42}
)
Form Data
response = client.post(
"https://httpbin.org/post",
data={"username": "john", "password": "secret"}
)
Raw Content
response = client.post(
"https://httpbin.org/post",
content=b"raw bytes here"
)
Timeout
# Per-request timeout (overrides client default)
response = client.get(
"https://httpbin.org/delay/5",
timeout=10.0
)
Authentication
# Basic auth - httpx-style tuple
response = client.get(
"https://httpbin.org/basic-auth/user/pass",
auth=("user", "pass")
)
Response Object
response = client.get("https://httpbin.org/get")
# Status
response.status_code # 200
response.is_success # True (2xx)
response.is_redirect # False (3xx)
response.is_client_error # False (4xx)
response.is_server_error # False (5xx)
response.is_error # False (4xx or 5xx)
# Content
response.content # Raw bytes
response.text # Decoded string (auto-detects encoding)
response.json() # Parse as JSON
# Headers (case-insensitive)
response.headers["Content-Type"]
response.headers["content-type"] # Same value
response.headers.get("X-Custom", "default")
# Cookies from response
response.cookies["session_id"]
# URL (final URL after redirects)
response.url
# HTTP version
response.http_version # "HTTP/2" or "HTTP/1.1"
# Raise exception for error status codes
response.raise_for_status() # Raises HTTPStatusError for 4xx/5xx
Cookies
Automatic Cookie Handling
Cookies are automatically stored and sent with subsequent requests:
with tlshttp.Client() as client:
# Server sets a cookie
client.get("https://httpbin.org/cookies/set/session/abc123")
# Cookie is automatically sent
response = client.get("https://httpbin.org/cookies")
print(response.json()) # {"cookies": {"session": "abc123"}}
Manual Cookie Management
# Set cookies on client
client.cookies["token"] = "xyz789"
client.cookies.set("user", "john", domain="example.com")
# Get cookies
session = client.cookies.get("session")
# Clear all cookies (actually works, unlike other wrappers!)
client.cookies.clear()
# Clear cookies for specific domain
client.cookies.clear(domain="example.com")
# Iterate cookies
for name, value in client.cookies.items():
print(f"{name}: {value}")
# Export to dict
all_cookies = client.cookies.to_dict()
Headers
Case-Insensitive Access
HTTP headers are case-insensitive per RFC 7230. tlshttp handles this correctly:
from tlshttp import Headers
headers = Headers({"Content-Type": "application/json"})
# All of these return the same value
headers["Content-Type"]
headers["content-type"]
headers["CONTENT-TYPE"]
Client Default Headers
client = tlshttp.Client(
headers={"User-Agent": "MyBot/1.0", "Accept": "application/json"}
)
# Per-request headers merge with (and override) client defaults
response = client.get(
"https://httpbin.org/headers",
headers={"Accept": "text/html"} # Overrides Accept, keeps User-Agent
)
Browser Profiles
tlshttp includes 50+ browser TLS fingerprint profiles that mimic real browsers:
Chrome
client = tlshttp.Client(profile="chrome_120") # Recommended default
client = tlshttp.Client(profile="chrome_131") # Newest
client = tlshttp.Client(profile="chrome_103") # Oldest supported
Available: chrome_103 through chrome_131, plus PSK variants like chrome_120_psk
Firefox
client = tlshttp.Client(profile="firefox_120")
client = tlshttp.Client(profile="firefox_133") # Newest
client = tlshttp.Client(profile="firefox_102") # Oldest
Available: firefox_102 through firefox_133
Safari
client = tlshttp.Client(profile="safari_16_0")
client = tlshttp.Client(profile="safari_ios_16_0")
client = tlshttp.Client(profile="safari_ios_17_0")
Opera
client = tlshttp.Client(profile="opera_89")
client = tlshttp.Client(profile="opera_90")
client = tlshttp.Client(profile="opera_91")
List All Profiles
from tlshttp.profiles import BROWSER_PROFILES
for name in sorted(BROWSER_PROFILES.keys()):
print(name)
Async Client
The AsyncClient has the same API as Client, but all methods are async:
import asyncio
import tlshttp
async def main():
async with tlshttp.AsyncClient(profile="firefox_120") as client:
# Single request
response = await client.get("https://httpbin.org/get")
# Concurrent requests
urls = [
"https://httpbin.org/get?id=1",
"https://httpbin.org/get?id=2",
"https://httpbin.org/get?id=3",
]
tasks = [client.get(url) for url in urls]
responses = await asyncio.gather(*tasks)
for response in responses:
print(response.json()["args"]["id"])
asyncio.run(main())
Concurrency Limit
Control the maximum number of concurrent requests:
# Limit to 5 concurrent requests (default: 10)
client = tlshttp.AsyncClient(max_concurrent=5)
Proxy Support
# HTTP proxy
client = tlshttp.Client(proxy="http://proxy.example.com:8080")
# With authentication
client = tlshttp.Client(proxy="http://user:pass@proxy.example.com:8080")
# SOCKS5 proxy
client = tlshttp.Client(proxy="socks5://proxy.example.com:1080")
Error Handling
import tlshttp
try:
with tlshttp.Client(timeout=5.0) as client:
response = client.get("https://httpbin.org/delay/10")
response.raise_for_status()
except tlshttp.TimeoutError:
print("Request timed out")
except tlshttp.ConnectError:
print("Failed to connect")
except tlshttp.HTTPStatusError as e:
print(f"HTTP {e.response.status_code}: {e.response.text}")
except tlshttp.RequestError as e:
print(f"Request failed: {e}")
Exception Hierarchy
TLSHTTPError (base)
├── RequestError
│ ├── ConnectError
│ ├── TimeoutError
│ └── ProxyError
└── HTTPStatusError (raised by raise_for_status())
Comparison with Other Libraries
| Feature | tlshttp | requests | httpx | curl_cffi | tls-client |
|---|---|---|---|---|---|
| TLS Fingerprinting | ✅ 50+ | ❌ | ❌ | ✅ | ✅ |
| Async Support | ✅ | ❌ | ✅ | ✅ | ❌ |
| HTTP/2 | ✅ | ❌ | ✅ | ✅ | ✅ |
| httpx-like API | ✅ | ❌ | ✅ | ❌ | ❌ |
| Cookie clear() works | ✅ | ✅ | ✅ | ✅ | ❌ |
| No memory leaks | ✅ | ✅ | ✅ | ✅ | ❌ |
| Auto-download binary | ✅ | N/A | N/A | ✅ | ❌ |
How It Works
tlshttp wraps the bogdanfinn/tls-client Go library via ctypes. On first use, it automatically downloads the appropriate binary for your platform from the tls-client GitHub releases.
The Go library provides:
- Accurate browser TLS fingerprints (JA3, JA4, HTTP/2 settings, header order)
- HTTP/2 and HTTP/3 support
- Connection pooling
tlshttp adds:
- Pythonic httpx-like API
- Proper memory management (context managers, weak reference finalizers)
- Case-insensitive headers (RFC 7230)
- True async support via anyio thread pool
- Bug fixes for issues in other wrappers
Platform Support
| Platform | Architecture | Supported |
|---|---|---|
| Linux | x86_64 | ✅ |
| Linux | ARM64 | ✅ |
| Linux (Alpine/musl) | x86_64 | ✅ |
| macOS | x86_64 (Intel) | ✅ |
| macOS | ARM64 (M1/M2/M3) | ✅ |
| Windows | x86_64 | ✅ |
| Windows | x86 (32-bit) | ✅ |
Development
# Clone
git clone https://github.com/Sekinal/tlshttp.git
cd tlshttp
# Install with dev dependencies
uv sync --group dev
# Run all tests
uv run pytest
# Skip integration tests (no network needed)
uv run pytest -m "not integration"
# Run specific test file
uv run pytest tests/test_headers.py -v
# Run with coverage
uv run pytest --cov=tlshttp
License
MIT License - see LICENSE for details.
This project uses binaries from tls-client which is licensed under BSD-4-Clause.
Credits
- bogdanfinn/tls-client - The Go TLS client library
- httpx - API design inspiration
Metadata
Release files for tlshttp 0.2.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| tlshttp-0.2.0.tar.gz | 42.6 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| tlshttp-0.2.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 75.0 kB
Release files / tlshttp-0.2.0.tar.gz
| Download URL | tlshttp-0.2.0.tar.gz |
|---|---|
| Size | 42.6 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
eba6031d0aa5db65f1d8158b7db77e9007f70029a35c662b377d8a3574a20189
|
|
BLAKE2b-256 checksum How to use checksums |
820f6bdc4d2c6cbd75c1e5c8b886b5b36fb8a218de0124ce1ad082a15f40deba
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.7
|
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 Jan 9, 2026.
Transparency logRelease files / tlshttp-0.2.0-py3-none-any.whl
| Download URL | tlshttp-0.2.0-py3-none-any.whl |
|---|---|
| Size | 32.4 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
cd64e6ccc618f4befed95d330fc1e3d5856451e9dcaec6b859ddf936530cc83a
|
|
BLAKE2b-256 checksum How to use checksums |
356adaf84ccb1a2e9e812abbf84cdd2113276c72ae22cdf14d13f97364dbde2c
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.7
|
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 Jan 9, 2026.
Transparency log