<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Numerix Labs</title>
    <description>The latest articles on DEV Community by Numerix Labs (@numerixlabs).</description>
    <link>https://dev.to/numerixlabs</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3924010%2Fdb9d96da-17b9-4cfc-9831-b7c9f1bf8e07.png</url>
      <title>DEV Community: Numerix Labs</title>
      <link>https://dev.to/numerixlabs</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/numerixlabs"/>
    <language>en</language>
    <item>
      <title>URL publication-quality PDF in one POST request</title>
      <dc:creator>Numerix Labs</dc:creator>
      <pubDate>Tue, 12 May 2026 06:30:09 +0000</pubDate>
      <link>https://dev.to/numerixlabs/url-publication-quality-pdf-in-one-post-request-3n5j</link>
      <guid>https://dev.to/numerixlabs/url-publication-quality-pdf-in-one-post-request-3n5j</guid>
      <description>&lt;p&gt;If you've ever tried to "just turn this page into a PDF on the server," you probably spent a Saturday on it.&lt;/p&gt;

&lt;p&gt;Headless Chromium works — until it doesn't. CSS backgrounds disappear. Custom fonts come out as squares. The Lambda cold-starts a fresh Chromium each invocation and the first three PDFs of the morning take eleven seconds. By the time the PDF looks right and renders fast, you've shipped half a stack to keep one Chromium process happy.&lt;/p&gt;

&lt;p&gt;So we made a smaller version of that problem: &lt;strong&gt;POST a URL, get a PDF back.&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST https://api.numerixlabs.com/screenshot &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer &lt;/span&gt;&lt;span class="nv"&gt;$SNAPAPI_KEY&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"url":"https://example.com/invoice/42","format":"pdf"}'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-o&lt;/span&gt; invoice-42.pdf
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's the whole API. No render queue, no font upload pipeline, no Chromium process to babysit.&lt;/p&gt;

&lt;h2&gt;
  
  
  What "publication-quality" means here
&lt;/h2&gt;

&lt;p&gt;It's easy to render a PDF that &lt;em&gt;looks&lt;/em&gt; right in a viewer and falls apart when you print it. Three things usually fail:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;CSS background images and colours drop.&lt;/strong&gt; Chromium's default &lt;code&gt;printBackground&lt;/code&gt; is &lt;code&gt;false&lt;/code&gt; — so the PDF you generate at midnight will not have your brand colour fills, even though the screenshot endpoint shows them. We turn &lt;code&gt;printBackground&lt;/code&gt; on by default, so a &lt;code&gt;format=pdf&lt;/code&gt; response now matches the on-screen render byte-for-byte across backgrounds, gradient fills, and tinted table rows. (This was the last regression we shipped a fix for; we hit it ourselves on the invoice path.)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Web fonts don't load before the page is captured.&lt;/strong&gt; We wait for &lt;code&gt;networkidle2&lt;/code&gt; (≤2 long-lived connections — the pragmatic stopping point for analytics-heavy pages that never go fully idle) before snapshotting, so &lt;code&gt;@font-face&lt;/code&gt; resolution finishes before the render pass. No squares.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Default A4 page size, full background fidelity.&lt;/strong&gt; &lt;code&gt;format=pdf&lt;/code&gt; renders at A4 with &lt;code&gt;printBackground: true&lt;/code&gt;, so brand colour fills, gradient backgrounds, and tinted table rows survive into the file. Chromium emulates &lt;code&gt;@media print&lt;/code&gt; by default during PDF generation — so if your page has a print stylesheet, you'll get the print layout in the PDF. If you've designed the page screen-only, the on-screen layout is what you'll get.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The combination is what we mean by "publication-quality." Not "renders without errors" — &lt;em&gt;looks the same as the browser, every time, including the bits that print media queries traditionally murder&lt;/em&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common use cases we've seen
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Invoices and receipts.&lt;/strong&gt; Render your existing HTML invoice page; ship the PDF as the email attachment. Removes the entire "build a separate PDF templating layer" project.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reports.&lt;/strong&gt; Dashboards or analytics views that exist as live HTML — once a month, snapshot them.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Compliance / audit captures.&lt;/strong&gt; Snapshot a page state at a specific moment with a stable hash you can refer to later.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Newsletter archives.&lt;/strong&gt; Render the issue at send-time and store the PDF.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What the request looks like
&lt;/h2&gt;

&lt;p&gt;The minimum request is a URL and a format. Everything else has sensible defaults.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;POST&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;https://api.numerixlabs.com/screenshot&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://example.com/invoice/42"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"format"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"pdf"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can override the viewport width, add a post-load delay, clip to a CSS selector, capture full-page or above-fold, and pass custom headers and cookies — the docs cover the full surface. The default behaviour is "do the polite thing": full-page capture at 1280px, wait for &lt;code&gt;networkidle2&lt;/code&gt;, no delay.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why we built it like this
&lt;/h2&gt;

&lt;p&gt;A POST-with-a-URL is the smallest possible API that does the job. We chose it over a "PDF SDK with options for every PDF rendering library" because the same shape solves a lot of nearby problems (PNG, JPEG, webhooks for delivery later) without adding more verbs.&lt;/p&gt;

&lt;p&gt;Same shape, same retry policy, same auth — different &lt;code&gt;format&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;p&gt;Free trial is one POST away — get a key at &lt;a href="https://numerixlabs.com" rel="noopener noreferrer"&gt;numerixlabs.com&lt;/a&gt;. If you're already on Puppeteer-in-a-Lambda and the cold-start latency is hurting you, that's the migration we'd love to talk about.&lt;/p&gt;

&lt;p&gt;If &lt;code&gt;format=pdf&lt;/code&gt; doesn't match what you see in your browser, mail us — that's a bug, not a "feature request," and we want to know.&lt;/p&gt;

&lt;p&gt;— Built by &lt;a href="https://numerixlabs.com" rel="noopener noreferrer"&gt;NumerixLabs&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>api</category>
      <category>showdev</category>
      <category>tooling</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
