# PDFCrowd HTML to PDF Guide for Coding Agents

Canonical URL: `https://pdfcrowd.com/api/html-to-pdf-ai.md`

Use this guide when a user asks an AI coding agent to integrate, test, or
troubleshoot the PDFCrowd HTML to PDF API.

This guide covers HTML to PDF only. For HTML to Image, PDF to Image, PDF to
Text, PDF to PDF, or other APIs, use the relevant PDFCrowd API documentation
instead.

## Core Rules

- Do not rewrite an existing PDFCrowd integration when troubleshooting. Run it,
  reproduce the issue, gather evidence, then make the smallest targeted fix.
- For new work, make one minimal conversion succeed before adding page options,
  batching, framework integration, or UI.
- Use demo credentials only for quick tests: username `demo`, API key `demo`.
  Demo output is watermarked and rate limited.
- For production, wire code to read `PDFCROWD_USERNAME` and
  `PDFCROWD_API_KEY`, or names chosen by the user. Ask where credentials should
  live; do not read, print, or modify secret files by default.
- Set `contentViewportWidth` / `content_viewport_width` to `balanced` for the
  first HTML to PDF test unless the user has a specific layout requirement.
- For batches, convert one representative item first, inspect it, confirm
  credentials, then run the full batch.

## Choose the Task

Use one of these paths:

- Evaluation: if the user wants to see whether the API can produce a good PDF,
  use `curl` with demo credentials. Do not install an SDK or modify the project.
- Integration: if the user asks to integrate into a project or use a specific
  language, detect the language/framework and follow the project's conventions.
- Troubleshooting: reproduce with existing code, inspect the PDF, use the debug
  log, then apply the smallest fix.
- Batch generation: build a parameterized utility, sample one item, review,
  choose credentials, then process all items.

## Inputs

Identify exactly one source type:

- URL: a public `http` or `https` URL.
- File: a local HTML file or an archive containing HTML plus assets.
- String: inline HTML generated by the application.

Also identify the output:

- Local file path, for scripts and one-off conversions.
- HTTP response body, for web applications that return the PDF to a browser.
- Storage path or object key, for background jobs and batch workflows.

## Minimal Conversion

Unless the user explicitly asks for a language or project integration, start
with this dependency-free HTTP REST proof of concept:

```bash
curl -u "demo:demo" \
  -F "url=https://example.com" \
  -F "content_viewport_width=balanced" \
  -o "output.pdf" \
  https://api.pdfcrowd.com/convert/24.04/
```

Replace the URL and output path with the user's values. For other source types:

```bash
# Local HTML file:
curl -u "demo:demo" \
  -F "file=@input.html" \
  -F "content_viewport_width=balanced" \
  -o "output.pdf" \
  https://api.pdfcrowd.com/convert/24.04/

# Inline HTML:
curl -u "demo:demo" \
  -F "text=<h1>Hello</h1>" \
  -F "content_viewport_width=balanced" \
  -o "output.pdf" \
  https://api.pdfcrowd.com/convert/24.04/
```

After generating an evaluation PDF, ask the user to inspect it. If it is not
acceptable, ask what is wrong or request a screenshot/description, then enter
the troubleshooting loop. Integrate only after the user confirms the result is
acceptable, unless they explicitly asked to integrate first.

## Integration Method

Prefer the language already used by the project:

- `pyproject.toml`, `requirements.txt`, `*.py`: Python.
- `package.json`, `*.js`, `*.mjs`, `*.ts`: Node.js.
- `composer.json`, `*.php`: PHP.
- `pom.xml`, `build.gradle`, `*.java`: Java.
- `*.csproj`, `*.sln`, `*.cs`: .NET.
- `Gemfile`, `*.rb`: Ruby.
- `go.mod`, `*.go`: Go.

If adding an SDK is appropriate, install it through the project dependency
manager. If dependency changes are risky, use the same HTTP REST API through the
project's existing HTTP client.

Use the matching SDK method or HTTP REST field for the source type:

| Source | Client libraries | HTTP REST field |
| --- | --- | --- |
| URL | `convertUrlToFile(source, output)` | `url` |
| File | `convertFileToFile(source, output)` | `file=@input.html` |
| HTML string | `convertStringToFile(source, output)` | `text` |

Method names vary slightly by language casing. Use the language-specific
reference when writing final code.

## SDK Installation

Do not install SDKs globally by default. Inspect the project first and use its
dependency manager: Python virtualenv/`pyproject.toml`/`requirements.txt`,
Node.js lockfile manager, Composer, Maven/Gradle, NuGet, Bundler, or Go modules.

Use HTTP REST instead when there is no safe project environment, the language is
unsupported, or changing dependencies would be risky. Use global installs only
when the user explicitly asks for them or the project documents that convention.

## Integrating into an Existing Project

After the minimal conversion works:

