Generating PDFs in Ruby or Go traditionally means installing wkhtmltopdf or running a headless Chromium instance on your server. That comes with dependency management headaches and high memory usage. With FUNBREW PDF API, you just send an HTTP request and get a PDF back.
This guide covers multiple HTTP clients in both Ruby and Go, with practical examples for HTML rendering, templates, and URL conversion. For Node.js, Python, and PHP examples, see the language quickstart guide.
Setup
1. Create an Account
Sign up for free. You get 30 PDF generations per month at no cost.
2. Get Your API Key
Navigate to the "API Keys" section in your dashboard and generate a key. It starts with sk-.
# Set as an environment variable (recommended)
export FUNBREW_PDF_API_KEY="sk-your-api-key"
For secure key management practices, see the security guide.
3. API Endpoint
POST https://api.pdf.funbrew.cloud/v1/pdf/from-html
Send HTML, get a PDF binary back. Simple request/response model. See the full API reference in the documentation.
Ruby
net/http (Standard Library)
No extra gems needed. Here's a minimal example to generate a PDF.
require 'net/http'
require 'json'
require 'uri'
uri = URI('https://api.pdf.funbrew.cloud/v1/pdf/from-html')
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
http.open_timeout = 10
http.read_timeout = 30
request = Net::HTTP::Post.new(uri)
request['Authorization'] = "Bearer #{ENV['FUNBREW_PDF_API_KEY']}"
request['Content-Type'] = 'application/json'
request.body = {
html: <<~HTML,
<html>
<head>
<style>
body { font-family: Arial, sans-serif; padding: 40px; }
h1 { color: #1a1a1a; border-bottom: 2px solid #3b82f6; padding-bottom: 8px; }
.date { color: #6b7280; margin-top: 24px; }
</style>
</head>
<body>
<h1>Report</h1>
<p>This PDF was generated via the API.</p>
<p class="date">Generated: #{Time.now.iso8601}</p>
</body>
</html>
HTML
engine: 'quality',
format: 'A4'
}.to_json
response = http.request(request)
if response.code == '200'
File.binwrite('output.pdf', response.body)
puts "PDF generated: output.pdf (#{response.body.bytesize} bytes)"
else
puts "Error: #{response.code} - #{response.body}"
end
Faraday
Faraday makes it easy to add retries and middleware.
require 'faraday'
require 'json'
conn = Faraday.new(url: 'https://api.pdf.funbrew.cloud') do |f|
f.request :retry, max: 3, interval: 1, backoff_factor: 2
f.options.timeout = 30
f.options.open_timeout = 10
end
response = conn.post('/v1/pdf/from-html') do |req|
req.headers['Authorization'] = "Bearer #{ENV['FUNBREW_PDF_API_KEY']}"
req.headers['Content-Type'] = 'application/json'
req.body = {
html: '<h1>Hello PDF</h1><p>Generated with Faraday</p>',
engine: 'fast',
format: 'A4'
}.to_json
end
if response.success?
File.binwrite('output.pdf', response.body)
puts "PDF generated: output.pdf"
else
puts "Error: #{response.status} - #{response.body}"
end
Template-Based PDF Generation
Use registered templates to generate PDFs by passing data. See the template engine guide for how to create templates.
require 'faraday'
require 'json'
conn = Faraday.new(url: 'https://api.pdf.funbrew.cloud') do |f|
f.options.timeout = 30
end
response = conn.post('/v1/pdf/from-template') do |req|
req.headers['Authorization'] = "Bearer #{ENV['FUNBREW_PDF_API_KEY']}"
req.headers['Content-Type'] = 'application/json'
req.body = {
template_id: 'invoice-v1',
data: {
company_name: 'Acme Corp',
invoice_number: 'INV-2026-0042',
items: [
{ name: 'Web Consulting', quantity: 1, price: 1500 },
{ name: 'Design Work', quantity: 3, price: 500 }
],
total: 3000
},
format: 'A4'
}.to_json
end
File.binwrite('invoice.pdf', response.body) if response.success?
For more on invoice automation, see the invoice PDF automation guide.
URL to PDF Conversion
Convert existing web pages directly to PDF.
require 'faraday'
require 'json'
conn = Faraday.new(url: 'https://api.pdf.funbrew.cloud') do |f|
f.options.timeout = 60 # Longer timeout for URL conversion (page loading)
end
response = conn.post('/v1/pdf/from-url') do |req|
req.headers['Authorization'] = "Bearer #{ENV['FUNBREW_PDF_API_KEY']}"
req.headers['Content-Type'] = 'application/json'
req.body = {
url: 'https://example.com/report',
format: 'A4',
wait_for: 'networkidle' # Wait for JS rendering to complete
}.to_json
end
File.binwrite('page.pdf', response.body) if response.success?
Go
net/http (Standard Library)
Generate PDFs with Go's standard library. Zero external dependencies.
package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"os"
"time"
)
type PDFRequest struct {
HTML string `json:"html"`
Engine string `json:"engine"`
Format string `json:"format"`
}
func generatePDF() error {
payload := PDFRequest{
HTML: `<html>
<head>
<style>
body { font-family: Arial, sans-serif; padding: 40px; }
h1 { color: #1a1a1a; border-bottom: 2px solid #3b82f6; padding-bottom: 8px; }
</style>
</head>
<body>
<h1>Report</h1>
<p>This PDF was generated from Go via the API.</p>
</body>
</html>`,
Engine: "quality",
Format: "A4",
}
body, err := json.Marshal(payload)
if err != nil {
return fmt.Errorf("marshal error: %w", err)
}
req, err := http.NewRequest("POST", "https://api.pdf.funbrew.cloud/v1/pdf/from-html", bytes.NewReader(body))
if err != nil {
return fmt.Errorf("request error: %w", err)
}
req.Header.Set("Authorization", "Bearer "+os.Getenv("FUNBREW_PDF_API_KEY"))
req.Header.Set("Content-Type", "application/json")
client := &http.Client{Timeout: 30 * time.Second}
resp, err := client.Do(req)
if err != nil {
return fmt.Errorf("request failed: %w", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
errBody, _ := io.ReadAll(resp.Body)
return fmt.Errorf("API error %d: %s", resp.StatusCode, string(errBody))
}
pdf, err := io.ReadAll(resp.Body)
if err != nil {
return fmt.Errorf("read error: %w", err)
}
if err := os.WriteFile("output.pdf", pdf, 0644); err != nil {
return fmt.Errorf("write error: %w", err)
}
fmt.Printf("PDF generated: output.pdf (%d bytes)\n", len(pdf))
return nil
}
func main() {
if err := generatePDF(); err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
os.Exit(1)
}
}
Resty
Resty simplifies retries and error handling.
package main
import (
"fmt"
"os"
"time"
"github.com/go-resty/resty/v2"
)
type PDFRequest struct {
HTML string `json:"html"`
Engine string `json:"engine"`
Format string `json:"format"`
}
func main() {
client := resty.New().
SetTimeout(30 * time.Second).
SetRetryCount(3).
SetRetryWaitTime(1 * time.Second).
SetRetryMaxWaitTime(10 * time.Second)
resp, err := client.R().
SetHeader("Authorization", "Bearer "+os.Getenv("FUNBREW_PDF_API_KEY")).
SetHeader("Content-Type", "application/json").
SetBody(PDFRequest{
HTML: "<h1>Hello PDF</h1><p>Generated with Resty</p>",
Engine: "fast",
Format: "A4",
}).
Post("https://api.pdf.funbrew.cloud/v1/pdf/from-html")
if err != nil {
fmt.Fprintf(os.Stderr, "Request failed: %v\n", err)
os.Exit(1)
}
if resp.StatusCode() != 200 {
fmt.Fprintf(os.Stderr, "API error %d: %s\n", resp.StatusCode(), resp.String())
os.Exit(1)
}
os.WriteFile("output.pdf", resp.Body(), 0644)
fmt.Printf("PDF generated: output.pdf (%d bytes)\n", len(resp.Body()))
}
Template-Based PDF Generation
package main
import (
"fmt"
"os"
"time"
"github.com/go-resty/resty/v2"
)
type TemplateRequest struct {
TemplateID string `json:"template_id"`
Data interface{} `json:"data"`
Format string `json:"format"`
}
type InvoiceData struct {
CompanyName string `json:"company_name"`
InvoiceNumber string `json:"invoice_number"`
Items []InvoiceItem `json:"items"`
Total int `json:"total"`
}
type InvoiceItem struct {
Name string `json:"name"`
Quantity int `json:"quantity"`
Price int `json:"price"`
}
func main() {
client := resty.New().SetTimeout(30 * time.Second)
resp, err := client.R().
SetHeader("Authorization", "Bearer "+os.Getenv("FUNBREW_PDF_API_KEY")).
SetHeader("Content-Type", "application/json").
SetBody(TemplateRequest{
TemplateID: "invoice-v1",
Data: InvoiceData{
CompanyName: "Acme Corp",
InvoiceNumber: "INV-2026-0042",
Items: []InvoiceItem{
{Name: "Web Consulting", Quantity: 1, Price: 1500},
{Name: "Design Work", Quantity: 3, Price: 500},
},
Total: 3000,
},
Format: "A4",
}).
Post("https://api.pdf.funbrew.cloud/v1/pdf/from-template")
if err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
os.Exit(1)
}
if resp.StatusCode() == 200 {
os.WriteFile("invoice.pdf", resp.Body(), 0644)
fmt.Println("Invoice PDF generated")
}
}
Error Handling
The API returns errors in JSON format. Proper error handling helps you quickly identify and resolve issues. For a comprehensive guide, see the error handling guide.
Ruby
require 'faraday'
require 'json'
class PdfApiError < StandardError
attr_reader :status, :code, :detail
def initialize(status, body)
parsed = JSON.parse(body) rescue {}
@status = status
@code = parsed['error'] || 'unknown'
@detail = parsed['message'] || body
super("PDF API Error [#{status}] #{@code}: #{@detail}")
end
end
def generate_pdf_safe(html, options = {})
conn = Faraday.new(url: 'https://api.pdf.funbrew.cloud') do |f|
f.request :retry, max: 3,
retry_statuses: [429, 500, 502, 503],
interval: 1,
backoff_factor: 2
f.options.timeout = options.fetch(:timeout, 30)
end
response = conn.post('/v1/pdf/from-html') do |req|
req.headers['Authorization'] = "Bearer #{ENV['FUNBREW_PDF_API_KEY']}"
req.headers['Content-Type'] = 'application/json'
req.body = { html: html, engine: 'quality', format: 'A4' }.to_json
end
raise PdfApiError.new(response.status, response.body) unless response.success?
response.body
rescue Faraday::TimeoutError
raise PdfApiError.new(408, '{"error":"timeout","message":"Request timed out"}')
rescue Faraday::ConnectionFailed
raise PdfApiError.new(0, '{"error":"connection","message":"Could not connect to API"}')
end
Go
package pdfapi
import (
"encoding/json"
"fmt"
"io"
"net/http"
)
type APIError struct {
StatusCode int
ErrorCode string `json:"error"`
Message string `json:"message"`
}
func (e *APIError) Error() string {
return fmt.Sprintf("PDF API Error [%d] %s: %s", e.StatusCode, e.ErrorCode, e.Message)
}
func (e *APIError) IsRetryable() bool {
switch e.StatusCode {
case 429, 500, 502, 503:
return true
default:
return false
}
}
func parseErrorResponse(resp *http.Response) error {
body, _ := io.ReadAll(resp.Body)
apiErr := &APIError{StatusCode: resp.StatusCode}
if err := json.Unmarshal(body, apiErr); err != nil {
apiErr.ErrorCode = "unknown"
apiErr.Message = string(body)
}
return apiErr
}
Framework Integration
Rails (Ruby on Rails)
Serve PDFs as download responses directly from a Rails controller.
# app/services/pdf_generator.rb
class PdfGenerator
def initialize
@conn = Faraday.new(url: 'https://api.pdf.funbrew.cloud') do |f|
f.request :retry, max: 3, retry_statuses: [429, 500, 502, 503]
f.options.timeout = 30
end
end
def from_html(html, options = {})
response = @conn.post('/v1/pdf/from-html') do |req|
req.headers['Authorization'] = "Bearer #{ENV['FUNBREW_PDF_API_KEY']}"
req.headers['Content-Type'] = 'application/json'
req.body = {
html: html,
engine: options.fetch(:engine, 'quality'),
format: options.fetch(:format, 'A4')
}.to_json
end
raise "PDF generation failed: #{response.status}" unless response.success?
response.body
end
def from_template(template_id, data, options = {})
response = @conn.post('/v1/pdf/from-template') do |req|
req.headers['Authorization'] = "Bearer #{ENV['FUNBREW_PDF_API_KEY']}"
req.headers['Content-Type'] = 'application/json'
req.body = {
template_id: template_id,
data: data,
format: options.fetch(:format, 'A4')
}.to_json
end
raise "PDF generation failed: #{response.status}" unless response.success?
response.body
end
end
# app/controllers/invoices_controller.rb
class InvoicesController < ApplicationController
def download
invoice = Invoice.find(params[:id])
generator = PdfGenerator.new
pdf = generator.from_template('invoice-v1', {
company_name: invoice.company_name,
invoice_number: invoice.number,
items: invoice.items.map { |i| { name: i.name, quantity: i.quantity, price: i.price } },
total: invoice.total
})
send_data pdf,
filename: "invoice-#{invoice.number}.pdf",
type: 'application/pdf',
disposition: 'attachment'
end
end
Gin (Go)
package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"os"
"time"
"github.com/gin-gonic/gin"
)
var httpClient = &http.Client{Timeout: 30 * time.Second}
func generatePDFFromHTML(html string) ([]byte, error) {
body, _ := json.Marshal(map[string]string{
"html": html,
"engine": "quality",
"format": "A4",
})
req, _ := http.NewRequest("POST", "https://api.pdf.funbrew.cloud/v1/pdf/from-html", bytes.NewReader(body))
req.Header.Set("Authorization", "Bearer "+os.Getenv("FUNBREW_PDF_API_KEY"))
req.Header.Set("Content-Type", "application/json")
resp, err := httpClient.Do(req)
if err != nil {
return nil, fmt.Errorf("API request failed: %w", err)
}
defer resp.Body.Close()
if resp.StatusCode != 200 {
errBody, _ := io.ReadAll(resp.Body)
return nil, fmt.Errorf("API error %d: %s", resp.StatusCode, string(errBody))
}
return io.ReadAll(resp.Body)
}
func main() {
r := gin.Default()
r.GET("/invoices/:id/pdf", func(c *gin.Context) {
invoiceID := c.Param("id")
html := fmt.Sprintf(`<html>
<body>
<h1>Invoice %s</h1>
<p>Generated via FUNBREW PDF API</p>
</body>
</html>`, invoiceID)
pdf, err := generatePDFFromHTML(html)
if err != nil {
c.JSON(500, gin.H{"error": err.Error()})
return
}
c.Header("Content-Disposition", fmt.Sprintf(`attachment; filename="invoice-%s.pdf"`, invoiceID))
c.Data(200, "application/pdf", pdf)
})
r.Run(":8080")
}
Echo (Go)
package main
import (
"fmt"
"net/http"
"os"
"time"
"github.com/go-resty/resty/v2"
"github.com/labstack/echo/v4"
)
var restyClient = resty.New().SetTimeout(30 * time.Second)
func generatePDF(html string) ([]byte, error) {
resp, err := restyClient.R().
SetHeader("Authorization", "Bearer "+os.Getenv("FUNBREW_PDF_API_KEY")).
SetHeader("Content-Type", "application/json").
SetBody(map[string]string{
"html": html,
"engine": "quality",
"format": "A4",
}).
Post("https://api.pdf.funbrew.cloud/v1/pdf/from-html")
if err != nil {
return nil, err
}
if resp.StatusCode() != 200 {
return nil, fmt.Errorf("API error %d: %s", resp.StatusCode(), resp.String())
}
return resp.Body(), nil
}
func main() {
e := echo.New()
e.GET("/reports/:id/pdf", func(c echo.Context) error {
reportID := c.Param("id")
html := fmt.Sprintf(`<html>
<body>
<h1>Report %s</h1>
<p>Generated via FUNBREW PDF API</p>
</body>
</html>`, reportID)
pdf, err := generatePDF(html)
if err != nil {
return c.JSON(http.StatusInternalServerError, map[string]string{"error": err.Error()})
}
c.Response().Header().Set("Content-Disposition",
fmt.Sprintf(`attachment; filename="report-%s.pdf"`, reportID))
return c.Blob(http.StatusOK, "application/pdf", pdf)
})
e.Logger.Fatal(e.Start(":8080"))
}
Batch Processing
When generating many PDFs, use concurrency to speed things up. For detailed patterns, see the batch processing guide.
Ruby (Parallel gem)
require 'faraday'
require 'json'
require 'parallel'
conn = Faraday.new(url: 'https://api.pdf.funbrew.cloud') do |f|
f.request :retry, max: 3, retry_statuses: [429, 500, 502, 503]
f.options.timeout = 30
end
invoices = [
{ id: 'INV-001', company: 'Acme Corp', total: 1000 },
{ id: 'INV-002', company: 'Globex Inc', total: 2500 },
{ id: 'INV-003', company: 'Initech', total: 1800 },
]
results = Parallel.map(invoices, in_threads: 5) do |invoice|
html = "<html><body><h1>Invoice #{invoice[:id]}</h1>
<p>#{invoice[:company]}</p>
<p>Total: $#{invoice[:total]}</p>
</body></html>"
response = conn.post('/v1/pdf/from-html') do |req|
req.headers['Authorization'] = "Bearer #{ENV['FUNBREW_PDF_API_KEY']}"
req.headers['Content-Type'] = 'application/json'
req.body = { html: html, engine: 'fast', format: 'A4' }.to_json
end
if response.success?
File.binwrite("#{invoice[:id]}.pdf", response.body)
{ id: invoice[:id], status: :ok, size: response.body.bytesize }
else
{ id: invoice[:id], status: :error, code: response.status }
end
end
results.each { |r| puts "#{r[:id]}: #{r[:status]}" }
Go (goroutines)
package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"os"
"sync"
"time"
)
type Invoice struct {
ID string
Company string
Total int
}
type Result struct {
ID string
Status string
Size int
Err error
}
func main() {
invoices := []Invoice{
{ID: "INV-001", Company: "Acme Corp", Total: 1000},
{ID: "INV-002", Company: "Globex Inc", Total: 2500},
{ID: "INV-003", Company: "Initech", Total: 1800},
}
client := &http.Client{Timeout: 30 * time.Second}
results := make([]Result, len(invoices))
var wg sync.WaitGroup
sem := make(chan struct{}, 5) // Limit concurrency to 5
for i, inv := range invoices {
wg.Add(1)
go func(idx int, invoice Invoice) {
defer wg.Done()
sem <- struct{}{}
defer func() { <-sem }()
html := fmt.Sprintf(`<html><body>
<h1>Invoice %s</h1>
<p>%s</p>
<p>Total: $%d</p>
</body></html>`, invoice.ID, invoice.Company, invoice.Total)
body, _ := json.Marshal(map[string]string{
"html": html, "engine": "fast", "format": "A4",
})
req, _ := http.NewRequest("POST",
"https://api.pdf.funbrew.cloud/v1/pdf/from-html",
bytes.NewReader(body))
req.Header.Set("Authorization", "Bearer "+os.Getenv("FUNBREW_PDF_API_KEY"))
req.Header.Set("Content-Type", "application/json")
resp, err := client.Do(req)
if err != nil {
results[idx] = Result{ID: invoice.ID, Status: "error", Err: err}
return
}
defer resp.Body.Close()
pdf, _ := io.ReadAll(resp.Body)
if resp.StatusCode == 200 {
os.WriteFile(invoice.ID+".pdf", pdf, 0644)
results[idx] = Result{ID: invoice.ID, Status: "ok", Size: len(pdf)}
} else {
results[idx] = Result{ID: invoice.ID, Status: "error",
Err: fmt.Errorf("status %d", resp.StatusCode)}
}
}(i, inv)
}
wg.Wait()
for _, r := range results {
if r.Err != nil {
fmt.Printf("%s: error - %v\n", r.ID, r.Err)
} else {
fmt.Printf("%s: %s (%d bytes)\n", r.ID, r.Status, r.Size)
}
}
}
Summary
Both Ruby and Go can generate PDFs with FUNBREW PDF API using simple HTTP requests. The standard libraries work fine, but Faraday (Ruby) and Resty (Go) make retries and timeout handling easier.
Explore these resources to go further:
- Playground to try the API from your browser
- Production guide for performance optimization
- Webhook integration guide for async PDF generation
- Pricing comparison to choose the right plan