<?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>Forem: Gkm</title>
    <description>The latest articles on Forem by Gkm (@megovind).</description>
    <link>https://forem.com/megovind</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%2F3910204%2F92db0a40-adf6-48fd-b3ba-26f49d343014.png</url>
      <title>Forem: Gkm</title>
      <link>https://forem.com/megovind</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/megovind"/>
    <language>en</language>
    <item>
      <title>Shipping Python and Node SDKs for HTML2DocHub</title>
      <dc:creator>Gkm</dc:creator>
      <pubDate>Sun, 03 May 2026 11:40:21 +0000</pubDate>
      <link>https://forem.com/megovind/shipping-python-and-node-sdks-for-html2dochub-53l2</link>
      <guid>https://forem.com/megovind/shipping-python-and-node-sdks-for-html2dochub-53l2</guid>
      <description>&lt;p&gt;A few months ago I got tired of paying $29/month to a PDF SaaS for the ~3,000 invoices my side project actually rendered. The math was infuriating — I was paying about 10× the real cost per document because of how their tier pricing worked, and there was no way to scale linearly. So I built my own and it's been running in production for a few weeks. Today I shipped the official Python and Node.js clients. Here's what's under the hood.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it is
&lt;/h2&gt;

&lt;p&gt;HTML2DocHub is an HTML-to-PDF API with one rule: you pay only for pages you render. ₹0.08 per page (~$0.001), ₹0.10 minimum per completed job. No subscription. No tier to overshoot. Failed renders are free. &lt;/p&gt;

&lt;p&gt;A 2-page invoice costs ~$0.002. A 40-page report costs ~$0.04. The wallet ledger shows every charge with the job ID, so you can cross-reference with your own logs. That's the whole pitch — the &lt;a href="https://html2dochub.com/blog/cost-transparency" rel="noopener noreferrer"&gt;why we show a clear total&lt;/a&gt; post has the longer version.&lt;/p&gt;

&lt;h2&gt;
  
  
  The stack, briefly
&lt;/h2&gt;

&lt;p&gt;Boring tech, well-glued. FastAPI for the API, Playwright + Chromium for the rendering, Postgres for state, S3 for the rendered files. The interesting decisions are at the policy layer (per-page billing, idempotency, transparent fees), not the infra layer — and that's deliberate.&lt;/p&gt;

&lt;h2&gt;
  
  
  What shipped today
&lt;/h2&gt;

&lt;p&gt;Two SDKs, both MIT-licensed.&lt;/p&gt;

&lt;h3&gt;
  
  
  Python — &lt;code&gt;pip install html2dochub&lt;/code&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;  &lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;html2dochub&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Client&lt;/span&gt;                            

  &lt;span class="n"&gt;pdf&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;api_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sk_live_...&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="n"&gt;html&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;&amp;lt;h1&amp;gt;Hello&amp;lt;/h1&amp;gt;&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;format&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;A4&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;                                                                                                                                                         
  &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Both &lt;code&gt;Client&lt;/code&gt; (sync) and &lt;code&gt;AsyncClient&lt;/code&gt; (asyncio) with the same surface. Automatic retries on &lt;code&gt;429&lt;/code&gt; and &lt;code&gt;5xx&lt;/code&gt; with exponential backoff. Typed exception hierarchy (&lt;code&gt;AuthenticationError&lt;/code&gt;, &lt;code&gt;RateLimitError&lt;/code&gt; with &lt;code&gt;retry_after&lt;/code&gt;, &lt;code&gt;InsufficientFundsError&lt;/code&gt;, &lt;code&gt;ValidationError&lt;/code&gt;, &lt;code&gt;APIError&lt;/code&gt;). Idempotency keys on every render. Python 3.9+. One dependency: &lt;code&gt;httpx&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;See the &lt;a href="https://html2dochub.com/html-to-pdf-python" rel="noopener noreferrer"&gt;Python landing page&lt;/a&gt; for framework-specific examples (FastAPI, Django, Celery).                                                  &lt;/p&gt;

