Metadata-Version: 2.4
Name: writehuman
Version: 0.1.0
Summary: Official WriteHuman API client for Python: humanize AI text, detect AI authorship, read usage.
License: MIT
Project-URL: Homepage, https://writehuman.ai/api/docs
Project-URL: Documentation, https://writehuman.ai/api/docs
Project-URL: Source, https://github.com/WriteHuman-ai/rephrase-api/tree/main/sdk/python
Project-URL: Changelog, https://github.com/WriteHuman-ai/rephrase-api/blob/main/sdk/python/CHANGELOG.md
Keywords: writehuman,humanize,ai-detection,api-client
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Typing :: Typed
Requires-Python: >=3.9
Description-Content-Type: text/markdown
Requires-Dist: httpx<1,>=0.25

# writehuman

Official WriteHuman API client for Python. Humanize AI-written text, score text for AI
authorship, and read your usage, with typed errors and a retry policy that follows the
API's own `Retry-After` and never resends a billed request.

```
pip install writehuman
```

```python
from writehuman import WriteHuman, InsufficientBalanceError, RateLimitError

wh = WriteHuman("wh_live_your_api_key_here")

out = wh.humanize("Artificial intelligence has transformed numerous industries over the past decade.",
                  tone="professional")
print(out["results"][0])
print(out["words_remaining"]["total"], "words left")

score = wh.detect("Your text to check here.", include_sentences=True)
print(score["ai_probability"], score["predicted_class"])

print(wh.usage())
```

Async, same methods:

```python
from writehuman import AsyncWriteHuman

async with AsyncWriteHuman("wh_live_...") as wh:
    out = await wh.humanize("...")
```

## Errors

Every failure raises a subclass of `WriteHumanError` with `code` (the API's stable error
code), `status`, `retry_after` (seconds, when the API sent one) and, on
`InsufficientBalanceError`, `words_remaining`.

| exception | API `code` |
|---|---|
| `AuthenticationError` | `missing_api_key`, `invalid_api_key` |
| `NoSubscriptionError` | `no_subscription` |
| `InsufficientBalanceError` | `insufficient_balance` |
| `InvalidRequestError` | `invalid_request`, `forbidden_param`, `word_limit` |
| `ModerationError` | `moderation_blocked` |
| `RateLimitError` | `rate_limited`, `auth_locked_out`, `queue_full`, `queue_timeout` |
| `ServiceUnavailableError` | `at_capacity`, `billing_unavailable`, `detector_unavailable` |
| `ServerTimeoutError` | `timeout` (the API's 504: nothing charged) |
| `ProcessingError` | `processing_failed`, `no_results`, `safety_limit` |
| `BillingError` | `billing_failed` (some words may have been charged; check `usage()`) |
| `APIError` | any code this version does not know (`code` is preserved) |
| `ProtocolError`, `ClientTimeoutError`, `ConnectionError` | no API body |

```python
try:
    wh.humanize(text)
except InsufficientBalanceError as e:
    print("out of words:", e.words_remaining)
except RateLimitError as e:
    print("slow down; the API asked for", e.retry_after, "seconds")
```

## Retries and timeouts

- `429`, `503` and `504` are retried after exactly the `Retry-After` the API sends, up to
  `max_retries` (default 2). A `Retry-After` above `max_retry_after` (default 60 s, for
  example an authentication lockout) is not slept through: the error is raised at once with
  `retry_after` set. A missing `Retry-After` waits `fallback_retry_after` (default 2 s).
- Nothing else is retried. `humanize` and `detect` are billed and non-idempotent, so a client
  timeout, a dropped connection, or a 429/503/504 that did not come from the API (a load
  balancer or proxy answering without the API's error body) raises immediately rather than
  risking a second charge. `usage` is read-only and may retry those.
- Timeouts are total deadlines through the whole response, not per-read limits (`AsyncWriteHuman`
  enforces them exactly; `WriteHuman` checks after every chunk of the body, so it can overrun
  by at most one network read). `timeout=` sets all operations (float) or per operation
  (`{"humanize": 120, "detect": 30, "usage": 15}`, the defaults); `connect_timeout` defaults
  to 10 s. Every method takes a per-call `timeout=`.

```python
wh = WriteHuman(key, timeout={"humanize": 60}, max_retries=3)
```

## Types

`HumanizeResponse`, `DetectResponse`, `UsageResponse`, `WordsRemaining` and `ErrorCode`
are generated from the API's OpenAPI document and exported for type checkers. The package
ships `py.typed`.

This client passes the WriteHuman SDK conformance suite (`sdk/conformance` in the API
repository), as does the TypeScript client; both implement the same documented policy.
