Metadata-Version: 2.4
Name: thronic-solver
Version: 1.0.0
Summary: Official Python SDK for the Thronic Solver hCaptcha solving API.
Project-URL: Homepage, https://solver.thronic.cc
Project-URL: Documentation, https://solver.thronic.cc/docs
Project-URL: Source, https://github.com/imlittledoo/thronic-solver
Project-URL: Bug Tracker, https://github.com/imlittledoo/thronic-solver/issues
Project-URL: Changelog, https://github.com/imlittledoo/thronic-solver/blob/main/CHANGELOG.md
Author-email: Thronic <hello@thronic.cc>
Maintainer-email: Thronic <hello@thronic.cc>
License: MIT
License-File: LICENSE
Keywords: api,automation,bypass,captcha,captcha-solver,hcaptcha,sdk,solver,thronic
Classifier: Development Status :: 5 - Production/Stable
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.8
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: Implementation :: CPython
Classifier: Topic :: Internet :: WWW/HTTP
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Typing :: Typed
Requires-Python: >=3.8
Requires-Dist: requests>=2.25.0
Provides-Extra: async
Requires-Dist: httpx>=0.24.0; extra == 'async'
Provides-Extra: dev
Requires-Dist: build>=1.0; extra == 'dev'
Requires-Dist: httpx>=0.24.0; extra == 'dev'
Requires-Dist: pytest-asyncio>=0.21; extra == 'dev'
Requires-Dist: pytest>=7.0; extra == 'dev'
Requires-Dist: twine>=4.0; extra == 'dev'
Description-Content-Type: text/markdown

# thronic-solver

Official Python SDK for the [Thronic Solver](https://solver.thronic.cc) hCaptcha solving API.

Sub-second solves, transparent pay-per-solve pricing, typed responses, sync **and** async clients, automatic retries, and a CLI.

```bash
pip install thronic-solver
```

---

## Quickstart

```python
from thronic_solver import Thronic

client = Thronic(api_key="tsk_live_...")

token = client.solve(url="https://example.com", sitekey="a1b2c3")
print(token)
```

`solve()` blocks until the challenge is solved and returns a `Solution`.
`str(solution)` is the token, so `print(token)` prints it directly — or use
`token.token`, `token.cost`, `token.elapsed`, `token.balance`.

Set the key via the environment instead of in code:

```bash
export THRONIC_API_KEY=tsk_live_...
```

```python
client = Thronic()   # picks up THRONIC_API_KEY
```

---

## Async

```bash
pip install "thronic-solver[async]"
```

```python
import asyncio
from thronic_solver import AsyncThronic

async def main():
    async with AsyncThronic() as client:
        token = await client.solve(url="https://example.com", sitekey="a1b2c3")
        print(token)

asyncio.run(main())
```

Solve many concurrently:

```python
results = await client.solve_many([
    {"url": "https://a.com", "sitekey": "k1"},
    {"url": "https://b.com", "sitekey": "k2"},
])
for r in results:
    print(r if not isinstance(r, Exception) else f"failed: {r}")
```

---

## Fire-and-poll (non-blocking)

```python
task = client.create_task(url="https://example.com", sitekey="a1b2c3")
print(task.task_id)

result = client.get_result(task.task_id)
if result.is_success:
    print(result.token)
elif result.is_pending:
    print("still solving…")
```

Batch solves with the sync client:

```python
for outcome in client.solve_many([{"url": u, "sitekey": k} for u, k in targets]):
    if isinstance(outcome, Exception):
        print("failed:", outcome)
    else:
        print("token:", outcome.token)
```

---

## Account

```python
bal = client.balance()
print(bal.balance, bal.currency, bal.remaining_solves)

acc = client.account()
print(acc.username, acc.email)

for row in client.history(limit=10):
    print(row.task_id, row.status, row.cost, row.solve_ms)

s = client.stats()
print(s.success_rate, s.avg_solve_ms)

client.validate()   # -> True / False
```

---

## Options

```python
client = Thronic(
    api_key="tsk_live_...",
    base_url="https://solver.thronic.cc",  # or http://localhost:5002
    timeout=30,          # per-request timeout (seconds)
    max_retries=3,       # retries on 429/5xx/network errors
    backoff_factor=0.5,  # exponential backoff with jitter
)
```

Enterprise challenges, proxies and custom user agents:

```python
token = client.solve(
    url="https://example.com",
    sitekey="a1b2c3",
    type="hcaptcha_enterprise",
    rqdata="...",
    proxy="user:pass@host:port",
    user_agent="Mozilla/5.0 ...",
    timeout=180,
    poll_interval=3,
)
```

---

## Error handling

Every error inherits from `ThronicError`:

```python
from thronic_solver import (
    Thronic, ThronicError, InsufficientBalanceError,
    RateLimitError, SolveFailedError, SolveTimeoutError,
)

try:
    token = client.solve(url=url, sitekey=key)
except InsufficientBalanceError as e:
    print(f"Top up! balance={e.balance} cost={e.cost}")
except RateLimitError as e:
    print(f"Slow down, retry in {e.retry_after}s")
except SolveFailedError as e:
    print(f"Engine could not solve task {e.task_id}")
except SolveTimeoutError:
    print("Took too long")
except ThronicError as e:
    print(f"API error: {e} (http={e.status_code}, code={e.code})")
```

| Exception | HTTP | Meaning |
|---|---|---|
| `ValidationError` | 400 | Missing/invalid `url` or `sitekey` |
| `AuthenticationError` | 401 | Missing or invalid API key |
| `InsufficientBalanceError` | 402 | Balance too low — top up |
| `PermissionDeniedError` | 403 | Key disabled or account inactive |
| `RateLimitError` | 429 | Rate limit exceeded (`retry_after`) |
| `ServiceUnavailableError` | 503 | Engine/API temporarily down |
| `NetworkError` | – | Connection/DNS/timeout before reaching the API |
| `APIError` | 5xx | Unexpected server response |
| `SolveFailedError` | – | Engine finished but produced no token |
| `SolveTimeoutError` | – | No result within your timeout |

Transient failures (429, 500, 502, 503, 504, network errors) are retried
automatically with exponential backoff + jitter, honouring `Retry-After`.

---

## CLI

```bash
export THRONIC_API_KEY=tsk_live_...

thronic balance
thronic account
thronic solve --url https://example.com --sitekey a1b2c3
thronic history --limit 10
thronic stats
thronic validate

# machine-readable
thronic --json balance
```

---

## Requirements

- Python 3.8+
- `requests` (installed automatically); `httpx` only for the async client

## Links

- **Website:** https://solver.thronic.cc
- **API docs:** https://solver.thronic.cc/docs
- **Source:** https://github.com/imlittledoo/thronic-solver
- **Issues:** https://github.com/imlittledoo/thronic-solver/issues
- **Contact:** hello@thronic.cc

## License

MIT © Thronic
