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

headers

dict

HTTP headers to send

cookies

dict or CookieJar

Cookies to include

params

dict

Query parameters appended to the URL

data

dict, str, or bytes

Form-encoded or raw body

json

dict

JSON body (alias for json_data)

json_data

dict

JSON body (auto-serialized, sets Content-Type)

files

dict

File uploads as multipart/form-data

auth

(user, password)

HTTP Basic authentication tuple

proxy

str

Proxy URL (http://, socks5://, etc.)

timeout

int

Request timeout in seconds

ja3

str

JA3 TLS fingerprint string

ja4r

str

JA4R fingerprint (raw format)

http2_fingerprint

str

HTTP/2 fingerprint string

quic_fingerprint

str

QUIC fingerprint string

fingerprint

str or TLSFingerprint

Named browser profile

user_agent

str

User-Agent header

disable_grease

bool

Disable GREASE extensions

force_http1

bool

Force HTTP/1.1

force_http3

bool

Force HTTP/3

insecure_skip_verify

bool

Skip TLS certificate verification

disable_redirect

bool

Disable automatic redirects

header_order

list[str]

Custom header ordering

order_headers_as_provided

bool

Preserve header insertion order

enable_connection_reuse

bool

Enable connection pooling

server_name

str

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

request(method, url, **kwargs)

Send a request with an explicit HTTP method

get(url, **kwargs)

GET request

post(url, data=, json=, json_data=, **kwargs)

POST request

put(url, data=, json=, json_data=, **kwargs)

PUT request

patch(url, data=, json=, json_data=, **kwargs)

PATCH request

delete(url, **kwargs)

DELETE request

head(url, **kwargs)

HEAD request

options(url, **kwargs)

OPTIONS request

arequest(method, url, **kwargs)

Async request

close()

Close the client

aclose()

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

base_url

str or None

Prepended to relative URLs (e.g. "/path" becomes "https://api.example.com/path")

auth

(user, password) or None

Default HTTP Basic auth for all requests

Attributes

Attribute

Type

Description

cookies

CookieJar

Persistent cookie jar

headers

CaseInsensitiveDict

Persistent headers sent with every request

base_url

str or None

Base URL for relative paths

auth

(str, str) or None

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

name

str

Unique identifier

ja3

str

JA3 fingerprint string

ja4r

str or None

JA4R fingerprint

http2_fingerprint

str or None

HTTP/2 fingerprint

quic_fingerprint

str or None

QUIC fingerprint

user_agent

str or None

User-Agent header

header_order

list[str] or None

Header ordering

disable_grease

bool

Disable GREASE extensions

force_http1

bool

Force HTTP/1.1

force_http3

bool

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"

CookieJar

A container for cookies with Python collection protocols:

from cycletls import CookieJar

jar = CookieJar([...])
"session_id" in jar      # True/False
len(jar)                 # number of cookies
repr(jar)                # <CookieJar(3 cookies)>