Make a PDF smaller by downsampling its images. Choose how far: screen (smallest), ebook, printer or prepress. Here is the whole call in Node.js, with the output it produced when we ran it.
Before you start
An API key. Create a free account; it comes with free tokens every month. Put the key in an environment variable so it stays out of your code: export BLOODWEB_KEY=your_key
Node.js 18 or later. Nothing to install: fetch, FormData and Blob are built in. Save the file with the .mjs ending so await works at the top level.
The input file in the same folder: scan.pdf. Use your own; that is the name the code expects.
The code
Save it as pdf-compress.mjs and run node pdf-compress.mjs.
// Node.js 18 or later: fetch, FormData and Blob are built in.
import { readFile, writeFile } from "node:fs/promises";
const form = new FormData();
form.append("file", new Blob([await readFile("scan.pdf")]), "scan.pdf");
form.append("level", "ebook");
const res = await fetch("https://bloodweb.net/api/v1/pdf-compress", {
method: "POST",
headers: { Authorization: `Bearer ${process.env.BLOODWEB_KEY}` },
body: form,
});
if (!res.ok) throw new Error(`${res.status}: ${(await res.json()).error.message}`);
await writeFile("smaller.pdf", Buffer.from(await res.arrayBuffer()));
What you get
Tested 19 October 2026: this exact code ran against our test copy of the API, which runs the same service code as bloodweb.net, and produced this.
It writes smaller.pdf: a PDF, 3 pages, 1 KB.
Page 1 of the result.
Change the call
Every option the PDF compressor takes. Add or change them the same way as the ones in the code.
level: screen: images at 72 DPI, smallest. ebook: 150 DPI, good for email. printer: 300 DPI. prepress: 300 DPI, colour kept for print shops. One of: screen, ebook, printer, prepress.
grayscale: Convert everything to shades of grey. Often halves a scanned colour document again.
If it fails
A failed call is not charged. The answer is JSON with an error object; the code above prints its message. The usual ones:
401missing_key or invalid_key: BLOODWEB_KEY is not set in the shell that runs the code, or the key was revoked.
415unsupported_type: the file is not a type this service reads. The type is read from the file itself, so renaming a file does not change it.
413file_too_large: the file is over the limit on the service page.
402insufficient_tokens: the balance does not cover the call. Top up in the dashboard.
429rate_limited: too many calls at once. Wait the seconds in the Retry-After header and send it again.