Use cases

URL to PDF API

One GET request turns a URL into a PDF. Send pdf=1 to the same /snap endpoint that takes screenshots, and the response body is the document itself, ready to write to a file or stream to whoever asked for it.

That shared endpoint is the point. Most teams that need PDFs also need images at some stage, of the same pages, for the same reasons. Here that is one vendor, one API token and one integration, with a parameter deciding which of the two comes back.

Your first PDF

Authenticate with a bearer token from your dashboard, name the page, and ask for PDF mode. Write the body straight to a file rather than parsing it: only errors are JSON.

Terminal
curl "https://api.screenshotbuddy.io/v1/snap?url=https%3A%2F%2Fexample.com%2Finvoices%2F1024&pdf=1" \
  -H "Authorization: Bearer <your API key>" \
  --output invoice.pdf
A render costs one credit. A failed render is refunded, and a repeat of this exact request is answered out of the cache for nothing at all.

The same call from Node, with the error envelope handled. Every refusal carries a machine readable code to branch on and a message written for a person.

render-invoice.mjs
import { writeFile } from 'node:fs/promises';

const query = new URLSearchParams({
  url: 'https://example.com/invoices/1024',
  pdf: '1',
});

const response = await fetch(`https://api.screenshotbuddy.io/v1/snap?${query}`, {
  headers: { Authorization: `Bearer ${process.env.SCREENSHOTBUDDY_API_KEY}` },
});

if (!response.ok) {
  const { code, message } = await response.json();
  throw new Error(`${code}: ${message}`);
}

await writeFile('invoice.pdf', Buffer.from(await response.arrayBuffer()));
There is no SDK to install and no client to keep up to date. It is an HTTP GET with a header on it, which is why this fits in whatever language your invoices already live in.

Paper size, orientation and margins

A PDF is a printed document, so it takes the parameters a printed document has. paperFormat accepts letter, legal, tabloid, ledger, a0, a1, a2, a3, a4, a5, a6, and defaults to a4. landscape=1 turns the page on its side. The four margins are set together, all of marginTop, marginRight, marginBottom and marginLeft, in the unit marginUnit names: mm, cm, in, px, defaulting to mm.

Terminal
curl -G "https://api.screenshotbuddy.io/v1/snap" \
  -H "Authorization: Bearer <your API key>" \
  --data-urlencode "url=https://example.com/reports/q3" \
  -d "pdf=1" \
  -d "paperFormat=letter" \
  -d "landscape=1" \
  -d "marginTop=20" -d "marginRight=15" -d "marginBottom=20" -d "marginLeft=15" \
  -d "marginUnit=mm" \
  --output report.pdf
Margins run from 0 to 1000 in whichever unit you chose. Set them all four or none.

For a size no paper format covers, width and height override paperFormat and are read in the same marginUnit. They go together too. scale shrinks or enlarges what is printed, between 0.1 and 2 for a PDF, which is the way to fit a wide table onto a narrower page without touching your own stylesheet.

Backgrounds come up next, and the answer is that the renderer prints the page as a browser would, colours and images included. For a lighter document the lever is your own print stylesheet, which the render respects. What a PDF cannot take is a delay: the printer has no way to wait a fixed moment before it prints, so a request combining the two is rejected rather than rendered without the wait you asked for.

Invoices, reports and receipts

Most PDF work is one of these three, and all three start the same way: you already have the document as HTML, because you already show it in the browser. The render takes a URL rather than a body of markup, so what you point it at is a page of your own, styled the way you want it printed.

That means the page has to be reachable from the outside. Private, loopback and internal addresses are refused, so a localhost print view or anything behind your VPN will not render. The pattern that works is a print route on your public application at a path that is hard to guess, a random or signed token rather than a sequential id, rendering the template your customer already sees.

A PDF is a rendering of whatever the server actually served. A sign-in wall or a 404 is printed just as faithfully as the invoice you meant, and it costs the same credit, so it is worth opening the URL yourself once, logged out, before you wire it up.

A PDF link that carries its own authorization

Sometimes what you want is not the file but a link to it: a download button, a line in an email, a href in a customer portal. Handing that to a browser normally means proxying the render through your own backend, because a browser cannot send an Authorization header and a token in a URL is a published token.

A signed URL removes that proxy. You sign the query on your server with the signing secret of a token, and the resulting URL proves on its own that it was built by you. It fetches exactly the document it describes and nothing else: nobody holding it can change the page, widen the paper or make any other request against your account.

sign.php
<?php

// signedSnapUrl() is the reference implementation from the signed URLs page.
$url = signedSnapUrl(
    [
        'url' => 'https://example.com/invoices/1024',
        'pdf' => '1',
        'paperFormat' => 'a4',
    ],
    42,
    getenv('SCREENSHOTBUDDY_SIGNING_SECRET'),
    time() + 86400
);

// $url is now safe to put in the email. Nothing of yours is exposed by it.
expires is optional, and it is worth choosing from how long the page carrying the link is expected to live. An email people open days later wants a generous one.

The token's permissions still apply, which is the useful part. A token can be narrowed to PDFs only, and one narrowed to screenshots that asks for pdf=1 is refused rather than billed.

Generating many PDFs at once

Month-end statements, a year of receipts, an archive of every report that changed overnight: these are jobs where holding a connection open per document is the wrong shape. Post the set to /renders instead, up to 20 captures in one body. Nothing renders on that request, so it cannot time out on a slow page, and the answer is the whole batch with every item queued.

Terminal
curl "https://api.screenshotbuddy.io/v1/renders" \
  -H "Authorization: Bearer <your API key>" \
  -H "Content-Type: application/json" \
  -d '{"items":[{"url":"https://example.com/invoices/1024","pdf":true},{"url":"https://example.com/invoices/1025","pdf":true,"paperFormat":"letter"}],"webhookUrl":"https://example.com/hooks/renders"}'
Each item takes the same parameters as the GET request it will be made as, and is validated by the same rules. webhookUrl is optional; polling works either way.

Collect the results by polling /renders/{id}, which renders nothing and carries no throttle of its own, or from one signed webhook delivery when every item is terminal. Each finished item hands back a signed URL that fetches its PDF, so there is no second call to make for the file itself.

A batch buys no extra capacity, and it does not pretend to. Every item is counted against the same renders per minute your plan allows. What changes is who waits: an item that meets the limit goes back to the queue and asks again rather than failing, for up to a quarter of an hour. Two items describing the same capture are refused, because that is one document rendered twice and one credit spent twice.

Repeat renders come out of the cache

Ask for the same document twice and the second answer is the rendering already made for you. It arrives faster, costs no credit, and says X-Cache: HIT so you can tell. An entry is keyed on the exact request, the URL and every option sent with it, and it belongs to your account alone.

That matters more for PDFs than it looks. An invoice does not change after it is issued, so the second download of it, and the two hundredth, are free. A rendering is kept for 86400 seconds unless you say otherwise; send cacheTtl to choose your own, up to 2592000 seconds, which is thirty days. Hits come out of a separate budget of 300 a minute rather than out of your renders, so a burst of downloads cannot eat the allowance you were saving for documents still to be made. When you need the page as it is right now, cache=0 renders it again and costs a credit like any other render.

Where to go next

Every PDF parameter, with its range and its default, is on the creating PDFs page. If you would rather see it than read it, the playground renders a live document in the browser: pick a paper format, flip to landscape, and copy the request once it looks right.

A screenshot and a PDF cost one credit each, out of the same monthly allowance, on every plan.