# CycleTLS Python - Complete API Documentation

> Python HTTP client that impersonates real browsers - bypass anti-bot detection with TLS/JA3/JA4/HTTP2 fingerprinting. Unlike requests or httpx, CycleTLS makes your requests indistinguishable from real browser traffic.

CycleTLS is a high-performance Python HTTP client built on a Go backend, providing advanced TLS fingerprinting capabilities. Use it for web scraping, API access, and bypassing bot detection systems that rely on TLS fingerprinting (Cloudflare, Akamai, etc.).

## Installation

```bash
pip install cycletls
```

Or with uv:
```bash
uv add cycletls
```

Requirements: Python >=3.8

---

## Quick Start

### Simple API (Zero Boilerplate)

```python
import cycletls

# Basic GET request
response = cycletls.get('https://httpbin.org/get')
print(response.status_code)  # 200
print(response.json())

# POST with JSON
response = cycletls.post('https://httpbin.org/post', json_data={'key': 'value'})

# With TLS fingerprinting
response = cycletls.get(
    'https://ja3er.com/json',
    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'
)
```

### Context Manager Pattern

```python
from cycletls import CycleTLS

with CycleTLS() as client:
    response = client.get('https://httpbin.org/get')
    print(response.json())
```

### Async Pattern

```python
import asyncio
import cycletls

async def main():
    response = await cycletls.aget('https://httpbin.org/get')
    print(response.json())

asyncio.run(main())
```

---

## Module-Level Functions

All HTTP methods available as module-level functions:

### Synchronous Functions

```python
import cycletls

cycletls.get(url, **kwargs) -> Response
cycletls.post(url, data=None, json_data=None, **kwargs) -> Response
cycletls.put(url, data=None, json_data=None, **kwargs) -> Response
cycletls.patch(url, data=None, json_data=None, **kwargs) -> Response
cycletls.delete(url, **kwargs) -> Response
cycletls.head(url, **kwargs) -> Response
cycletls.options(url, **kwargs) -> Response
cycletls.request(method, url, **kwargs) -> Response
```

### Async Functions

```python
import cycletls

await cycletls.aget(url, **kwargs) -> Response
await cycletls.apost(url, data=None, json_data=None, **kwargs) -> Response
await cycletls.aput(url, data=None, json_data=None, **kwargs) -> Response
await cycletls.apatch(url, data=None, json_data=None, **kwargs) -> Response
await cycletls.adelete(url, **kwargs) -> Response
await cycletls.ahead(url, **kwargs) -> Response
await cycletls.aoptions(url, **kwargs) -> Response
await cycletls.async_request(method, url, **kwargs) -> Response
```

### Configuration Functions

```python
# Set default values for all requests
cycletls.set_default(
    proxy='socks5://127.0.0.1:9050',
    timeout=10,
    ja3='771,4865-4866-4867...',
    user_agent='Mozilla/5.0...',
    enable_connection_reuse=True
)

# Get a default value
value = cycletls.get_default('timeout')

# Reset all defaults
cycletls.reset_defaults()

# Manually close global session
cycletls.close_global_session()
```

---

## Request Parameters

All parameters available for any request method:

### URL and Method Parameters

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `url` | str | Required | Target URL |
| `method` | str | GET | HTTP method: GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS |
| `params` | dict | None | Query parameters to append to URL |

### Body Parameters

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `data` | dict/str/bytes | None | Request body (form data or raw) |
| `json_data` | dict | None | JSON request body (auto-serialized with Content-Type header) |
| `body_bytes` | bytes | None | Raw binary body |
| `files` | dict | None | File uploads (not yet implemented) |

### Header Parameters

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `headers` | dict | None | Custom HTTP headers |
| `user_agent` | str | None | User-Agent header (convenience parameter) |
| `header_order` | list[str] | None | Custom header ordering (e.g., ['accept', 'user-agent']) |
| `order_headers_as_provided` | bool | False | Use provided header order exactly |

### Cookie Parameters

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `cookies` | dict/list/CookieJar | None | Cookies to send with request |

### TLS Fingerprinting Parameters

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `ja3` | str | None | JA3 fingerprint string (format: TLSVersion,Ciphers,Extensions,EllipticCurves,ECPointFormats) |
| `ja4r` | str | None | JA4R raw format fingerprint (advanced TLS control) |
| `http2_fingerprint` | str | None | HTTP/2 Akamai fingerprint (format: settings\|window_update\|priority\|pseudo_header_order) |
| `quic_fingerprint` | str | None | QUIC fingerprint string |
| `disable_grease` | bool | False | Disable GREASE for exact JA4 matching |

### TLS Configuration Parameters

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `server_name` | str | None | Custom SNI (Server Name Indication) for domain fronting |
| `insecure_skip_verify` | bool | False | Skip TLS certificate verification |
| `tls13_auto_retry` | bool | False | Auto-retry with TLS 1.3 compatible curves |

### Protocol Parameters

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `force_http1` | bool | False | Force HTTP/1.1 protocol |
| `force_http3` | bool | False | Force HTTP/3 (QUIC) protocol |
| `protocol` | str | None | Explicit protocol: 'http1', 'http2', 'http3', 'websocket', 'sse' |

