Metadata-Version: 2.4
Name: twilldocs
Version: 0.1.0
Summary: Official Python SDK for the Twill Docs document generation API.
Project-URL: Homepage, https://www.twilldocs.com
Project-URL: Repository, https://github.com/twilldocs/twilldocs-python
Author: Twill Docs
License: MIT
License-File: LICENSE
Keywords: api,documents,invoice,pdf,sdk,twilldocs
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Typing :: Typed
Requires-Python: >=3.9
Requires-Dist: httpx>=0.24
Requires-Dist: typing-extensions>=4.0
Description-Content-Type: text/markdown

# Twill Docs — Python SDK

The official Python SDK for [Twill Docs](https://www.twilldocs.com), the
document infrastructure API. Turn structured data into production-ready PDFs —
invoices, receipts, payslips, and more — with typed payloads.

- **Typed templates** — each document type has a `TypedDict` input, checked by mypy and your editor.
- **Typed errors** — catch `TwillRateLimitError`, `TwillValidationError`, and friends.
- Built on [httpx](https://www.python-httpx.org/).

## Install

```bash
pip install twilldocs
```

Requires Python 3.9+.

## Quickstart

```python
from twilldocs import TwillDocs

twill = TwillDocs(api_key="twdc_...")

# Create an invoice, wait for it to render, then download the PDF.
doc = twill.documents.generate("invoice", {
    "invoice_number": "INV-1001",
    "issue_date": "2026-07-22",
    "due_date": "2026-08-21",
    "currency": "USD",
    "seller": {"name": "Northwind Studio", "address": "500 Market St", "tax_id": "US123456789"},
    "buyer": {"name": "Acme Corp", "address": "1 Infinite Loop"},
    "line_items": [
        {"description": "Consulting", "quantity": 3, "unit_price": 1200},
        {"description": "Travel expenses", "quantity": 1, "unit_price": 340},
    ],
    "tax_rate": 0.085,
})

pdf = twill.documents.download(doc["id"])  # bytes
open(f"invoice-{doc['id']}.pdf", "wb").write(pdf)
```

You supply line items and the tax rate; **Twill computes the totals** and renders
the document.

## Configuration

```python
twill = TwillDocs(
    api_key="twdc_...",                 # required
    base_url="https://api.twilldocs.com",  # default; use http://localhost:8080 for local dev
    timeout=30.0,                       # per-request timeout in seconds
)
```

The client holds a connection pool — reuse one instance, or use it as a context
manager:

```python
with TwillDocs(api_key="twdc_...") as twill:
    twill.documents.generate("receipt", {...})
```

## Documents

```python
doc = twill.documents.create("invoice", {...})   # returns immediately, status "pending"
twill.documents.retrieve(doc["id"])              # check status
twill.documents.wait_until_ready(doc["id"])      # poll until succeeded / failed
pdf = twill.documents.download(doc["id"])         # bytes
twill.documents.generate("invoice", {...})       # create + wait, in one call
```

Every `create`/`generate` sends an idempotency key automatically (override with
`idempotency_key=...`), so a retried request never produces a duplicate.

### Templates

The template name you pass narrows the accepted input type. Templates and their
input `TypedDict`s:

| Template | Input type |
| -------- | ---------- |
| `invoice` | `InvoiceInput` |
| `quote` | `QuoteInput` |
| `receipt` | `ReceiptInput` |
| `purchase_order` | `PurchaseOrderInput` |
| `delivery_note` | `DeliveryNoteInput` |
| `payslip` | `PayslipInput` |
| `offer_letter` | `OfferLetterInput` |
| `nda` | `NdaInput` |
| `service_agreement` | `ServiceAgreementInput` |

All input types are importable for building payloads with full typing:

```python
from twilldocs import InvoiceInput
```

## API keys

```python
keys = twill.api_keys.list()
twill.api_keys.revoke(keys[0]["id"])
```

## Brand

```python
twill.brand.retrieve()
twill.brand.update(theme="modern")
twill.brand.update(logo=("logo.png", open("logo.png", "rb").read(), "image/png"))
twill.brand.delete_logo()
```

## Errors

Every failure raises a subclass of `TwillError`:

```python
from twilldocs import TwillValidationError, TwillRateLimitError, TwillError
import time

try:
    twill.documents.generate("invoice", data)
except TwillValidationError as e:
    print("Invalid input:", e.errors)     # per-field messages
except TwillRateLimitError as e:
    time.sleep(e.retry_after or 1)
except TwillError as e:
    print(e.status, e.type, e.message)
```

| Class | When |
| ----- | ---- |
| `TwillValidationError` | 400 / 422 (`.errors` has field messages) |
| `TwillAuthenticationError` | 401 |
| `TwillPermissionError` | 403 |
| `TwillNotFoundError` | 404 |
| `TwillConflictError` | 409 |
| `TwillRateLimitError` | 429 (`.retry_after` seconds) |
| `TwillServerError` | 5xx |
| `TwillConnectionError` | network failure before a response |
| `TwillTimeoutError` | request exceeded `timeout` |

## Health

```python
twill.health()  # {"status": "ok" | "degraded", "checks": {...}}
```

## License

[MIT](./LICENSE)
