Metadata-Version: 2.4
Name: rendex
Version: 1.4.0
Summary: Official Python SDK for Rendex — render HTML to images, generate PDFs, and capture screenshots
Project-URL: Homepage, https://rendex.dev
Project-URL: Documentation, https://rendex.dev/docs
Project-URL: Repository, https://github.com/copperline-labs/copperline-labs
Project-URL: Issues, https://github.com/copperline-labs/copperline-labs/issues
Author-email: Copperline Labs LLC <support@rendex.dev>
License-Expression: MIT
License-File: LICENSE
Keywords: ai-agent,automation,html-to-image,no-code,pdf,rendering,rendex,screenshot,sdk,webpage-capture
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.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Typing :: Typed
Requires-Python: >=3.9
Requires-Dist: httpx>=0.27
Description-Content-Type: text/markdown

# rendex

Official Python SDK for [Rendex](https://rendex.dev) — the HTML-to-image, PDF, and screenshot rendering API. Turn raw HTML or any webpage into an image or PDF with a single function call.

- Full type hints (PEP 561 compatible)
- Single dependency (`httpx`)
- Sync API with context manager support
- Typed error handling with API error codes

## Install

```bash
pip install rendex
```

## Quick Start

```python
from pathlib import Path
from rendex import Rendex

rendex = Rendex("your-api-key")

# Render raw HTML straight to a PNG
result = rendex.render_html("<h1>Hello, world</h1>")
Path("hello.png").write_bytes(result.image)

# Or render Markdown (converted to HTML server-side)
md = rendex.render_markdown("# Hello, world\n\nRendered from **Markdown**.")
Path("hello-md.png").write_bytes(md.image)

# Or capture a live URL
shot = rendex.screenshot("https://example.com", format="png", full_page=True)
Path("screenshot.png").write_bytes(shot.image)

print(f"{result.metadata.bytes_size} bytes, loaded in {result.metadata.load_time_ms}ms")
```

## API Reference

### `Rendex(api_key, *, base_url="https://api.rendex.dev")`

Create a new Rendex client.

```python
rendex = Rendex("your-api-key")

# Or with context manager for connection reuse
with Rendex("your-api-key") as rendex:
    result = rendex.screenshot("https://example.com")
```

### `rendex.screenshot(url, **options)`

Capture a screenshot and return the binary image with metadata.

```python
result = rendex.screenshot(
    "https://example.com",
    format="webp",
    width=1920,
    height=1080,
    dark_mode=True,
)
Path("screenshot.webp").write_bytes(result.image)
print(result.metadata.load_time_ms)  # 350
```

**Returns** `ScreenshotResult`:
- `image` — `bytes` of the captured image
- `metadata` — `ScreenshotMetadata` with url, dimensions, format, bytes_size, load_time_ms, quality, etc.

### `rendex.render_html(html, **options)`

Render raw HTML straight to an image and return the binary result. Convenience wrapper over `screenshot(html=...)`.

```python
result = rendex.render_html("<h1>Hello, world</h1>", format="png", width=1200)
Path("hello.png").write_bytes(result.image)
```

**Returns** `ScreenshotResult` — same shape as `screenshot()`.

You can also pass `html` directly to `screenshot()` / `screenshot_json()`:

```python
result = rendex.screenshot(html="<h1>Hello, world</h1>", full_page=True)
```

> HTML rendering is POST-only and accepts up to 5MB. It is not available via `screenshot_url()` (the GET endpoint can't carry an HTML body). Provide exactly one of `url`, `html`, or `markdown`; the SDK raises `RendexError` locally if you supply none or more than one.

### `rendex.render_markdown(markdown, **options)`

Render Markdown straight to an image and return the binary result. The server converts the Markdown to HTML before rendering. Convenience wrapper over `screenshot(markdown=...)`.

```python
result = rendex.render_markdown("# Hello, world\n\nRendered from **Markdown**.", format="png", width=1200)
Path("hello.png").write_bytes(result.image)
```

**Returns** `ScreenshotResult` — same shape as `screenshot()`.

You can also pass `markdown` directly to `screenshot()` / `screenshot_json()`:

```python
result = rendex.screenshot(markdown="# Hello, world", full_page=True)
```

> Markdown rendering is POST-only and accepts up to 5MB. Like `html`, it is not available via `screenshot_url()`. Provide exactly one of `url`, `html`, or `markdown`.

### `rendex.screenshot_json(url, **options)`

Capture a screenshot and return JSON with a base64-encoded image.

```python
result = rendex.screenshot_json("https://example.com")
print(result["data"]["bytesSize"])        # 45823
print(result["meta"]["usage"]["remaining"])  # 499
```

**Returns** `ScreenshotJsonResponse` dict with `data` (image + metadata) and `meta` (request ID, usage).

### `rendex.render_html_json(html, **options)`

Render raw HTML and return JSON with a base64-encoded image. Convenience wrapper over `screenshot_json(html=...)`.

```python
result = rendex.render_html_json("<h1>Invoice #1042</h1>", format="pdf", pdf_format="Letter")
print(result["data"]["format"])  # "pdf"
```

### `rendex.render_markdown_json(markdown, **options)`

Render Markdown and return JSON with a base64-encoded image. The server converts the Markdown to HTML before rendering. Convenience wrapper over `screenshot_json(markdown=...)`.

```python
result = rendex.render_markdown_json("# Invoice #1042", format="pdf", pdf_format="Letter")
print(result["data"]["format"])  # "pdf"
```

### Mustache data templating

Pass a `data` dict alongside `html` or `markdown` to render logic-less [Mustache](https://mustache.github.io/) templates before capture. This lets you generate invoices, reports, or any dynamic content without string-formatting Python-side.

```python
from pathlib import Path
from rendex import Rendex

rendex = Rendex("your-api-key")

# Markdown invoice with variable substitution and a loop
template = """
# Invoice \#{{number}}

| Item | Qty | Price |
|------|-----|-------|
{{#items}}
| {{name}} | {{qty}} | {{price}} |
{{/items}}

**Total: {{total}}**
"""

result = rendex.render_markdown(
    template,
    data={
        "number": "1042",
        "items": [
            {"name": "Screenshot API — Pro", "qty": 1, "price": "€ 49.00"},
            {"name": "Overage credits (500)", "qty": 1, "price": "€ 5.00"},
        ],
        "total": "€ 54.00",
    },
    format="pdf",
    pdf_format="A4",
)
Path("invoice-1042.pdf").write_bytes(result.image)
```

The same `data` parameter works on `render_html`, `screenshot`, `screenshot_json`, `render_html_json`, and `render_markdown_json`.

**Template syntax** (server-side, logic-less Mustache):

| Syntax | Behaviour |
|--------|-----------|
| `{{var}}` | HTML-escaped substitution |
| `{{{var}}}` | Raw (unescaped) substitution |
| `{{#section}}...{{/section}}` | Render block for each item in a list (or once if truthy) |
| `{{^section}}...{{/section}}` | Render block when section is falsy / empty |
| `{{a.b}}` | Nested property access |

> `data` is not valid with `url` (the server returns 400). Serialized payload is capped at 256 KB server-side.

### `rendex.screenshot_url(url, **options)`

Generate a GET URL for embedding. No network call — pure URL builder.

```python
url = rendex.screenshot_url("https://example.com", format="png", width=1200)
# Use in <img> tags, OpenGraph, etc.
```

> **Note**: The API key is included in the URL. Use server-side only.

### Async jobs & batches

For long-running captures or many URLs at once, submit work asynchronously and poll for results.

```python
# Single async job: returns immediately, poll for the result
job = rendex.screenshot_json("https://example.com", async_mode=True)
job_id = job["data"]["jobId"]

status = rendex.job_status(job_id)
if status["data"]["status"] == "completed":
    print(status["data"]["resultUrl"])

# Batch: 1–500 URLs with shared defaults
batch = rendex.batch(
    ["https://example.com", "https://github.com"],
    defaults={"format": "webp", "full_page": True},
    cache_ttl=7200,
)
batch_id = batch["data"]["batchId"]

progress = rendex.batch_status(batch_id)
print(f'{progress["data"]["completedJobs"]}/{progress["data"]["totalJobs"]} done')
```

- `rendex.batch(urls, *, defaults=None, webhook_url=None, cache_ttl=None)` → `BatchCreateResponse`
- `rendex.job_status(job_id)` → `JobStatusResponse`
- `rendex.batch_status(batch_id)` → `BatchStatusResponse`

> Batch capture is URL-only (raw HTML is single-request); `defaults` accepts the same snake_case options as `screenshot()`.

### Screenshot Options

All options are keyword arguments in snake_case. Only `url` (positional) is required:

| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `format` | `str` | `"png"` | `"png"`, `"jpeg"`, `"webp"`, or `"pdf"` |
| `width` | `int` | `1280` | Viewport width (320–3840) |
| `height` | `int` | `800` | Viewport height (240–2160) |
| `full_page` | `bool` | `False` | Capture the full scrollable page |
| `quality` | `int` | `80` | JPEG/WebP quality (1–100, default 80) |
| `delay` | `int` | `0` | Delay before capture in ms (0–10000) |
| `dark_mode` | `bool` | `False` | Emulate dark mode |
| `device_scale_factor` | `float` | `2` | Device pixel ratio (1–3). 2× Retina by default |
| `block_ads` | `bool` | `True` | Block ads and trackers |
| `block_resource_types` | `list` | — | Block: `"font"`, `"image"`, `"media"`, `"stylesheet"`, `"other"` |
| `timeout` | `int` | `30` | Page load timeout in seconds (5–60) |
| `wait_until` | `str` | `"networkidle2"` | `"load"`, `"domcontentloaded"`, `"networkidle0"`, `"networkidle2"` |
| `wait_for_selector` | `str` | — | CSS selector to wait for |
| `best_attempt` | `bool` | `True` | Return best-effort screenshot on timeout |
| `selector` | `str` | — | Capture a specific element by CSS selector |
| `css` / `js` | `str` | — | Inject custom CSS / JavaScript before capture (max 50KB each) |
| `cookies` / `headers` | `list` / `dict` | — | Cookies (max 50) and custom HTTP headers for the request |
| `user_agent` | `str` | — | Override the browser user-agent string |
| `geo` / `geo_city` / `geo_state` | `str` | — | Geo-targeted capture (Pro/Enterprise) |
| `pdf_format` | `str` | `"A4"` | PDF page size: `"A4"`, `"Letter"`, `"Legal"`, `"Tabloid"`, `"A3"` (format=`"pdf"`) |
| `pdf_landscape` | `bool` | `False` | PDF landscape orientation |
| `pdf_print_background` | `bool` | `True` | Print background colors/images in the PDF |
| `pdf_margin` | `dict` | — | PDF margins, e.g. `{"top": "1cm", "bottom": "1cm"}` |
| `pdf_scale` | `float` | `1` | PDF render scale (0.1–2) |
| `async_mode` | `bool` | `False` | Return a job ID immediately (sent as `async`) |
| `webhook_url` | `str` | — | URL to call when an async capture completes |
| `cache_ttl` | `int` | `86400` | Signed result URL TTL in seconds (3600–2592000) |

## Error Handling

```python
from rendex import Rendex, RendexApiError, RendexNetworkError

rendex = Rendex("your-api-key")

try:
    rendex.screenshot("https://example.com")
except RendexApiError as e:
    # API returned an error
    print(e.error_code)   # "RATE_LIMITED", "VALIDATION_ERROR", etc.
    print(e.status_code)  # 429, 400, etc.
    print(e.request_id)   # For debugging with Rendex support
    print(e.details)      # Validation details (if any)
except RendexNetworkError as e:
    # Network failure (DNS, timeout, connection refused)
    print(f"Network error: {e}")
```

### Error Codes

| Code | HTTP Status | Description |
|------|-------------|-------------|
| `VALIDATION_ERROR` | 400 | Invalid request parameters |
| `INVALID_URL` | 400 | URL failed SSRF validation |
| `TIMEOUT` | 408 | Page took too long to load |
| `CAPTURE_FAILED` | 500 | Browser rendering error |
| `RATE_LIMITED` | 429 | Rate limit exceeded |
| `USAGE_EXCEEDED` | 429 | Monthly credit limit reached |
| `MISSING_API_KEY` | 401 | No API key provided |
| `INVALID_API_KEY` | 401 | API key verification failed |

## License

MIT - [Copperline Labs LLC](https://copperlinelabs.com)