### Connection Parameters

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `proxy` | str | None | Proxy URL (http://, https://, socks4://, socks5://, socks5h://) |
| `timeout` | int | 6 | Request timeout in seconds |
| `disable_redirect` | bool | False | Disable automatic redirect following |
| `enable_connection_reuse` | bool | False | Enable connection pooling for subsequent requests |

### Async-Specific Parameters

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `timeout` | float | 30.0 | Maximum time to wait for request completion (seconds) |
| `poll_interval` | float | 0.0 | Polling interval (0.0 = adaptive: tight loop → 100μs → 1ms) |

---

## Response Object

Properties and methods available on Response objects:

### Status Properties

| Property | Type | Description |
|----------|------|-------------|
| `status_code` | int | HTTP status code (200, 404, etc.) |
| `ok` | bool | True if 200 <= status_code < 400 |
| `is_redirect` | bool | True if 300 <= status_code < 400 |
| `is_error` | bool | True if status_code >= 400 |
| `is_client_error` | bool | True if 400 <= status_code < 500 |
| `is_server_error` | bool | True if 500 <= status_code < 600 |
| `reason` | str | HTTP reason phrase ("OK", "Not Found", etc.) |

### Content Properties

| Property | Type | Description |
|----------|------|-------------|
| `text` | str | Response body as decoded string |
| `body` | str | Alias for text |
| `content` | bytes | Response body as raw bytes |
| `encoding` | str | Character encoding (auto-detected from headers) |
| `url` | str | Final URL after redirects |

### Header and Cookie Properties

| Property | Type | Description |
|----------|------|-------------|
| `headers` | CaseInsensitiveDict | Response headers (case-insensitive access) |
| `cookies` | CookieJar | Response cookies |

### Methods

```python
response.json() -> dict
    """Parse response body as JSON. Raises ValueError if not valid JSON."""

response.raise_for_status() -> None
    """Raise HTTPError if status_code >= 400."""
```

---

## CycleTLS Class

Main synchronous client class.

```python
from cycletls import CycleTLS

class CycleTLS:
    def __init__(self, port: int = None):
        """
        Initialize CycleTLS client.

        Args:
            port: Optional port for Go backend (auto-assigned if None)
        """

    def request(self, method: str, url: str, **kwargs) -> Response:
        """Send an HTTP request with any method."""

    def get(self, url: str, params: dict = None, **kwargs) -> Response:
        """Send a GET request."""

    def post(self, url: str, data=None, json_data=None, **kwargs) -> Response:
        """Send a POST request."""

    def put(self, url: str, data=None, json_data=None, **kwargs) -> Response:
        """Send a PUT request."""

    def patch(self, url: str, data=None, json_data=None, **kwargs) -> Response:
        """Send a PATCH request."""

    def delete(self, url: str, **kwargs) -> Response:
        """Send a DELETE request."""

    def head(self, url: str, **kwargs) -> Response:
        """Send a HEAD request."""

    def options(self, url: str, **kwargs) -> Response:
        """Send an OPTIONS request."""

    def batch(self, requests: list[dict]) -> list[Response]:
        """
        Send multiple requests in a batch.

        Args:
            requests: List of request dicts with 'url', 'method', and optional params

        Returns:
            List of Response objects
        """

    def close(self) -> None:
        """Close the client and cleanup resources."""

    def __enter__(self) -> CycleTLS:
        """Context manager entry."""

    def __exit__(self, exc_type, exc_val, exc_tb) -> None:
        """Context manager exit with automatic cleanup."""
```

---

## AsyncCycleTLS Class

Async client for concurrent requests.

```python
from cycletls import AsyncCycleTLS

class AsyncCycleTLS:
    def __init__(self, port: int = None):
        """Initialize async CycleTLS client."""

    async def request(self, method: str, url: str, poll_interval: float = 0.0,
                      timeout: float = 30.0, **kwargs) -> Response:
        """Send an async HTTP request."""

    async def get(self, url: str, **kwargs) -> Response:
        """Send an async GET request."""

    async def post(self, url: str, data=None, json_data=None, **kwargs) -> Response:
        """Send an async POST request."""

    async def put(self, url: str, **kwargs) -> Response:
        """Send an async PUT request."""

    async def patch(self, url: str, **kwargs) -> Response:
        """Send an async PATCH request."""

    async def delete(self, url: str, **kwargs) -> Response:
        """Send an async DELETE request."""

    async def head(self, url: str, **kwargs) -> Response:
        """Send an async HEAD request."""

    async def options(self, url: str, **kwargs) -> Response:
        """Send an async OPTIONS request."""

    async def close(self) -> None:
        """Close the async client."""

    async def __aenter__(self) -> AsyncCycleTLS:
        """Async context manager entry."""

    async def __aexit__(self, exc_type, exc_val, exc_tb) -> None:
        """Async context manager exit."""
```

---

## Session Class

Session with persistent cookies and headers.

```python
from cycletls import Session

class Session(CycleTLS):
    cookies: CookieJar      # Persistent cookie jar
    headers: CaseInsensitiveDict  # Persistent headers

    def __init__(self):
        """Initialize a Session with persistent cookies/headers."""
```

Usage:
```python
with Session() as session:
    session.headers['Authorization'] = 'Bearer token123'

    # Login - cookies automatically saved
    session.post('https://example.com/login', json_data={'user': 'admin'})

    # Subsequent requests include cookies automatically
    profile = session.get('https://example.com/profile')
```

---

## Cookie Class

For advanced cookie configuration.

```python
from cycletls import Cookie

class Cookie:
    name: str
    value: str
    path: str = None
    domain: str = None
    expires: datetime = None
    max_age: int = None
    secure: bool = False
    http_only: bool = False
    same_site: str = None  # "Strict", "Lax", or "None"
```

---

## Exceptions

```python
from cycletls import (
    CycleTLSError,      # Base exception
    RequestException,    # Base for request errors
    HTTPError,          # HTTP error (4xx, 5xx) - has .response attribute
    ConnectionError,    # Connection failed
    Timeout,            # Request timeout
    ConnectTimeout,     # Connection timeout
    ReadTimeout,        # Read timeout
    TooManyRedirects,   # Exceeded redirect limit
    InvalidURL,         # Malformed URL
    TLSError,           # TLS handshake error
    ProxyError,         # Proxy connection error
    InvalidHeader,      # Invalid header value
)
```

---

## JA3 Fingerprinting

JA3 format: `TLSVersion,Ciphers,Extensions,EllipticCurves,ECPointFormats`

Example Chrome 83:
```
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
```

Components:
- `771` = TLS 1.2
- `4865-4866-4867...` = Cipher suites
- `0-23-65281...` = TLS extensions
- `29-23-24` = Elliptic curves
- `0` = EC point formats

Common browser fingerprints:
```python
BROWSER_FINGERPRINTS = {
    'chrome_83': {
        '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 (KHTML, like Gecko) Chrome/83.0.4103.106 Safari/537.36'
    },
    'firefox_87': {
        'ja3': '771,4865-4867-4866-49195-49199-52393-52392-49196-49200-49162-49161-49171-49172-51-57-47-53-10,0-23-65281-10-11-35-16-5-51-43-13-45-28-21,29-23-24-25-256-257,0',
        'user_agent': 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:87.0) Gecko/20100101 Firefox/87.0'
    },
    'safari_15': {
        'ja3': '771,4865-4867-4866-49196-49195-52393-49200-49199-52392-49162-49161-49172-49171-157-156-53-47-49160-49170-10,0-23-65281-10-11-35-16-5-13-45-28-21,29-23-24-25,0',
        'user_agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.0 Safari/605.1.15'
    }
}
```

---

## HTTP/2 Fingerprinting

HTTP/2 Akamai fingerprint format: `settings|window_update|priority|pseudo_header_order`

Example Firefox:
```
1:65536;2:0;4:131072;5:16384|12517377|0|m,p,a,s
```

Components:
- `1:65536` = HEADER_TABLE_SIZE
- `2:0` = ENABLE_PUSH
- `4:131072` = MAX_CONCURRENT_STREAMS
- `5:16384` = INITIAL_WINDOW_SIZE
- `12517377` = Window update value
- `0` = Priority (0 = no priority frame)
- `m,p,a,s` = Pseudo-header order (method, path, authority, scheme)

---

## Proxy Configuration

Supported proxy protocols:

```python
# HTTP proxy
response = cycletls.get(url, proxy='http://host:port')

# HTTP proxy with auth
response = cycletls.get(url, proxy='http://user:pass@host:port')

# HTTPS proxy
response = cycletls.get(url, proxy='https://host:port')

# SOCKS4 proxy
response = cycletls.get(url, proxy='socks4://host:port')

# SOCKS5 proxy
response = cycletls.get(url, proxy='socks5://host:port')

# SOCKS5 with hostname resolution through proxy
response = cycletls.get(url, proxy='socks5h://host:port')
```

---

## Concurrent Requests Example

```python
import asyncio
import cycletls

async def main():
    # Make 10 requests concurrently
    responses = await asyncio.gather(*[
        cycletls.aget(f'https://httpbin.org/get?id={i}')
        for i in range(10)
    ])

    print(f"Completed {len(responses)} requests")
    print(f"All successful: {all(r.status_code == 200 for r in responses)}")

asyncio.run(main())
```

---

## Batch Requests

```python
from cycletls import CycleTLS

with CycleTLS() as client:
    requests = [
        {"url": "https://httpbin.org/get", "method": "GET"},
        {"url": "https://httpbin.org/post", "method": "POST", "json_data": {"key": "value"}},
        {"url": "https://httpbin.org/headers", "method": "GET"},
    ]

    responses = client.batch(requests)

    for response in responses:
        print(response.status_code)
```

---

## Source Code

- GitHub: https://github.com/Danny-Dasilva/cycletls_python
- PyPI: https://pypi.org/project/cycletls/
- Examples: https://github.com/Danny-Dasilva/cycletls_python/tree/main/examples
