How to Take Website Screenshots with Ruby

Published April 3, 2026 · 5 min read · URLSnap Team

Need website screenshots from your Ruby or Rails application? This tutorial walks through the fastest approach: call the URLSnap REST API over HTTP. No Selenium, no headless Chrome driver, no ferrum gem to maintain — just a plain HTTP request.

Works in Rails, Sinatra, Sidekiq background jobs, and standalone Ruby scripts.

Prerequisites

The standard library covers everything. Optionally install faraday or httparty if you prefer a higher-level HTTP client — we'll show both.

Get a Free API Key

require 'net/http'
require 'json'
require 'uri'

uri = URI('https://urlsnap.dev/api/register')
res = Net::HTTP.post(uri,
  { email: 'you@example.com' }.to_json,
  'Content-Type' => 'application/json'
)
data = JSON.parse(res.body)
puts data['key']   # => "us_abc123..."
# Free tier: 20 screenshots/day

Take Your First Screenshot (stdlib)

require 'net/http'
require 'uri'

API_KEY = 'us_your_key_here'  # get free at urlsnap.dev

def screenshot_url(url, output_path, **opts)
  params = URI.encode_www_form({
    url:       url,
    format:    opts.fetch(:format, 'png'),
    full_page: opts.fetch(:full_page, 'false'),
    width:     opts.fetch(:width, 1280),
    height:    opts.fetch(:height, 800),
  })

  uri = URI("https://urlsnap.dev/api/screenshot?#{params}")

  Net::HTTP.start(uri.host, uri.port, use_ssl: true) do |http|
    req = Net::HTTP::Get.new(uri)
    req['x-api-key'] = API_KEY

    http.request(req) do |resp|
      raise "API error #{resp.code}: #{resp.body}" unless resp.code == '200'
      File.open(output_path, 'wb') { |f| resp.read_body { |chunk| f.write(chunk) } }
    end
  end

  puts "Screenshot saved to #{output_path}"
end

screenshot_url('https://example.com', 'screenshot.png', full_page: 'true')
💡 Using resp.read_body with a block streams the response directly to disk — no buffering the whole image in memory first.

All API Parameters

ParameterTypeDefaultDescription
urlstringrequiredTarget URL to screenshot
widthinteger1280Viewport width in pixels
heightinteger800Viewport height in pixels
formatstringpngpng or jpeg
full_pageboolfalseCapture full scrollable page
delayinteger0Wait ms after load, max 5000

Using Faraday (or HTTParty)

# Gemfile: gem 'faraday'
require 'faraday'

API_KEY = 'us_your_key_here'

conn = Faraday.new('https://urlsnap.dev') do |f|
  f.headers['x-api-key'] = API_KEY
  f.options.timeout = 60
end

def screenshot_with_faraday(conn, url, output_path, full_page: false)
  response = conn.get('/api/screenshot') do |req|
    req.params[:url]       = url
    req.params[:format]    = 'png'
    req.params[:full_page] = full_page.to_s
    req.params[:width]     = 1280
  end

  raise "Error #{response.status}: #{response.body}" unless response.success?

  File.binwrite(output_path, response.body)
  puts "Saved #{output_path} (#{response.body.bytesize / 1024}KB)"
end

screenshot_with_faraday(conn, 'https://example.com', 'shot.png', full_page: true)

Rails: Link Preview Controller

# app/controllers/previews_controller.rb
class PreviewsController < ApplicationController
  API_KEY = ENV.fetch('URLSNAP_API_KEY')

  def show
    url = params.require(:url)

    response = Net::HTTP.start('urlsnap.dev', 443, use_ssl: true) do |http|
      params = URI.encode_www_form(url: url, width: 1200, height: 630, format: 'png')
      req = Net::HTTP::Get.new("/api/screenshot?#{params}")
      req['x-api-key'] = API_KEY
      http.request(req)
    end

    if response.code == '429'
      render json: { error: 'Daily limit reached' }, status: :too_many_requests
    elsif response.code == '200'
      send_data response.body, type: 'image/png', disposition: 'inline'
    else
      render json: { error: 'Screenshot failed' }, status: :service_unavailable
    end
  end
end

Sidekiq Background Job

# app/jobs/screenshot_job.rb
class ScreenshotJob
  include Sidekiq::Job
  sidekiq_options retry: 3

  API_KEY = ENV.fetch('URLSNAP_API_KEY')

  def perform(page_id, target_url)
    page = Page.find(page_id)

    params = URI.encode_www_form(url: target_url, full_page: 'true', format: 'png')
    uri    = URI("https://urlsnap.dev/api/screenshot?#{params}")

    Net::HTTP.start(uri.host, uri.port, use_ssl: true) do |http|
      req = Net::HTTP::Get.new(uri)
      req['x-api-key'] = API_KEY

      resp = http.request(req)
      raise "API error #{resp.code}" unless resp.code == '200'

      # Attach to ActiveStorage
      page.screenshot.attach(
        io: StringIO.new(resp.body),
        filename: "page_#{page_id}.png",
        content_type: 'image/png'
      )
    end

    page.update!(screenshot_at: Time.current)
  end
end

Convert a URL to PDF

require 'net/http'
require 'uri'

API_KEY = 'us_your_key_here'

def url_to_pdf(url, output_path, format: 'A4', landscape: false)
  params = URI.encode_www_form(url: url, format: format, landscape: landscape.to_s)
  uri = URI("https://urlsnap.dev/api/pdf?#{params}")

  Net::HTTP.start(uri.host, uri.port, use_ssl: true) do |http|
    req = Net::HTTP::Get.new(uri)
    req['x-api-key'] = API_KEY
    resp = http.request(req)
    raise "PDF error #{resp.code}" unless resp.code == '200'
    File.binwrite(output_path, resp.body)
  end

  puts "PDF saved to #{output_path}"
end

url_to_pdf('https://example.com', 'page.pdf')

Check Your Daily Quota

require 'net/http'
require 'json'

API_KEY = 'us_your_key_here'

uri = URI('https://urlsnap.dev/api/me')
req = Net::HTTP::Get.new(uri)
req['x-api-key'] = API_KEY

res  = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |h| h.request(req) }
info = JSON.parse(res.body)

puts "Plan:       #{info['plan']}"
puts "Used today: #{info['requests_today']}/#{info['daily_limit']}"
puts "All-time:   #{info['requests_total']}"

Ready to screenshot the web with Ruby?

Free tier: 20 screenshots/day. No gem install, no browser setup, no credit card.

Get your free API key →