HTML to PDF in Ruby
One POST, a PDF back. No headless browser to install, no Chromium to keep patched, no fonts to install on the box — the render happens on a warm browser that is already running.
Render HTML to a PDF
require 'net/http'
require 'json'
uri = URI('https://api.pdfcraft.dev/v1/render')
request = Net::HTTP::Post.new(uri)
request['Authorization'] = "Bearer #{ENV.fetch('PDFCRAFT_API_KEY')}"
request['Content-Type'] = 'application/json'
request.body = {
html: '<h1>Invoice 1042</h1>',
options: { printBackground: true, margin: { top: '20mm' } }
}.to_json
response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true, read_timeout: 60) do |http|
http.request(request)
end
raise "render failed: #{response.code} #{response.body}" unless response.is_a?(Net::HTTPSuccess)
File.binwrite('invoice.pdf', response.body)Read a PDF back as JSON
The same key works for extraction. Send a PDF, name the fields you want by the label printed on the page, and get them back with the raw text, a coerced value, a confidence and a bounding box.
require 'base64'
request.body = {
file: Base64.strict_encode64(File.binread('statement.pdf')),
schema: { account_number: 'string', closing_balance: 'currency' },
options: { rows_as_objects: true }
}.to_json
body = JSON.parse(response.body)
puts body.dig('fields', 'closing_balance', 'value')
body.dig('tables', 0, 'rows_as_objects').each { |row| puts row }What bites in Ruby
- `File.binwrite`, not `File.write`. On any platform with newline translation, `write` corrupts the PDF, and on Linux it works — so this is a bug that only appears once someone runs it on Windows.
- `Base64.strict_encode64`, not `encode64`. The latter inserts a newline every 60 characters, which is valid base64 to some parsers and not to others; strict avoids the argument entirely.
- Set `read_timeout`. Net::HTTP defaults to 60 seconds, which is fine, but be explicit — the default has changed between Ruby versions.
Errors are one shape
Every failure is {"error":{"code","message","docs_url"}} with a stable code, so you can switch on error.code rather than parsing prose. The two worth handling explicitly are rate_limited — honour Retry-After — and render_failed, which means your HTML broke rather than ours did.
The same thing in another language
- HTML to PDF in Node.js
- HTML to PDF in Python
- HTML to PDF in Go
- HTML to PDF in PHP
- HTML to PDF in Java
- HTML to PDF in C#
- HTML to PDF in Rust
- HTML to PDF in curl
- HTML to PDF in Deno
- HTML to PDF in Bun
- HTML to PDF in TypeScript
Or try it with no code at all in the playground. A free key is 100 renders a month.