&lt;h3&gt;
  
  
  Node.js / TypeScript — &lt;code&gt;npm install @html2dochub/client&lt;/code&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;  &lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Client&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@html2dochub/client&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;             

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;pdf&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;apiKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;sk_live_...&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;html&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;h1&amp;gt;Hello&amp;lt;/h1&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;                                                                                                                                                             
    &lt;span class="na"&gt;options&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;format&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;A4&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;                                                                                                                                                                                   
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Zero runtime dependencies — uses native Node 18+ &lt;code&gt;fetch&lt;/code&gt;. Dual ESM + CJS build (~14.8 KB packed). Full TypeScript declarations. Works on Vercel, Lambda, Cloud Run, Cloudflare Workers — anywhere you can make an HTTPS call.&lt;/p&gt;

&lt;p&gt;See the &lt;a href="https://html2dochub.com/html-to-pdf-nodejs" rel="noopener noreferrer"&gt;Node.js landing page&lt;/a&gt; for Express, Next.js App Router, and BullMQ worker examples.                                        &lt;/p&gt;

&lt;h2&gt;
  
  
  Three things I've learned running it
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Per-page billing changes user behaviour.&lt;/strong&gt; With subscriptions, customers obsessively monitor whether they're "getting their money's worth." With pay-per-use, they just render PDFs. Two of my early users went from generating receipts only on demand to generating one for every transaction, because the cost was visible and small.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Idempotency keys are non-negotiable for B2B.&lt;/strong&gt; Every customer running renders inside a queue (BullMQ, Celery, Sidekiq) has at-least-once semantics. Without idempotency keys,retries double-bill. With them, retries are free. Both SDKs ship this from v0.1.0.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Chromium beats every other engine I tried.&lt;/strong&gt; I tested WeasyPrint, wkhtmltopdf, and PrinceXML before settling on Playwright + Chromium. Modern CSS — flexbox gaps, grid, custom
properties, web fonts, dark mode — just works. The other engines need workarounds for things that have been standard CSS for 5+ years.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Things I deliberately did not build
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;A free public playground.&lt;/strong&gt; The dashboard already has one for signed-in users. A public version with rate limits sounds nice but gets abused via IP rotation.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Analytics on the marketing site.&lt;/strong&gt; The privacy policy promises no third-party analytics or fingerprinting. I use Search Console for traffic data and PostHog only on the authenticated dashboard.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Native binary distribution.&lt;/strong&gt; Both SDKs are pure language wrappers around HTTPS. No Chromium in your &lt;code&gt;node_modules&lt;/code&gt;. No 200 MB Lambda layer. No ARM/x86 binary builds to maintain.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What's next
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Stripe + USD pricing (international launch)&lt;/li&gt;
&lt;li&gt;More SDKs (Go, Ruby, PHP) once there's measurable demand&lt;/li&gt;
&lt;li&gt;An MCP server so AI coding tools can call the API directly &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you have a backend somewhere that generates PDFs, you can probably swap your existing library for HTML2DocHub in 30 minutes. The &lt;a href="https://html2dochub.com/blog/pdfshift%20migration-guide" rel="noopener noreferrer"&gt;PDFShift migration guide&lt;/a&gt; walks through the API differences if you're coming from there specifically.&lt;/p&gt;

&lt;p&gt;New accounts get a starter credit (about 100 pages worth) — no card required. &lt;a href="https://html2dochub.com/register" rel="noopener noreferrer"&gt;Try it&lt;/a&gt;, and if you hit a rough edge, email me at &lt;a href="mailto:hello@html2dochub.com"&gt;hello@html2dochub.com&lt;/a&gt;. Solo dev, real responses.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>python</category>
      <category>node</category>
      <category>pdf</category>
    </item>
  </channel>
</rss>
