Metadata-Version: 2.4
Name: docjet
Version: 1.0.0
Summary: Typed Python SDK for the DocJet document generation API - render branded PDFs and PNG images, verify webhook signatures.
Project-URL: Homepage, https://docjet.dev
Project-URL: Documentation, https://docjet.dev
Project-URL: API, https://api.docjet.dev
Author-email: DocJet <support@docjet.dev>
License-Expression: MIT
Keywords: document-generation,html-to-pdf,invoice,og-image,pdf,webhook
Classifier: Development Status :: 5 - Production/Stable
Classifier: Intended Audience :: Developers
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
Provides-Extra: dev
Requires-Dist: pytest; extra == 'dev'
Description-Content-Type: text/markdown

# docjet — Python SDK

Typed Python client for the [DocJet](https://docjet.dev) document generation API.
Send a template ID plus data, get back a branded PDF or PNG — built for AI agents
and automation builders.

- **Typed** — full type hints, ships `py.typed` (PEP 561)
- **One dependency** — [`httpx`](https://www.python-httpx.org/)
- **Python 3.10+**
- **Webhook signature verification built in** — verify `X-DocJet-Signature` in one call

## Install

```bash
pip install docjet
```

## Quickstart

```python
from docjet import DocJetClient

client = DocJetClient(api_key="binfra_your_key")

# Render a PDF — returns a signed download URL
result = client.render(
    template_id="invoice-ro",
    data={"client": "Demo SRL", "total": 1000},
)
print(result["url"])  # https://api.docjet.dev/dl/...
```

Get a free API key at [docjet.dev](https://docjet.dev) — first render in under 5 minutes.

## Verify webhook signatures (the differentiator)

When a render completes, DocJet POSTs to your webhook URL and signs every callback
with an `X-DocJet-Signature` header:

```
X-DocJet-Signature: t=<unix-seconds>,v1=<hmac-sha256-hex>
```

Verify it in one call — constant-time comparison and a 5-minute replay window
are handled for you:

```python
from docjet import verify_webhook_signature

# Flask example — works the same in FastAPI, Django, etc.
@app.post("/webhooks/docjet")
def docjet_webhook():
    raw_body = request.get_data(as_text=True)        # RAW body, before JSON parsing
    header = request.headers.get("X-DocJet-Signature", "")

    if not verify_webhook_signature(raw_body, header, WEBHOOK_SECRET):
        return "invalid signature", 400

    payload = json.loads(raw_body)                    # now safe to trust
    print(payload["url"])
    return "", 204
```

- Get your signing secret: `GET /v1/keys/webhook-secret`
- Pass the **raw** request body — verifying a re-serialized body will fail
- Tampered, expired (>300s old), or malformed signatures return `False`
- Custom tolerance: `verify_webhook_signature(body, header, secret, tolerance_seconds=600)`

## Full surface

```python
client.render(template_id=..., data=..., options=...)        # PDF  -> {"url": ...}
client.render_image(template_id=..., data=..., width=1200, height=630)  # PNG -> {"url": ...}
client.list_templates()                                       # public, no auth
client.usage()                                                # current-period usage
```

Pass `html=...` instead of `template_id=...` to render your own HTML.

## Error handling

Non-2xx responses raise `DocJetError` with the API error code and HTTP status:

```python
from docjet import DocJetClient, DocJetError

try:
    client.render(template_id="invoice-ro", data={...})
except DocJetError as e:
    print(e.code)         # e.g. "QUOTA_EXCEEDED"
    print(e.status_code)  # e.g. 429
```

| Code | Status | Meaning |
|------|--------|---------|
| `AUTH_INVALID` | 401 | Missing or invalid API key |
| `PAYLOAD_INVALID` | 400/422 | Bad request body or template_id |
| `TEMPLATE_NOT_FOUND` | 404 | Unknown template_id |
| `RATE_LIMITED` | 429 | Too many requests per minute |
| `QUOTA_EXCEEDED` | 429 | Monthly render quota used up |
| `CONCURRENCY_EXCEEDED` | 429 | Too many simultaneous renders |
| `INTERNAL_ERROR` | 5xx | Server-side failure |

Invalid `template_id` values are rejected **locally** (no network call) with
`PAYLOAD_INVALID` / 422 — template IDs must match `^[a-z0-9][a-z0-9-]{0,63}$`.

## License

MIT
