Why Ruby Developers Use a Screenshot API
Ruby developers building web apps, scraping pipelines, and automation scripts regularly need screenshots and PDFs. The traditional approach is to install Puppeteer via Node.js alongside a Ruby app, then shell out to it — adding a Node.js runtime, npm dependencies, and a headless Chrome binary to a project that otherwise has none of those requirements. Running headless Chrome in a Ruby on Rails app on Heroku or Render requires custom buildpacks, bumps the slug size past the 500 MB limit, and burns dyno memory. SnapAPI replaces that entire dependency chain with a single HTTP call from any Ruby HTTP library you already use.
Net::HTTP Screenshot Example
The Ruby standard library Net::HTTP requires no gem dependencies and works in any Ruby environment. Here is a complete screenshot helper using Net::HTTP:
require 'net/http'
require 'uri'
require 'json'
def screenshot(url, full_page: true, format: 'png')
base = URI('https://snapapi.pics/screenshot')
base.query = URI.encode_www_form(
access_key: ENV['SNAPAPI_KEY'],
url: url,
full_page: full_page ? '1' : '0',
format: format
)
response = Net::HTTP.get_response(base)
raise "SnapAPI error #{response.code}" unless response.code == '200'
response.body
end
# Save a PNG screenshot
image_bytes = screenshot('https://example.com')
File.binwrite('screenshot.png', image_bytes)
puts "Saved #{image_bytes.bytesize} bytes"
Faraday Screenshot Example
Faraday is the most widely used HTTP client gem in Ruby and Rails ecosystems. It provides middleware-based request/response processing and integrates cleanly with Rails apps. Here is the equivalent screenshot function with Faraday:
require 'faraday'
def screenshot_faraday(url, full_page: true)
conn = Faraday.new(url: 'https://snapapi.pics') do |f|
f.response :raise_error
f.adapter Faraday.default_adapter
end
response = conn.get('/screenshot') do |req|
req.params['access_key'] = ENV['SNAPAPI_KEY']
req.params['url'] = url
req.params['full_page'] = full_page ? '1' : '0'
req.params['format'] = 'png'
end
response.body
end
bytes = screenshot_faraday('https://example.com')
File.binwrite('output.png', bytes)
# PDF generation
def pdf_from_url(url)
conn = Faraday.new(url: 'https://snapapi.pics')
response = conn.get('/screenshot') do |req|
req.params['access_key'] = ENV['SNAPAPI_KEY']
req.params['url'] = url
req.params['format'] = 'pdf'
req.params['full_page'] = '1'
req.params['paper_format'] = 'A4'
end
response.body
end
Rails ActiveJob Background Screenshot
In a Rails app, screenshots should be generated in a background job rather than inline in a controller action. Here is a complete ActiveJob implementation that captures a screenshot and attaches it to an ActiveStorage record:
class ScreenshotJob < ApplicationJob
queue_as :default
retry_on StandardError, wait: :exponentially_longer, attempts: 3
def perform(page_id, url)
page = Page.find(page_id)
image_bytes = capture_screenshot(url)
page.screenshot.attach(
io: StringIO.new(image_bytes),
filename: "screenshot-#{page_id}.png",
content_type: 'image/png'
)
page.update!(screenshot_captured_at: Time.current)
end
private
def capture_screenshot(url)
uri = URI('https://snapapi.pics/screenshot')
uri.query = URI.encode_www_form(
access_key: ENV['SNAPAPI_KEY'],
url: url,
full_page: '1',
format: 'png',
viewport_width: '1280',
viewport_height: '800'
)
response = Net::HTTP.get_response(uri)
raise "Screenshot failed: #{response.code}" unless response.code == '200'
response.body
end
end
# Enqueue from controller
ScreenshotJob.perform_later(page.id, page.url)
Concurrent Batch Screenshots with Ruby Threads
Ruby's Thread class allows concurrent HTTP requests for batch screenshot jobs. The GIL (Global Interpreter Lock) in MRI Ruby still allows I/O-bound operations like HTTP requests to run concurrently, making threading effective for screenshot batch processing:
require 'net/http'
require 'uri'
def batch_screenshots(urls, concurrency: 5)
results = {}
mutex = Mutex.new
semaphore = Mutex.new
active = 0
threads = urls.map do |url|
Thread.new do
loop do
semaphore.synchronize { break if active < concurrency; sleep 0.05 }
semaphore.synchronize { active += 1 }
begin
bytes = screenshot(url)
mutex.synchronize { results[url] = { status: :ok, bytes: bytes.bytesize } }
rescue => e
mutex.synchronize { results[url] = { status: :error, message: e.message } }
ensure
semaphore.synchronize { active -= 1 }
end
break
end
end
end
threads.each(&:join)
results
end
urls = [
'https://example.com',
'https://ruby-lang.org',
'https://rubyonrails.org'
]
results = batch_screenshots(urls, concurrency: 3)
results.each { |url, r| puts "#{url}: #{r[:status]} (#{r[:bytes]} bytes)" }
Scraping and Data Extraction from Ruby
SnapAPI exposes a scrape endpoint that returns cleaned HTML or markdown text and an extract endpoint that returns structured JSON from a web page. Both endpoints handle JavaScript rendering, bot detection bypass, and proxy rotation automatically. From Ruby, call the scrape endpoint to get page content without managing Nokogiri + HTTP client stacks for JavaScript-rendered sites:
def scrape_page(url)
uri = URI('https://snapapi.pics/scrape')
uri.query = URI.encode_www_form(
access_key: ENV['SNAPAPI_KEY'],
url: url,
output: 'markdown'
)
response = Net::HTTP.get_response(uri)
return nil unless response.code == '200'
JSON.parse(response.body)
end
result = scrape_page('https://example.com/pricing')
puts result['content'] # clean markdown text of the page
Sinatra Integration
For lightweight Sinatra applications, adding a screenshot endpoint takes fewer than twenty lines. Here is a Sinatra route that accepts a URL parameter and returns a screenshot PNG inline:
require 'sinatra'
require 'net/http'
require 'uri'
get '/screenshot' do
target_url = params[:url]
halt 400, 'url required' if target_url.nil? || target_url.empty?
uri = URI('https://snapapi.pics/screenshot')
uri.query = URI.encode_www_form(
access_key: ENV['SNAPAPI_KEY'],
url: target_url,
format: 'png',
viewport_width: '1280',
viewport_height: '800'
)
response = Net::HTTP.get_response(uri)
halt 502, 'Screenshot failed' unless response.code == '200'
content_type 'image/png'
response.body
end
Environment Configuration and Key Storage
Store your SnapAPI key in an environment variable and never commit it to source control. In Rails, use the encrypted credentials system: run rails credentials:edit and add snapapi_key: your_key_here, then reference it as Rails.application.credentials.snapapi_key. For non-Rails Ruby apps, use the dotenv gem — add gem 'dotenv' to your Gemfile, create a .env file with SNAPAPI_KEY=your_key_here, add .env to .gitignore, and call Dotenv.load at app startup. For production deployments on Heroku, Render, or Fly.io, set the environment variable through the platform's secrets management interface rather than a dotenv file. Rotate the key via the SnapAPI dashboard if it is ever accidentally exposed.