DEV Community

Ghezam Capital
Ghezam Capital

Posted on

Turning a URL into a PDF (and catching when it changes) with one API call

A lot of "URL to PDF" code starts the same way: spin up Puppeteer or Playwright, open the page, call page.pdf(), hope the layout survives headless rendering. It works, until it's the one script nobody wants to touch because it depends on a specific Chromium version.

I added PDF output to Screenshot Happy mostly because I kept needing it for the same reasons I needed screenshots: invoices-as-pages, saved copies of terms/pricing pages, "here's what this looked like" archiving.

The request

Same endpoint as the PNG/JPEG capture, just a different format:

curl -G "https://screenshot-api-production-ffd7.up.railway.app/screenshot" \
  -H "x-api-key: YOUR_API_KEY" \
  --data-urlencode "url=https://example.com" \
  -d "format=pdf" \
  -d "full_page=true" \
  -o page.pdf
Enter fullscreen mode Exit fullscreen mode

format=pdf renders the page as it actually looks on screen — not a print stylesheet. Add full_page=true if you want one page sized to the whole scrollable content instead of just the viewport. It's not compatible with selector (that one's PNG/JPEG only, for grabbing a single element).

The other half: knowing when the page changes

Capturing a page once is easy. The harder question is usually "did this change since last time, and do I need to know about it?" That's what /monitors is for — same capture engine, running on a schedule:

curl -X POST "https://screenshot-api-production-ffd7.up.railway.app/monitors" \
  -H "x-api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://example.com/pricing",
    "webhook_url": "https://your-server.com/webhook",
    "frecuencia_minutos": 60,
    "umbral_diferencia": 5
  }'
Enter fullscreen mode Exit fullscreen mode

It screenshots the URL on that interval, diffs the new capture against the last one, and POSTs to your webhook only when the difference crosses the threshold (umbral_diferencia, percent of changed pixels). No polling loop to write, no image-diffing library to pick and maintain.

Where this is actually useful

  • QA / visual regression — capture a page (or a specific element via selector) before and after a deploy and diff them, without keeping a headless browser alive in CI.
  • Archiving — a PDF snapshot of a contract, terms page or listing at a point in time.
  • Competitive/vendor tracking — get a webhook when a competitor's pricing page or a vendor's ToS actually changes, instead of checking manually.

Limits, honestly

Free tier is 200 screenshots/month and 3 monitored pages checked every 6h — enough to try it, not enough to run serious production monitoring on. Paid plans (from €9/month) raise both the screenshot quota and the check frequency, and add webhook alerts. Full parameters and error codes are in the docs.

If you're already generating PDFs or diffing screenshots with your own Puppeteer/Playwright setup and it's working fine, this isn't trying to talk you out of that. If you're the one who gets paged when that setup breaks, this is meant to remove that job, not replace a tool you like.

Curious whether people doing visual regression testing today are rolling their own diffing, or using a dedicated tool for it — what does that stack actually look like for you?

Top comments (2)

Collapse
 
respect17 profile image
Kudzai Murimi •

Pairing the PDF export with your existing screenshot diff monitor instead of building a separate pipeline is the right call. One capture engine, two output formats. Does umbral_diferencia account for stuff like ads or timestamps that change on every load, or does that end up needing a selector scoped diff to avoid false positives?

Collapse
 
ghezam_capital_0898 profile image
Ghezam Capital •

Good question — right now umbral_diferencia is just a flat percentage-of-changed-pixels across the whole page, so yes, a rotating ad slot or a live timestamp would eat into that budget and could trigger a false positive if the threshold's set low. There's no selector-scoped diff for monitors today — selector currently only scopes the screenshot capture itself (PNG/JPEG), not the comparison. Workaround for now: set a higher threshold on pages with known-noisy elements. A selector-scoped monitor diff is a fair feature request though — noted.