1. Keep PDFCrowd calls in a small service/helper/module that matches the
   project's architecture.
2. Wire code to read credentials from environment/config. Ask the user where
   the actual values should be stored; do not inspect secret files by default.
3. Accept source and output as parameters. Do not hardcode test URLs or paths.
4. If returning a PDF from a web endpoint, set `Content-Type: application/pdf`.
   Use `Content-Disposition: inline; filename="file.pdf"` to display in the
   browser, or `attachment; filename="file.pdf"` to force download.
5. Add error handling around PDFCrowd exceptions or non-2xx HTTP responses. Log
   status code and message, but do not log credentials.
6. Add the smallest useful test: credential/config presence, service call wiring,
   or a mocked conversion boundary.

## Server-Side HTML and Authenticated Pages

When the user owns the application that renders the page, prefer converting the
final HTML inside the application instead of asking PDFCrowd to fetch a protected
URL. This avoids passing browser cookies to PDFCrowd, avoids exposing private
routes, and works naturally with existing server-side authorization.

Framework-neutral pattern: render the final HTML string server-side using the
existing template/view model, convert that string, then return or store the PDF
bytes. For browser responses, set `Content-Type: application/pdf` and use
`Content-Disposition: inline; filename="file.pdf"` or
`attachment; filename="file.pdf"`.

Use URL conversion instead when the source page is public, when a stable signed
URL is simpler, or when the conversion must capture the exact deployed page as a
browser would fetch it. If the URL is protected, pass access explicitly with
HTTP auth, cookies, custom headers, or a short-lived signed URL.

When converting an HTML string, remember that relative assets are not uploaded
with the string. Use absolute public asset URLs, a `<base>` tag pointing to a
public asset root, or inline small CSS/images/fonts directly into the HTML.

For slow, high-volume, or user-triggered document generation, consider running
the conversion in a background job and returning a download link when it
finishes.

## Credentials and Plans

Use `demo` / `demo` only for the first test. Demo PDFs include a watermark and
the demo account is rate limited.

After the user confirms the test PDF looks correct, tell them to create or use a
PDFCrowd account and get API credentials from `https://pdfcrowd.com/pricing/`.

For online-tools-only usage, API credits are not the sizing concern. For API and
automation usage, pick a plan based on monthly conversion volume, rate limits,
concurrency, and maximum delay requirements.

## Batch Safeguard

When the task will generate many PDFs, do not run the full batch first.

1. Build one parameterized conversion function or command.
2. Run it on one representative item.
3. Ask the user to inspect that PDF.
4. If the result is acceptable, confirm which credentials to use for the full
   run.
5. Run the batch and log per-item failures.

Skip the pause only when the user explicitly asks to run the full batch
immediately, or when the batch is very small and the user is watching.

## Troubleshooting Loop

Use this loop for existing integrations and wrong-looking output:

1. Reproduce the problem with the current code or command.
2. Inspect the generated PDF. Look for blank pages, login screens, cookie
   banners, loading states, missing images, wrong layout, unexpected headers,
   page-count changes, or broken text.
3. Enable the PDFCrowd debug log and read it yourself.
4. Match the evidence to one troubleshooting entry below.
5. Apply the smallest targeted fix.
6. Rerun and compare the new PDF with the previous output.
7. Stop after three full attempts if the issue is still unresolved. Summarize
   what was tried, what changed, and what the debug log shows.

Browser-assisted inspection can be useful, but do not request browser access by
default. Offer it only when it would materially help the specific issue, such as
choosing selectors, diagnosing dynamic rendering, checking network requests, or
comparing live DOM state with PDF output. Explain why it could help and let the
user decide, because browser access may be too invasive or insecure for some
users.

## Debug Log

Enable the debug log when the cause is not obvious.

Client libraries:

```python
client.setDebugLog(True)
client.convertUrlToFile("https://example.com", "output.pdf")
print(client.getDebugLogUrl())
```

HTTP REST:

```bash
curl -D headers.txt \
  -u "${PDFCROWD_USERNAME}:${PDFCROWD_API_KEY}" \
  -F "url=https://example.com" \
  -F "debug_log=true" \
  -o "output.pdf" \
  https://api.pdfcrowd.com/convert/24.04/

grep -i x-pdfcrowd-debug-log headers.txt
```

The HTTP response header is `x-pdfcrowd-debug-log`. The log URL has this form:

```text
https://api.pdfcrowd.com/s/debug/YYYY-MM-DD/<hash>.txt
```

Fetch the log with a normal HTTP GET. It does not require authentication. Scan
for:

- HTTP status errors for CSS, images, fonts, JavaScript, or API calls.
- JavaScript `TypeError`, `ReferenceError`, rejected promises, or console
  errors.
