# PDF4.dev: Agent Instructions

## What this service does

PDF4.dev is a PDF generation API that lets developers create pixel-perfect PDFs from HTML templates. Design templates visually or in code using a Monaco-based editor with live preview, define dynamic fields with Handlebars {{variables}}, and generate PDFs via a simple REST API call from any language: TypeScript, Python, Go, Rust, PHP, or cURL. The rendering pipeline uses headless Chromium (Playwright) for accurate CSS support including flexbox, grid, and web fonts. PDF4.dev also provides a built-in MCP server so AI agents like Claude, ChatGPT, and Cursor can generate PDFs with natural language. Free browser-based PDF tools (merge, split, compress, convert) process files entirely client-side for privacy. Self-hostable via Docker with SQLite storage, PDF4.dev is a developer-first alternative to PDFMonkey, DocRaptor, and DIY Puppeteer setups.

## IMPORTANT: Base URL and HTTP methods

- Base URL: `https://pdf4.dev`. There is NO `api.pdf4.dev` subdomain. Always use `https://pdf4.dev/api/v1/...`.
- Supported HTTP methods: GET, POST, PUT, DELETE only. PATCH is NOT supported.
- Do NOT invent endpoints. There is no `/update`, `/edit`, or similar. Use PUT on the resource URL.
- Template body field is `html` (not `content`, `body`, or `template`).

## Authentication

All API calls require a Bearer token in the Authorization header:

```
Authorization: Bearer p4_live_xxx
```

Get an API key at: https://pdf4.dev/dashboard/settings

Two permission scopes:
- `full_access`: all endpoints (render, templates CRUD, components, logs)
- `render_only`: only POST /api/v1/render

## OpenAPI Specification

https://pdf4.dev/api/v1/openapi.json

## Primary Actions

### 1. Generate a PDF (most common)

```
POST https://pdf4.dev/api/v1/render
Content-Type: application/json
Authorization: Bearer p4_live_xxx

{
  "template_id": "invoice",
  "data": {
    "company_name": "Acme Corp",
    "invoice_number": "INV-001",
    "total": "$1,500.00"
  }
}
```

Response: `application/pdf` binary

You can also render from raw HTML instead of a template:

```json
{
  "html": "<h1>Hello {{name}}</h1>",
  "data": { "name": "World" }
}
```

### 2. List templates

```
GET https://pdf4.dev/api/v1/templates
Authorization: Bearer p4_live_xxx
```

### 3. Create a template

```
POST https://pdf4.dev/api/v1/templates
Content-Type: application/json
Authorization: Bearer p4_live_xxx

{
  "name": "Monthly Invoice",
  "html": "<h1>Invoice #{{invoice_number}}</h1><p>Amount: {{total}}</p>",
  "sample_data": { "invoice_number": "INV-001", "total": "$1,000.00" }
}
```

### 4. View generation logs

```
GET https://pdf4.dev/api/v1/logs?limit=50&status=success
Authorization: Bearer p4_live_xxx
```

### 5. Create and attach header/footer components

Components are reusable HTML fragments shared across templates:

- **header**: repeats at the top of every printed page (rendered via `<thead>` in a table layout)
- **footer**: repeats at the bottom of every printed page (rendered via `<tfoot>`)
- **block**: renders inline where placed in the template HTML

**Managing components (MCP tools):**
- `create_component`: Create a header, footer, or block with `{{variables}}`. In footers, use `<span class="pageNumber"></span>` and `<span class="totalPages"></span>` for page numbers.
- `list_components` / `get_component`: List or inspect components.
- `update_component` / `delete_component`: Modify or remove components.
- `create_template` / `update_template`: Attach header/footer via `header_component_id` and `footer_component_id` (comp_xxx IDs). Embed blocks in HTML with `<pdf4-block component-id="comp_xxx">Label</pdf4-block>`.

**Component HTML best practices:**
- Write components as **div fragments**, not full HTML documents. No `<!DOCTYPE>`, `<html>`, or `<body>` tags.
- Components **inherit parent template styles** (font, color, background). If `font_family` or `google_fonts_url` is set on the template's format, components use those fonts automatically.
- Keep component-specific styling inline or in a `<style>` block within the component.
- `component_gap` on the format adds spacing between header/content and content/footer.
- `footer_position` controls where the footer renders: `"after-content"` (default) places it after content, `"page-bottom"` pins it to the bottom of every page.
- For alignment options, use `text_align`, `horizontal_align`, and `vertical_align` on the format.

