Metadata-Version: 2.4
Name: relaypdf
Version: 0.1.3
Summary: Official Python SDK for RelayPDF — HTML, Markdown, URL, and templates to PDF, LibreOffice convert, PDF tools, barcodes, zip, async jobs, and webhook verification
Author-email: RelayPDF <timspell@gmail.com>
License: MIT
Project-URL: Homepage, https://relaypdf.com
Project-URL: Documentation, https://relaypdf.com/docs/sdks/python
Project-URL: Repository, https://github.com/timspell1/PDF
Keywords: pdf,html-to-pdf,url-to-pdf,markdown-to-pdf,relaypdf,documents,libreoffice
Classifier: Programming Language :: Python :: 3
Classifier: License :: OSI Approved :: MIT License
Classifier: Typing :: Typed
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Dynamic: license-file

# relaypdf

Official Python client for [RelayPDF](https://relaypdf.com).

**HTML to PDFs without the struggle.** HTML to PDF API that converts HTML, Markdown, URLs, and Office files to production PDFs. One API for developers and coding agents.

Stdlib only (`urllib`). Covers the full public API: Chromium PDF and screenshots, Handlebars templates, LibreOffice / wkhtmltopdf convert, PDF tools, barcodes, zip, async jobs, account, signed webhooks, and webhook verification.

- **Docs:** [relaypdf.com/docs/sdks/python](https://relaypdf.com/docs/sdks/python)
- **REST reference:** [relaypdf.com/docs](https://relaypdf.com/docs)
- **OpenAPI:** [relaypdf.com/openapi.json](https://relaypdf.com/openapi.json)
- **Support:** [support@relaypdf.com](mailto:support@relaypdf.com)

Requires Python 3.10+.

## Introduction

RelayPDF is a commercial document API. You send HTML, Markdown, a public URL, Office bytes, or a published Handlebars template. You get a production PDF (or PNG/JPEG/WebP, Word, zip, …) over REST, this SDK, the CLI, or MCP.

**JSON body field names match REST (camelCase):** `html`, `printBackground`, `sourceFilename`, `callbackUrl`, `templateId`. **Method names are snake_case:** `from_html`, `from_path`, `form_fill`.

Failed operations are never billed.

## Base URL

Default: `https://api.relaypdf.com`

Override with `base_url` for local (`http://localhost:8787`).

## Definitions

| Term | Meaning |
|------|---------|
| **API key** | Secret from [Dashboard → API keys](https://relaypdf.com/dashboard/keys). Prefix `pdf_live_…`. Sent as `Authorization: Bearer <key>`. |
| **Wallet** | Prepaid USD. Ledger unit is **millicents**: 1 millicent = $0.001. `$5.00` trial = `5000` millicents. `client.account()` returns `wallet.balanceMillicents`. |
| **Template** | Handlebars draft. Publish, then render with `templateId` + `templateData`. |
| **Job** | Async generation (`response="async"`). Poll `jobs.wait`. |
| **File** | 24-hour download at `GET /v1/files/:id`. No API key on GET. |
| **Document worker** | LibreOffice / wkhtml / pdftoppm behind convert and raster. |

## Rate limiting

Trial: **20/min**. Funded or auto-reload: **60/min**. Burst: **5 / 10s**. HTTP 429 includes `Retry-After`. Not billed.

## Libraries and SDKs

| Surface | Package | Install |
|---------|---------|---------|
| Python | [`relaypdf`](https://pypi.org/project/relaypdf/) | `pip install relaypdf` |
| Node.js | [`@relaypdf/sdk`](https://www.npmjs.com/package/@relaypdf/sdk) | `npm install @relaypdf/sdk` |
| PHP | [`relaypdf/relaypdf`](https://packagist.org/packages/relaypdf/relaypdf) | `composer require relaypdf/relaypdf` |
| C# / .NET | [`RelayPDF`](https://www.nuget.org/packages/RelayPDF) | `dotnet add package RelayPDF` |
| Java | [`com.relaypdf:relaypdf`](https://central.sonatype.com/) | Maven `com.relaypdf:relaypdf` |
| CLI + MCP | [`@relaypdf/cli`](https://www.npmjs.com/package/@relaypdf/cli) | `npx @relaypdf/cli setup` |
| n8n | [`n8n-nodes-relaypdf`](https://www.npmjs.com/package/n8n-nodes-relaypdf) | Community Nodes |
| REST | — | `https://api.relaypdf.com` |

Do not ask a human to paste an API key. Run `npx @relaypdf/cli setup` and approve in the browser.

## Authentication

```python
import os
from relaypdf import RelayPDF

client = RelayPDF(api_key=os.environ["RELAYPDF_API_KEY"])
# client = RelayPDF(api_key=..., base_url="http://localhost:8787")
```

Missing `api_key` raises `TypeError`. Invalid keys return HTTP 401 `unauthorized`.

User-Agent: `relaypdf-python/0.1.3 (+https://relaypdf.com)`.

## Error codes

The client raises `RelayPDFError` with `status`, `code`, `message`, and optional `retry_after`.

| HTTP | `code` | Description |
|------|--------|-------------|
| 400 | `invalid_request` | Bad fields or exclusive sources |
| 400 | `url_not_allowed` | Private URL or non-https `callbackUrl` |
| 401 | `unauthorized` | Missing or unknown API key |
| 402 | `payment_required` | Empty wallet |
| 403 | `account_suspended` | Account cannot use keys |
| 404 | `not_found` | Unknown job, file, or template |
| 413 | `payload_too_large` | HTML or file too large |
| 429 | `rate_limited` | Honor `retry_after`; not billed |
| 502 | `render_failed` / `processing_failed` | Unbilled |
| 503 | `convert_unavailable` / `ai_unavailable` / `storage_unavailable` | Unbilled |
| 500 | `internal_error` | Unbilled |

```python
from relaypdf import RelayPDF, RelayPDFError

try:
    client.pdf.from_url("https://example.com")
except RelayPDFError as err:
    print(err.status, err.code, err.message, err.retry_after)
```

## Billing

| Job | Cost |
|-----|------|
| HTML / URL / Markdown / template PDF | $0.015 |
| Screenshot | $0.015 |
| AI template generate | $0.05 |
| LibreOffice convert | $0.04 |
| wkhtmltopdf | $0.025 |
| Tools (merge, stamp, raster, barcode, zip, …) | $0.005 |

New accounts: **$5.00** trial (`5000` millicents). Only successful jobs debit.

## Installation

```bash
pip install relaypdf
```

```bash
uv add relaypdf
```

## Getting started

```python
import os
from relaypdf import RelayPDF

client = RelayPDF(api_key=os.environ["RELAYPDF_API_KEY"])

pdf = client.pdf.from_html(
    "<h1>Invoice #1042</h1><p>Total: $1,200.00</p>",
    filename="invoice.pdf",
)
pdf.save("invoice.pdf")
print(pdf.id, pdf.size_bytes, pdf.content_type)
```

## Response modes

`response="binary"` (default) | `"url"` | `"async"`. Same as REST.

```python
url_result = client.pdf.from_html("<h1>Hi</h1>", response="url")
print(url_result.url, url_result.expires_at)

job = client.convert.from_path("deck.pptx", to="pdf", response="async")
done = client.jobs.wait(job.id)
file = client.files.download(done["id"])
file.save("deck.pdf")
```

`files.download` does not send the API key.

## Client options

```python
RelayPDF(api_key: str, base_url: str = "https://api.relaypdf.com", opener=None)
```

`opener` injects `urlopen` for tests. The SDK does not retry.

`file=` may be `bytes` or a base64 `str`.

## Documentation for API methods

All URIs are relative to `https://api.relaypdf.com`. JSON bodies use REST camelCase field names.

| Resource | Method | HTTP | Description |
|----------|--------|------|-------------|
| `RelayPDF` | `health()` | `GET /health` | Liveness. No API key. |
| `RelayPDF` | `account()` | `GET /v1/account` | Plan, rate tier, wallet. Not billed. |
| `pdf` | `create(**input)` | `POST /v1/pdf` | HTML, URL, Markdown, or template → PDF |
| `pdf` | `from_html(html, **extra)` | `POST /v1/pdf` | HTML → PDF |
| `pdf` | `from_url(url, **extra)` | `POST /v1/pdf` | Public URL → PDF |
| `pdf` | `from_markdown(markdown, **extra)` | `POST /v1/pdf` | Markdown → PDF |
| `pdf` | `from_template(templateId, templateData, **extra)` | `POST /v1/pdf` | Published template → PDF |
| `pdf` | `merge(files, **extra)` | `POST /v1/pdf/merge` | Merge 2–20 PDFs |
| `pdf` | `extract(pages, **input)` | `POST /v1/pdf/extract` | Extract page ranges |
| `pdf` | `protect(userPassword, **input)` | `POST /v1/pdf/protect` | Password-protect |
| `pdf` | `unlock(password, **input)` | `POST /v1/pdf/unlock` | Remove password |
| `pdf` | `bookmarks(bookmarks, **input)` | `POST /v1/pdf/bookmarks` | Outline bookmarks |
| `pdf` | `raster(**input)` | `POST /v1/pdf/raster` | Pages → PNG/JPEG |
| `pdf` | `from_images(files, **extra)` | `POST /v1/pdf/from-images` | Images → PDF |
| `pdf` | `stamp(**input)` | `POST /v1/pdf/stamp` | Text or image watermark |
| `pdf` | `rotate(degrees, **input)` | `POST /v1/pdf/rotate` | Rotate pages |
| `pdf` | `delete_pages(pages, **input)` | `POST /v1/pdf/delete-pages` | Delete pages |
| `pdf` | `compress(**input)` | `POST /v1/pdf/compress` | Lossless optimize |
| `pdf` | `info(**input)` | `POST /v1/pdf/info` | Metadata JSON |
| `pdf` | `text(**input)` | `POST /v1/pdf/text` | Extract text layer |
| `pdf` | `form_fields(**input)` | `POST /v1/pdf/form/fields` | List AcroForm fields |
| `pdf` | `form_fill(fields, **input)` | `POST /v1/pdf/form/fill` | Fill and flatten |
| `images` | `create(**input)` | `POST /v1/images` | HTML or URL → image |
| `images` | `from_html(html, **extra)` | `POST /v1/images` | HTML screenshot |
| `images` | `from_url(url, **extra)` | `POST /v1/images` | URL screenshot |
| `convert` | `create(**input)` | `POST /v1/convert` | LibreOffice or wkhtml |
| `convert` | `from_html(html, **extra)` | `POST /v1/convert` | HTML → docx/xlsx/pdf |
| `convert` | `from_path(path, **extra)` | `POST /v1/convert` | Local Office file |
| `convert` | `wkhtml(**input)` | `POST /v1/convert` | wkhtmltopdf engine |
| `templates` | `list()` | `GET /v1/templates` | List drafts |
| `templates` | `gallery()` | `GET /v1/templates/gallery` | Stock layouts |
| `templates` | `get(id)` | `GET /v1/templates/:id` | Read |
| `templates` | `create(**input)` | `POST /v1/templates` | Create draft |
| `templates` | `update(id, **input)` | `PATCH /v1/templates/:id` | Update draft |
| `templates` | `delete(id)` | `DELETE /v1/templates/:id` | Delete |
| `templates` | `publish(id, comment=None)` | `POST /v1/templates/:id/publish` | Publish |
| `barcodes` | `create(**input)` | `POST /v1/barcodes` | Barcode / QR |
| `barcodes` | `qr(text, **extra)` | `POST /v1/barcodes` | QR helper |
| `zip` | `create(files, **extra)` | `POST /v1/zip` | Zip files |
| `jobs` | `get(id)` | `GET /v1/jobs/:id` | Poll job |
| `jobs` | `wait(id, interval_ms=1000, timeout_ms=120000)` | `GET /v1/jobs/:id` | Wait until done |
| `files` | `download(id)` | `GET /v1/files/:id` | 24h download |
| `webhooks` | `list()` | `GET /v1/webhooks` | List signed endpoints (secret never listed) |
| `webhooks` | `create(url, events=None)` | `POST /v1/webhooks` | Create HTTPS endpoint; `secret` once; cap 25; not billed |
| `webhooks` | `delete(id)` | `DELETE /v1/webhooks/:id` | Unsubscribe |
| — | `verify_webhook(secret, body, header)` | Dashboard webhook | HMAC-SHA256 |

Node has additional template helpers (`discard`, `duplicate`, `versions`, `restore`, `validate`, `preview`, `generate`). Those REST paths are available with raw HTTP if you need them before a Python minor.

## Method examples

```python
pdf = client.pdf.from_url(
    "https://example.com",
    filename="page.pdf",
    options={"format": "A4", "printBackground": True},
)

md = client.pdf.from_markdown("# Hello\n\nFrom **Markdown**.")

invoice = client.pdf.from_template(
    "invoice",
    {"number": "INV-1042", "total": 1458},
    filename="invoice.pdf",
    strict=True,
)

shot = client.images.from_url("https://example.com", options={"fullPage": True})
from_word = client.convert.from_path("letter.docx", to="pdf")
docx = client.convert.from_html("<h1>Report</h1>", to="docx")

pack = client.pdf.merge(
    files=[
        {"url": "https://example.com/cover.pdf"},
        {"file": from_word.bytes},
    ]
)
client.pdf.stamp(file=pack.bytes, text="DRAFT", rotate=-24)
qr = client.barcodes.qr("https://relaypdf.com")
```

## Result types

| Class | Fields |
|-------|--------|
| `BinaryResult` | `kind`, `id`, `filename`, `size_bytes`, `content_type`, `bytes`, `save(path)` |
| `UrlResult` | `kind`, `id`, `status`, `url`, `filename`, `size_bytes`, `expires_at` |
| `AsyncResult` | `kind`, `id`, `status`, `poll_url` |

Exports: `RelayPDF`, `RelayPDFError`, `DEFAULT_BASE_URL`, `verify_webhook`, `WEBHOOK_SIGNATURE_HEADER`, `WEBHOOK_EVENT_HEADER`.

## Webhooks

Use the **raw** request body. Create and list endpoints with `client.webhooks`. Secret is the dashboard webhook secret, not the API key.

```python
created = client.webhooks.create("https://example.com/hooks", ["job.completed"])
# created["secret"] is shown once

from relaypdf import verify_webhook, WEBHOOK_SIGNATURE_HEADER

ok = verify_webhook(
    os.environ["RELAYPDF_WEBHOOK_SECRET"],
    raw_body,
    signature_header,
)
```

Header format: `t=<unix>,v1=<hex>`. HMAC-SHA256 of `{t}.{raw_body}`. Default skew 300s.

## Authorization

HTTP Bearer. Not JWT.

## Related docs

- [Node.js SDK](https://relaypdf.com/docs/sdks/node)
- [PHP SDK](https://relaypdf.com/docs/sdks/php)
- [C# / .NET SDK](https://relaypdf.com/docs/sdks/dotnet)
- [Java SDK](https://relaypdf.com/docs/sdks/java)
- [CLI](https://relaypdf.com/docs/cli)
- [MCP](https://relaypdf.com/docs/mcp)
- [Errors](https://relaypdf.com/docs/errors)
- [Wallet](https://relaypdf.com/docs/wallet)

## License

MIT. Strategic Products LLC, d/b/a RelayPDF.
