Metadata-Version: 2.4
Name: doclinth
Version: 0.1.0
Summary: Official Python SDK for the Doclinth PDF generation API
Project-URL: Homepage, https://doclinth.com
Project-URL: Documentation, https://doclinth.com/docs/python
Project-URL: Repository, https://github.com/Doclinth/doclinth-python
Project-URL: Issues, https://github.com/Doclinth/doclinth-python/issues
Author: Doclinth
License-Expression: MIT
License-File: LICENSE
Keywords: api,doclinth,document-generation,pdf
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.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Typing :: Typed
Requires-Python: >=3.10
Requires-Dist: httpx>=0.27
Requires-Dist: pydantic>=2.7
Description-Content-Type: text/markdown

# doclinth

Official Python SDK for the [Doclinth](https://doclinth.com) PDF generation API.
Sync and async clients, Pydantic models, automatic retries with safe idempotency,
and webhook signature verification.

## Install

```sh
pip install doclinth
```

Requires Python 3.10+.

## Quickstart

```python
from doclinth import Doclinth

client = Doclinth(
    api_key="dl_live_…",          # or dl_test_…
    base_url="https://doclinth.com",  # or set DOCLINTH_BASE_URL
)

# Binary (default) — returns the PDF bytes.
pdf = client.generate(
    template_id="b1c2d3e4-…",
    data={"invoice_number": "INV-1042", "total": 2592},
)
with open("invoice.pdf", "wb") as f:
    f.write(pdf)

# Signed URL — returns UrlResult(url=…, expires_at=…).
result = client.generate(
    template_id="b1c2d3e4-…",
    data={"total": 2592},
    output="url",
)
print(result.url, result.expires_at)

# Async delivery — returns QueuedGeneration; result POSTed to webhook_url.
queued = client.generate(
    template_id="b1c2d3e4-…",
    data={"total": 2592},
    webhook_url="https://hooks.example.com/doclinth",
)
print(queued.generation_id)  # poll with client.wait_for_generation(...)
```

Use the client as a context manager so the underlying HTTP pool is closed:

```python
with Doclinth(api_key="…", base_url="https://doclinth.com") as client:
    pdf = client.generate(template_id="…", data={})
```

## Async

```python
import asyncio
from doclinth import AsyncDoclinth

async def main() -> None:
    async with AsyncDoclinth(
        api_key="dl_live_…",
        base_url="https://doclinth.com",
    ) as client:
        pdf = await client.generate(
            template_id="b1c2d3e4-…",
            data={"invoice_number": "INV-1042"},
        )
        open("invoice.pdf", "wb").write(pdf)

asyncio.run(main())
```

## Templates & generations

```python
# List your templates.
templates = client.templates.list()

# Learn the data contract before you render.
detail = client.templates.get(templates[0].id)
detail.variables   # ["customer.name", "items", "total", …]
detail.sample_data

# AI-author a new draft template (consumes monthly AI allowance).
created = client.templates.create(
    prompt="A packing slip with order number, ship-to, and line items",
    quality="fast",
)

# Generation status (by X-Request-Id / generation_id).
status = client.generations.get("req_…")
status.status  # "success" | "error"

# Poll until an async generation finishes (404 generation_not_found = still pending).
status = client.wait_for_generation("gen_…", timeout=120.0)
```

## Webhook verification

When you pass `webhook_url`, Doclinth POSTs the signed result with header
`X-Doclinth-Signature: sha256=<hex>`.

```python
from doclinth import WEBHOOK_SIGNATURE_HEADER, verify_webhook

def handle(raw_body: bytes, headers: dict[str, str], secret: str) -> bool:
    signature = headers.get(WEBHOOK_SIGNATURE_HEADER, "")
    return verify_webhook(raw_body, signature, secret)
```

## Retries & idempotency

By default the client retries `429` and `5xx` responses (and network errors) up
to twice with exponential backoff, honoring `Retry-After`. To make those retries
safe, `generate` attaches an `Idempotency-Key` automatically — so a retried
request can never generate (or bill) a second PDF. Provide your own key to
dedupe across process restarts:

```python
client.generate(template_id="…", data=data, idempotency_key=order_id)
```

Set `max_retries=0` to disable retries (and the automatic key).
`templates.create` is non-idempotent and AI-metered, so it only retries a
pre-work `429`, never a `5xx` — a dropped response never risks a duplicate
(billed) template.

## Errors

Non-2xx responses raise `DoclinthError` with a stable `code` and `status`:

```python
from doclinth import Doclinth, DoclinthError

try:
    client.generate(template_id=template_id, data=data)
except DoclinthError as e:
    if e.code == "quota_exceeded":
        ...  # prompt an upgrade
    raise
```

`wait_for_generation` raises `DoclinthTimeoutError` (a `DoclinthError` subclass)
when the deadline is exceeded.

See the [error reference](https://doclinth.com/docs/errors) for all codes.

## License

MIT
