webcapture Get a free key

Poll an async PDF render in Node with fetch

For serverless functions and request handlers that cannot hold a connection, ?wait=false returns immediately with a ticket, and the poll url carries the finished document.

The ticket answers 202 with the id, the status queued, and two urls: poll, a JSON status endpoint, and download, the artifact. Poll about once a second; the status walks queued, rendering, ready, and on a failed render the poll body carries the error. Both the poll and the download carry your key.

The recipe

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

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

const ticket = await fetch(`${ORIGIN}/v1/convert?wait=false`, {
  method: "POST",
  headers: {
    "Authorization": "Bearer wc_your_key",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({"url": "https://example.com/"}),
});
if (!ticket.ok) throw new Error(`convert failed: ${ticket.status}`);
// 202 {"id": "cnv_02b8c9d9", "status": "queued",
//      "poll": ".../v1/conversions/cnv_02b8c9d9",
//      "download": ".../v1/conversions/cnv_02b8c9d9.pdf"}
const job = await ticket.json();

let row = job;
while (row.status === "queued" || row.status === "rendering") {
  await new Promise((resolve) => setTimeout(resolve, 1000));
  const poll = await fetch(job.poll, {
    headers: { "Authorization": "Bearer wc_your_key" },
  });
  row = await poll.json();
}
if (row.status !== "ready") throw new Error(row.error ?? `render ${row.status}`);

const file = await fetch(job.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()));

The loop stops on ready and treats anything else as a failure with the endpoint's own error message. A ready poll row adds attempts and bytes to the status, so the caller knows the size before it downloads. Renders usually finish in seconds; the one-second pace is the endpoint's own guidance, and the ticket's download url stays valid for at least seven days after the render.

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.