HTML to PDF in Ruby

The classic Rails answer — wicked_pdf — depends on wkhtmltopdf, which was archived in 2023 and renders CSS like it's 2012. Prawn means hand-positioning every element. Or: one Net::HTTP call, zero gems, real Chromium rendering.

1. Convert HTML to PDF

require 'net/http'
require 'json'

uri = URI('https://inkpdf.dev/v1/pdf')
req = Net::HTTP::Post.new(uri)
req['Authorization'] = "Bearer #{ENV.fetch('INKPDF_KEY')}"
req['Content-Type']  = 'application/json'
req.body = {
  html: '<h1>Hello from Ruby</h1><p>Modern CSS included.</p>',
  options: { format: 'A4' }
}.to_json

res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true, read_timeout: 60) do |http|
  http.request(req)
end

raise JSON.parse(res.body)['message'] unless res.is_a?(Net::HTTPSuccess)
File.binwrite('hello.pdf', res.body)

2. Invoice from a hash (no HTML)

req.body = {
  template: 'invoice',
  data: {
    brand: { name: 'Acme Studio', color: '#0f766e' },
    invoiceNumber: 'INV-2043',
    issueDate: '2026-07-01',
    dueDate: '2026-07-31',
    currency: 'USD',
    to: { name: 'Northwind Traders', address: '1 Market St, SF' },
    items: [
      { description: 'Design work', quantity: 10, unitPrice: 95 }
    ],
    taxRate: 8.5
  }
}.to_json

Totals and tax are computed server-side — see the template gallery for receipt, quote, report, and certificate layouts too.

3. Rails controller: send the PDF

class InvoicesController < ApplicationController
  def show
    invoice = Invoice.find(params[:id])
    pdf = InkpdfClient.render(template: 'invoice', data: invoice.to_template_data)
    send_data pdf,
              filename: "#{invoice.number}.pdf",
              type: 'application/pdf',
              disposition: 'attachment'
  end
end

Wrap the Net::HTTP call from step 1 in a small InkpdfClient class (or use Faraday/HTTParty if they're already in your Gemfile — it's one POST either way).

Markdown mode for reports

req.body = { markdown: report_markdown, theme: 'serif' }.to_json

Errors and quotas

Non-200 responses are JSON: {"error": "quota_exceeded", "message": "…"}. Only successful renders count against your monthly quota, and every PDF response carries x-inkpdf-used / x-inkpdf-quota headers so you always know where you stand.

Try it without writing code

The playground renders live against the real pipeline — 20 free renders a day, no signup.