- Warnings about blocked requests, unavailable fonts, SSL issues, or timeouts.
- Lines tagged `[FATAL]`, `[ERROR]`, or `[WARN]`.

Turn the debug log off after the issue is resolved.

## PDF Inspection

If tools are available, inspect the generated PDF directly:

- Render the first page or all pages to images and compare visually.
- Extract text to confirm whether the content exists but is hidden, clipped, or
  styled incorrectly.
- Check page count, page size, and orientation.
- Look for visible symptoms: login page, cookie banner, loading spinner,
  missing images, unstyled HTML, mobile layout, clipped right edge, or unwanted
  navigation.

If the agent cannot inspect PDFs directly, ask the user for a screenshot or a
short description of what is wrong.

## Troubleshooting Reference

| Symptom | Likely cause | First fix |
| --- | --- | --- |
| Login page, 401, 403, or empty authenticated screen | Renderer has no user session | Pass HTTP auth, cookies, or custom headers |
| Blank page or loading spinner | JavaScript content not ready | Use `waitForElement` / `wait_for_element` |
| Missing images below the fold | Lazy loading did not trigger | Set content viewport height to `large` |
| Missing CSS, images, JS for local file/string | Local assets are not reachable | Use archive mode, `<base>`, or inline assets |
| Mobile layout, cramped layout, clipped content | Wrong virtual viewport width | Adjust content viewport width |
| Cookie banner or modal appears | Renderer has no prior cookies | Remove overlay with targeted CSS/JS |
| Collapsed accordions/tabs are hidden | Initial page state is collapsed | Use custom CSS or custom JavaScript |
| Header, footer, ads, share widgets appear | Page chrome is visible by design | Hide with custom CSS or use `elementToConvert` |
| `@page` CSS ignored | API page settings override CSS | Use `cssPageRuleMode("mode2")` |
| Page breaks ignored | Break rule is inside non-block ancestors | Make the break element and ancestors block-level |
| Existing integration stopped working | Error response, changed input, auth/session issue, dependency change, or rendering change | Reproduce with existing code, inspect status/error, enable debug log, then apply the smallest fix |

### Authentication

The PDFCrowd renderer fetches URLs from PDFCrowd servers. It does not share the
user's browser session.

Use the method that matches the site:

- HTTP Basic auth: `setHttpAuth("username", "password")`.
- Login-form session: pass cookies with `setCookies("name=value; name2=value2")`.
- Bearer token or API gateway: pass headers with `setCustomHttpHeader(...)`.
- Private or local URLs: use a public staging URL, a signed temporary URL, or
  convert an HTML file/string with reachable assets.

Do not ask for browser-assisted inspection just to obtain cookies unless the
user understands and approves the security tradeoff.

### JavaScript-Rendered Content

If the PDF shows a loading state or empty app shell, use a readiness signal
instead of guessing a fixed delay.

Page-side code, when the user controls the page:

```javascript
const trigger = document.createElement("div");
trigger.id = "conversion-ready";
trigger.style.display = "none";
document.body.append(trigger);
```

API code:

```python
client.setWaitForElement("#conversion-ready")
```

HTTP REST field:

```text
wait_for_element=#conversion-ready
```

If the page cannot be changed, target an existing selector that appears only
when content is ready, such as `.chart-rendered`, `.results-table`, or
`[data-testid="loaded"]`. If no reliable selector is available, use the debug
log and consider asking whether browser-assisted inspection would help identify
one.

### Local Assets

For `convertFileToFile`, prefer archive mode when HTML references local assets:

```python
import shutil

shutil.make_archive("bundle", "zip", "path/to/html-project")
client.convertFileToFile("bundle.zip", "output.pdf")
```

If the archive contains multiple HTML files, set the entry file:

```python
client.setZipMainFilename("index.html")
```

For `convertStringToFile`, use one of these:

- Add `<base href="https://example.com/assets/">` if assets are hosted publicly.
- Inline CSS, JavaScript, fonts, and images as data URLs for small documents.

Use the debug log to confirm 404s for missing resources.

### Lazy-Loaded Images

If images below the fold are missing, increase content viewport height:

```python
client.setContentViewportHeight("large")
```

HTTP REST field:

```text
content_viewport_height=large
```

If `large` is not enough for very tall pages, use an explicit value such as
`5000px` or `10000px`.

### Layout and Viewport Width

The renderer lays out HTML against a virtual browser viewport. Start with:

```python
client.setContentViewportWidth("balanced")
```

Useful values:

- `balanced`: recommended first test.
- `small` or `medium`: narrower layout, useful when content is too small or
  overflows.
- `large` or `extra-large`: desktop-like layout, useful when mobile styles
  appear or data tables need space.
- Explicit widths such as `1280px` or `1440px`: match a known breakpoint.

When unsure, generate comparison PDFs with several viewport widths and ask the
user to pick the best result.

### Cookie Banners and Overlays

