webcapture Get a free key

Convert a URL to PDF in Node with fetch

Node 18+ ships fetch, so the whole recipe is two awaited calls and a writeFile: POST the url, read the receipt, GET the download with the same header.

The POST holds until the render finishes and answers with the receipt JSON. The receipt's download url is the GET that carries the bytes, and it needs the Authorization header again, because the download endpoint answers only to the key that made the render.

The recipe

import { writeFile } from "node:fs/promises";

const ORIGIN = "https://webcapture.io";

const res = await fetch(`${ORIGIN}/v1/convert`, {
  method: "POST",
  headers: {
    "Authorization": "Bearer wc_your_key",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({"url": "https://example.com/"}),
});
if (!res.ok) throw new Error(`convert failed: ${res.status}`);
const receipt = await res.json();

// receipt.download: ${ORIGIN}/v1/conversions/cnv_9f2c01ab.pdf
// the download is key-scoped: the GET carries the same header
const file = await fetch(receipt.download, {
  headers: { "Authorization": "Bearer wc_your_key" },
});
if (!file.ok) throw new Error(`download failed: ${file.status}`);
await writeFile("page.pdf", Buffer.from(await file.arrayBuffer()));

receipt.download is read as a property of the parsed receipt, and the GET that follows repeats the Authorization header. The receipt also carries bytes (the document's size), attempts, and remaining (renders left this month), which is everything a queue worker needs for logging. Res.text() on a failed convert carries the endpoint's JSON error, so the throw names the real cause.

Same-key GET: 401 without it; 404 for another key.

The fields this recipe uses

Related

Method

Source: convert field reference, checked 2026-10-05. Corrections.