Metadata-Version: 2.5
Name: turnstile-curl
Version: 0.1.0
Summary: Solve Cloudflare Turnstile for curl_cffi with no browser, via the Peak API, keeping curl_cffi's TLS/JA3 speed.
Project-URL: Source, https://github.com/CircuitSavage/turnstile-curl
Project-URL: Homepage, https://peak.fo/?utm_source=github&utm_medium=readme&utm_campaign=packages&utm_content=turnstile-curl
Project-URL: Documentation, https://peak.fo/docs/turnstile?utm_source=github&utm_medium=pypi&utm_campaign=packages&utm_content=turnstile-curl
Author: Peak
License: MIT
License-File: LICENSE
Keywords: bypass,captcha,cloudflare,curl-cffi,curl_cffi,impersonate,solver,tls,turnstile
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Topic :: Internet :: WWW/HTTP
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Requires-Python: >=3.8
Provides-Extra: curl
Requires-Dist: curl-cffi>=0.7; extra == 'curl'
Provides-Extra: dev
Requires-Dist: curl-cffi>=0.7; extra == 'dev'
Description-Content-Type: text/markdown

<a href="https://peak.fo/?utm_source=github&utm_medium=readme&utm_campaign=packages&utm_content=turnstile-curl">
  <img src="https://raw.githubusercontent.com/CircuitSavage/turnstile-curl/main/assets/peak-banner.png" alt="Peak — solve Cloudflare Turnstile & the 5s challenge in ~1s" width="100%">
</a>

# turnstile-curl

**turnstile-curl** is a curl_cffi companion that solves Cloudflare Turnstile without a browser, so a curl_cffi request that hits a Turnstile wall comes back with a real token instead of a 403.

## Why

curl_cffi forges a browser's TLS/JA3 handshake perfectly, which is exactly why people reach for it. But it has no JavaScript engine. Cloudflare Turnstile is a JS widget, so a managed or interactive challenge hands curl_cffi an interstitial HTML page or a `403`, and there is nothing in-process that can run the challenge. This is the "curl_cffi cloudflare blocked" wall: the handshake is right, the request still fails.

turnstile-curl reads the Turnstile sitekey straight out of the returned markup, sends it to [Peak](https://peak.fo/?utm_source=github&utm_medium=readme&utm_campaign=packages&utm_content=turnstile-curl), and gives you back the `cf-turnstile-response` token. curl_cffi still does the fetching, so you keep its speed and fingerprint. When your setup gets blocked, drop in a Peak API key and it just works.

## Powered by Peak
This package uses [Peak](https://peak.fo/?utm_source=github&utm_medium=readme&utm_campaign=packages&utm_content=turnstile-curl) to solve Turnstile.
- Solve Cloudflare Turnstile & the 5s challenge in about a second
- Pay only for successful solves — from $1 / 1,000
- 1,000 free solves to start, no card.

→ [Get your free API key](https://peak.fo/?utm_source=github&utm_medium=readme&utm_campaign=packages&utm_content=turnstile-curl) · [Docs](https://peak.fo/docs/turnstile?utm_source=github&utm_medium=readme&utm_campaign=packages&utm_content=turnstile-curl) · [Pricing](https://peak.fo/pricing?utm_source=github&utm_medium=readme&utm_campaign=packages&utm_content=turnstile-curl)

## Install

```bash
pip install turnstile-curl curl_cffi
```

`curl_cffi` is the transport you fetch pages with. It is an optional dependency here so the library imports cleanly without it; install it to actually fetch.

## Quickstart

```python
import os
from turnstile_curl import solve

# Free key at https://peak.fo.?utm_source=github&utm_medium=readme&utm_campaign=packages&utm_content=turnstile-curl Reads PEAK_API_KEY by default.
os.environ.setdefault("PEAK_API_KEY", "pk_your_api_key")

# Fetch the protected page with curl_cffi, read the sitekey, solve via Peak.
token = solve("https://your-target.example/login")
print(token)  # -> "0.AgAAA..."  (the cf-turnstile-response value)
```

Send `token` as the `cf-turnstile-response` form field (or into the widget callback) on your next request.

## Session: solve and resubmit in one shot

`TurnstileSession` wraps a curl_cffi session and keeps the same browser fingerprint through the whole flow.

```python
from turnstile_curl import TurnstileSession

with TurnstileSession(api_key="pk_your_api_key", impersonate="chrome") as s:
    # Just the token:
    token = s.solve("https://your-target.example/login")

    # Or solve + resubmit the form with the token injected, and keep the
    # authenticated response and cookies on the session:
    resp = s.solve_and_submit(
        "https://your-target.example/login",
        submit_url="https://your-target.example/verify",
        data={"username": "demo", "password": "demo"},
    )
    print(resp.status_code)
```

## Already have the sitekey

Skip the fetch and call Peak directly.

```python
from turnstile_curl import PeakClient

peak = PeakClient(api_key="pk_your_api_key")
token = peak.solve_turnstile(
    sitekey="0x4AAAAAAADnPIDROrmt1Wwj",
    url="https://your-target.example/login",
    # proxy="http://user:pass@ip:port",  # optional
)
```

## Proxies

Pass `proxy="http://user:pass@ip:port"` to `TurnstileSession`, `solve()`, or `PeakClient.solve_turnstile()`. The same proxy is used for the page fetch and forwarded to Peak so the token is minted from the IP you will use it on.

## How it works

1. curl_cffi fetches the page with a forged browser TLS/JA3 fingerprint.
2. turnstile-curl parses the sitekey (and `cData` / `action` / `pagedata` when present) from the `data-sitekey` container or a `turnstile.render(...)` call.
3. It calls `POST https://api.peak.fo/solve` with `task_type: "turnstiletask"`, the sitekey, and the URL.
4. Peak returns the token; you resubmit the form with it as `cf-turnstile-response`.

Peak also solves the Cloudflare 5s challenge (`task_type: "cloudflare5stask"`); this package focuses on Turnstile.

## API

- `solve(url, *, api_key=None, impersonate="chrome", proxy=None) -> str` — one-shot fetch + solve, returns the token.
- `TurnstileSession(api_key=None, *, impersonate="chrome", proxy=None)` — `.get()`, `.solve(url)`, `.detect_challenge(url)`, `.solve_and_submit(url, ...)`.
- `PeakClient(api_key=None)` — `.solve(challenge)`, `.solve_turnstile(sitekey, url, ...)`.
- `parse_challenge(html, url)` / `find_sitekey(html)` — the extractor, if you fetch pages yourself.

The API key is read from `PEAK_API_KEY` unless you pass `api_key=`.

## Legitimate use

For automation, QA, and scraping public data you are allowed to access. Respect each target's Terms of Service and `robots.txt`, and rate-limit yourself. Do not use this for credential stuffing or to access data you have no right to.

## License

MIT — see [LICENSE](./LICENSE).