**How rendering works:**
1. Handlebars variables are compiled in the template and all components
2. `<pdf4-header>`, `<pdf4-footer>`, `<pdf4-block>` tags are replaced with compiled component HTML
3. The DOM is restructured into a `<table>` with `<thead>` (header), `<tfoot>` (footer), `<tbody>` (content)
4. Chromium automatically repeats `<thead>` and `<tfoot>` on every printed page (CSS 2.1 paged media)
5. `component_gap` is applied as padding between the repeated elements and the body content
6. `footer_position: "page-bottom"` pins the footer to the bottom of every page (uses CSS position:fixed). Default `"after-content"` keeps footer inline with content flow.

## Page Format Options

Presets: `a4`, `a4-landscape`, `letter`, `letter-landscape`, `square`, `custom`

```json
{
  "format": {
    "preset": "a4",
    "margins": { "top": "20mm", "bottom": "20mm", "left": "15mm", "right": "15mm" },
    "background_color": "#ffffff",
    "font_family": "Roboto, sans-serif",
    "font_size": "14px",
    "color": "#333333",
    "line_height": "1.5",
    "google_fonts_url": "https://fonts.googleapis.com/css2?family=Roboto&display=swap",
    "component_gap": "5mm",
    "footer_position": "after-content"
  }
}
```

The `google_fonts_url` loads a font stylesheet in `<head>`, making it available to the template and all attached components. The `component_gap` adds spacing between header/footer components and page content. The `footer_position` controls whether the footer sticks to content (`"after-content"`, default) or pins to the bottom of every page (`"page-bottom"`).

## Error Format

All errors follow this structure:

```json
{
  "error": {
    "type": "invalid_request_error",
    "code": "missing_parameter",
    "message": "Either template_id or html is required"
  }
}
```

Error types: `authentication_error`, `invalid_request_error`, `not_found_error`, `api_error`

## MCP Integration

PDF4.dev is available as a Model Context Protocol (MCP) server for AI agents.

**MCP Server URL**: `https://pdf4.dev/api/mcp`
**Transport**: Streamable HTTP

**Authentication:** Pass your API key via the `Authorization` header (recommended). The `api_key` tool parameter is also supported as a fallback.

**Setup (Claude Desktop / Cursor / VS Code):**
```json
{
  "mcpServers": {
    "pdf4dev": {
      "url": "https://pdf4.dev/api/mcp",
      "headers": {
        "Authorization": "Bearer p4_live_xxx"
      }
    }
  }
}
```

**Available MCP tools (14 total, with annotations):**
- `get_info` -- Account overview: templates, variables, stats (read-only)
- `render_pdf` -- Generate a PDF from template or raw HTML, returns base64 (write, openWorld, idempotent)
- `preview_template` -- Render a template as PNG screenshot for visual feedback (read-only, openWorld)
- `list_templates` -- List all templates (read-only)
- `get_template` -- Get template details including HTML (read-only)
- `create_template` -- Create a new template (write)
- `update_template` -- Update an existing template (write, idempotent)
- `delete_template` -- Delete a template permanently (destructive)
- `list_components` -- List header/footer/block components (read-only)
- `get_component` -- Get component details (read-only)
- `create_component` -- Create a header/footer/block component (write)
- `update_component` -- Update a component (write, idempotent)
- `delete_component` -- Delete a component permanently (destructive)
- `list_logs` -- View generation history (read-only)

**Tool annotations:** All tools include MCP annotations (`readOnlyHint`, `destructiveHint`, `idempotentHint`, `openWorldHint`) so agents can determine which tools are safe to call without user confirmation. Read-only tools are always safe. Destructive tools (delete_template, delete_component) should prompt the user first. Open-world tools (render_pdf, preview_template) accept arbitrary HTML input.

**Data supports nested objects and arrays**, enabling Handlebars block expressions like `{{#each items}}`. The `data` parameter type is `Record<string, any>`.

**Built-in Handlebars helpers:** `formatNumber`, `formatDate`, `formatCurrency`, `uppercase`, `lowercase`, `eq`, `neq`, `gt`, `lt`, `gte`, `lte`, `math`, `padStart`. See https://pdf4.dev/llms-full.txt for full syntax.

## Contact

https://pdf4.dev
