API Usage¶
Module-Level Functions¶
The simplest way to use CycleTLS. These functions manage a global session automatically – no setup or teardown required.
Making Requests¶
import cycletls
response = cycletls.get("https://example.com")
response = cycletls.post("https://example.com", json={"key": "value"})
response = cycletls.put("https://example.com", json={"key": "value"})
response = cycletls.patch("https://example.com", json={"field": "new"})
response = cycletls.delete("https://example.com")
response = cycletls.head("https://example.com")
response = cycletls.options("https://example.com")
# Generic method
response = cycletls.request("GET", "https://example.com")
Async Variants¶
Every sync function has an a-prefixed async counterpart:
import asyncio
import cycletls
async def main():
response = await cycletls.aget("https://example.com")
response = await cycletls.apost("https://example.com", json={"key": "value"})
response = await cycletls.aput("https://example.com", json={"key": "value"})
response = await cycletls.apatch("https://example.com", json={"field": "new"})
response = await cycletls.adelete("https://example.com")
response = await cycletls.ahead("https://example.com")
response = await cycletls.aoptions("https://example.com")
asyncio.run(main())
Configuration¶
import cycletls
# Set defaults that apply to all subsequent requests
cycletls.set_default(
proxy="socks5://127.0.0.1:9050",
timeout=10,
ja3="771,4865-4866-4867-49195-49199-49196-49200-52393-52392-49171-49172-156-157-47-53,0-23-65281-10-11-35-16-5-13-18-51-45-43-27-17513,29-23-24,0",
user_agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
)
# Read a default back
timeout = cycletls.get_default("timeout") # 10
# Reset all defaults
cycletls.reset_defaults()
# Manually close the global session (useful in notebooks)
cycletls.close_global_session()
Request Parameters¶
All request methods (get, post, etc.) accept these keyword arguments:
Parameter |
Type |
Description |
|---|---|---|
|
|
HTTP headers to send |
|
|
Cookies to include |
|
|
Query parameters appended to the URL |
|
|
Form-encoded or raw body |
|
|
JSON body (alias for |
|
|
JSON body (auto-serialized, sets Content-Type) |
|
|
File uploads as multipart/form-data |
|
|
HTTP Basic authentication tuple |
|
|
Proxy URL ( |
|
|
Request timeout in seconds |
|
|
JA3 TLS fingerprint string |
|
|
JA4R fingerprint (raw format) |
|
|
HTTP/2 fingerprint string |
|
|
QUIC fingerprint string |
|
|
Named browser profile |
|
|
User-Agent header |
|
|
Disable GREASE extensions |
|
|
Force HTTP/1.1 |
|
|
Force HTTP/3 |
|
|
Skip TLS certificate verification |
|
|
Disable automatic redirects |
|
|
Custom header ordering |
|
|
Preserve header insertion order |
|
|
Enable connection pooling |
|
|
Custom SNI (Server Name Indication) |
Response Object¶
Every request returns a Response object:
response = cycletls.get("https://httpbin.org/get")
# Status
response.status_code # 200
response.ok # True (status 200-399)
response.is_error # False
response.reason # "OK"
# Body
response.text # Response body as str
response.content # Response body as bytes
response.json() # Parse body as JSON
# Headers (case-insensitive)
response.headers["Content-Type"]
response.headers.get("content-type") # same key, case-insensitive
# Cookies
response.cookies["session_id"]
# URL after redirects
response.url
# Encoding
response.encoding # "utf-8"
# Raise on error status
response.raise_for_status() # raises HTTPError if status >= 400
CycleTLS Client¶
For explicit client lifetime management:
from cycletls import CycleTLS
# Context manager (recommended)
with CycleTLS() as client:
response = client.get("https://example.com")
response = client.post("https://example.com", json={"key": "value"})
# Async context manager
async with CycleTLS() as client:
response = await client.arequest("GET", "https://example.com")
await client.aclose()
Methods¶
Method |
Description |
|---|---|
|
Send a request with an explicit HTTP method |
|
GET request |
|
POST request |
|
PUT request |
|
PATCH request |
|
DELETE request |
|
HEAD request |
|
OPTIONS request |
|
Async request |
|
Close the client |
|
Async close (httpx-compatible) |
Session¶
Session extends CycleTLS with persistent cookies, headers, base URL, and
authentication:
from cycletls import Session
with Session(base_url="https://api.example.com", auth=("user", "pass")) as s:
# Persistent headers
s.headers["X-Custom"] = "value"
# Login -- response cookies are saved automatically
s.post("/login", json={"user": "admin"})
# Subsequent requests include saved cookies and headers
profile = s.get("/profile")
# Per-request auth overrides session auth
admin = s.get("/admin", auth=("admin", "admin_pass"))
Constructor¶
Session(base_url=None, auth=None)
Parameter |
Type |
Description |
|---|---|---|
|
|
Prepended to relative URLs (e.g. |
|
|
Default HTTP Basic auth for all requests |
Attributes¶
Attribute |
Type |
Description |
|---|---|---|
|
|
Persistent cookie jar |
|
|
Persistent headers sent with every request |
|
|
Base URL for relative paths |
|
|
Default Basic auth credentials |
Async Session¶
Session also supports async methods:
import asyncio
from cycletls import Session
async def main():
async with Session(base_url="https://api.example.com") as s:
response = await s.arequest("GET", "/users")
# Cookies from async responses are persisted too
asyncio.run(main())
AsyncCycleTLS¶
A dedicated async client class:
import asyncio
from cycletls import AsyncCycleTLS
async def main():
async with AsyncCycleTLS() as client:
response = await client.get("https://example.com")
print(response.status_code)
asyncio.run(main())
Browser Fingerprint Profiles¶
Built-in Profiles¶
from cycletls import (
CHROME_120,
CHROME_121,
CHROME_ANDROID,
EDGE_120,
FIREFOX_121,
SAFARI_17,
SAFARI_IOS,
)
response = cycletls.get("https://example.com", fingerprint=CHROME_120)
TLSFingerprint¶
Create custom fingerprint profiles:
from cycletls import TLSFingerprint
profile = TLSFingerprint(
name="my_browser",
ja3="771,4865-4866-4867-49195...",
user_agent="Mozilla/5.0 ...",
http2_fingerprint="1:65536;2:0;4:131072;5:16384|12517377|0|m,p,a,s",
)
Field |
Type |
Description |
|---|---|---|
|
|
Unique identifier |
|
|
JA3 fingerprint string |
|
|
JA4R fingerprint |
|
|
HTTP/2 fingerprint |
|
|
QUIC fingerprint |
|
|
User-Agent header |
|
|
Header ordering |
|
|
Disable GREASE extensions |
|
|
Force HTTP/1.1 |
|
|
Force HTTP/3 |
FingerprintRegistry¶
Manage and look up profiles by name:
from cycletls import FingerprintRegistry, TLSFingerprint
registry = FingerprintRegistry()
# Register a custom profile
registry.register(my_profile)
# Look up by name
profile = FingerprintRegistry.get("chrome_120")
# List all registered profile names
for name in registry.all():
print(name)
Loading Profiles from Files¶
from cycletls import (
load_fingerprint_from_file,
load_fingerprints_from_dir,
load_fingerprints_from_env,
)
# Load a single profile from a JSON file
profile = load_fingerprint_from_file("profiles/chrome_125.json")
# Load all profiles from a directory (auto-registers them)
load_fingerprints_from_dir("profiles/")
# Load from CYCLETLS_FINGERPRINT_DIR environment variable
load_fingerprints_from_env()
Exceptions¶
All CycleTLS exceptions inherit from CycleTLSError, so you can catch
everything with a single except clause:
from cycletls import CycleTLSError
try:
response = cycletls.get("https://example.com")
except CycleTLSError as e:
print(f"Request failed: {e}")
Exception Hierarchy¶
CycleTLSError
+-- RequestException
| +-- HTTPError # 4xx/5xx status (from raise_for_status())
| +-- ConnectionError # Network connectivity failures
| +-- Timeout # Request exceeded timeout
| | +-- ConnectTimeout # (alias)
| | +-- ReadTimeout # (alias)
| +-- TooManyRedirects # Redirect limit exceeded
| +-- InvalidURL # Malformed URL
| +-- TLSError # TLS/SSL errors
| +-- ProxyError # Proxy connection errors
| +-- InvalidHeader # Invalid header name or value
+-- WebSocketError # WebSocket-specific errors
+-- SSEError # Server-Sent Events errors
WebSocket Client¶
from cycletls import WebSocketConnection
ws = WebSocketConnection("wss://echo.websocket.org")
ws.connect()
ws.send("hello")
message = ws.receive()
ws.close()
Server-Sent Events (SSE)¶
from cycletls import SSEConnection
sse = SSEConnection("https://example.com/events")
for event in sse.connect():
print(f"{event.event}: {event.data}")
Data Structures¶
CaseInsensitiveDict¶
A dictionary subclass for HTTP headers that allows case-insensitive key access:
from cycletls import CaseInsensitiveDict
headers = CaseInsensitiveDict({"Content-Type": "application/json"})
headers["content-type"] # "application/json"