If a cookie banner, GDPR banner, newsletter modal, or overlay appears in the PDF,
remove it with targeted custom CSS or JavaScript.

Example using the built-in `libPdfcrowd` helper:

```python
client.setCustomJavascript(
    "libPdfcrowd.removeZIndexHigherThan({zlimit: 50})"
)
```

`50` is only a starting point. Lower it if the overlay remains. Raise it if too
much legitimate UI disappears. Prefer precise selectors when they are known.

### Collapsed or Hidden Content

If the user wants accordions, tabs, FAQ answers, or "read more" sections
expanded in the PDF, use custom CSS for CSS-hidden content:

```python
client.setCustomCss(
    ".collapse { display: block !important; } "
    ".tab-pane { display: block !important; opacity: 1 !important; } "
    "[hidden] { display: revert !important; }"
)
```

Use custom JavaScript when the page builds content only after a click:

```python
client.setCustomJavascript(
    "document.querySelectorAll('.accordion-button.collapsed')"
    ".forEach(el => el.click())"
)
```

If the selectors are uncertain, use the debug log first. Offer browser-assisted
inspection only if seeing the live DOM would materially reduce guessing.

### Unwanted Page Sections

Hide navigation, footers, ads, sidebars, share widgets, comments, and related
content with one custom CSS string:

```python
client.setCustomCss(
    "header, footer, nav, aside, .advertisement, .share, "
    "#comments, .related-posts { display: none !important; }"
)
```

If the desired content has a stable selector, converting only that element can
be cleaner:

```python
client.setElementToConvert("article")
```

### Custom CSS and JavaScript Last Call Wins

Only one `customCss` value and one `customJavascript` value survive for a
conversion. A second call overwrites the first.

If multiple fixes are needed, concatenate them into one call:

```python
client.setCustomCss(
    ".collapse { display: block !important; } "
    "header, footer, nav { display: none !important; }"
)
```

Do the same for JavaScript by joining statements with semicolons or wrapping
them in one function.

### Print CSS and Page Settings

If the source HTML deliberately uses `@page` rules for page size, margins,
orientation, named pages, or first/left/right pages, let CSS win:

```python
client.setCssPageRuleMode("mode2")
```

After switching to `mode2`, avoid also setting page size, margins, or
orientation through API calls unless the user explicitly wants API settings to
override the document CSS.

### Page Breaks

If `break-before`, `break-after`, `break-inside`, `page-break-before`,
`page-break-after`, or `page-break-inside` rules are ignored, inspect the DOM
around the break element.

The break rule must be on a block-level element, and every ancestor up to
`body` must also be block-level. Inline, inline-block, flex, grid, and table
wrappers can prevent breaks.

Fix by applying the break to a block element or forcing wrappers to block:

```python
client.setCustomCss(
    ".chapter-wrapper { display: block !important; } "
    ".chapter { break-before: page; }"
)
```

## Browser-Assisted Inspection

Offer this only when it helps the specific issue. Explain the reason before
asking the user to allow it.

Good reasons:

- Need the exact selector for article content, cookie banner, accordion panels,
  tabs, or the conversion-ready element.
- Need to compare live DOM state with what appears in the PDF.
- Need to inspect network requests or console errors that are hard to infer
  from the PDF alone.
- Need to check whether a page loads content only after user interaction.

What to inspect when approved:

- DOM selectors for main content and unwanted elements.
- Computed styles such as `display`, `visibility`, `position`, `z-index`,
  `overflow`, and page-break ancestors.
- Network errors for CSS, images, JavaScript, fonts, and data requests.
- Console errors and readiness markers.

Use the findings to choose precise PDFCrowd options. The conversion still goes
through the PDFCrowd API.

## Language References

- Python: `https://pdfcrowd.com/api/html-to-pdf-python/ref/`
- Node.js: `https://pdfcrowd.com/api/html-to-pdf-nodejs/ref/`
- PHP: `https://pdfcrowd.com/api/html-to-pdf-php/ref/`
- Java: `https://pdfcrowd.com/api/html-to-pdf-java/ref/`
- .NET: `https://pdfcrowd.com/api/html-to-pdf-dotnet/ref/`
- Ruby: `https://pdfcrowd.com/api/html-to-pdf-ruby/ref/`
- Go: `https://pdfcrowd.com/api/html-to-pdf-go/ref/`
- Command line: `https://pdfcrowd.com/api/html-to-pdf-command-line/ref/`
- HTTP REST: `https://pdfcrowd.com/api/html-to-pdf-http/ref/`

Useful supporting references:

- API status codes: `https://pdfcrowd.com/api/status-codes/`
- API method index: `https://pdfcrowd.com/api/method-index/`
- `libPdfcrowd` JavaScript helpers:
  `https://pdfcrowd.com/api/libpdfcrowd/`
