Standard library only, no gem required

Screenshot API
for Ruby

Capture any website as PNG, JPEG, WebP, or PDF with one Net::HTTP request. No Selenium, no Ferrum, no Chrome in your Docker image. Works in Rails, Sidekiq, and plain scripts.

200 free screenshots/month. No credit card required.

Ruby quickstart: screenshot with Net::HTTP

The whole API is a GET request. Your key goes in the X-API-Key header, options go in query parameters, the image comes back as the response body.

screenshot.rb
require "net/http"
require "json"

uri = URI("https://app.snap-render.com/v1/screenshot")
uri.query = URI.encode_www_form(
  url: "https://example.com",
  format: "png",
  width: 1280,
  height: 800
)

request = Net::HTTP::Get.new(uri)
request["X-API-Key"] = ENV.fetch("SNAPRENDER_API_KEY")

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

unless response.is_a?(Net::HTTPSuccess)
  error = JSON.parse(response.body)["error"]
  raise "#{error["code"]}: #{error["message"]}"
end

File.binwrite("screenshot.png", response.body)
Reusable client: full page, mobile device, dark mode, cached, with retry on 429
class SnapRender
  ENDPOINT = URI("https://app.snap-render.com/v1/screenshot")

  def initialize(api_key = ENV.fetch("SNAPRENDER_API_KEY"))
    @api_key = api_key
  end

  # Returns [bytes, headers]. Raises on non-2xx after one retry for RATE_LIMITED.
  def capture(url, **options)
    uri = ENDPOINT.dup
    uri.query = URI.encode_www_form({ url: url }.merge(options))
    request = Net::HTTP::Get.new(uri)
    request["X-API-Key"] = @api_key

    attempts = 0
    begin
      attempts += 1
      response = Net::HTTP.start(uri.host, uri.port, use_ssl: true, read_timeout: 60) { |http| http.request(request) }
      return [response.body, response.to_hash] if response.is_a?(Net::HTTPSuccess)

      error = JSON.parse(response.body)["error"]
      if error["code"] == "RATE_LIMITED" && attempts < 2
        sleep 60   # per-minute burst limit; QUOTA_EXCEEDED is monthly and should not be retried
        retry
      end
      raise "SnapRender #{response.code} #{error["code"]}: #{error["message"]}"
    end
  end
end

bytes, headers = SnapRender.new.capture(
  "https://example.com",
  format: "jpeg", quality: 85, full_page: true,
  device: "iphone_15_pro", dark_mode: true,
  cache: true, cache_ttl: 3600
)
File.binwrite("mobile-dark.jpg", bytes)
puts "cache: #{headers["x-cache"]&.first}, credits left: #{headers["x-remaining-credits"]&.first}"
Webpage to PDF
pdf, _ = SnapRender.new.capture(
  "https://example.com/report",
  format: "pdf", full_page: true, width: 1024
)
File.binwrite("report.pdf", pdf)

On the wire this is the same GET request with format=pdf; the PDF is a page image, not a text layer.

Ads and cookie banners are blocked by default. The PDF is a rendered page image, not a print-stylesheet layout. Full parameter reference in the docs.

Take a screenshot in Ruby without Selenium or Ferrum

Ferrum and selenium-webdriver are good tools for system tests, where a browser is the point. As a way to produce an image of a URL in production they carry a lot: a Chrome build in every Docker image, chromedriver versions that must match, a font stack, and a browser process inside your Sidekiq worker that grows until the container is restarted. A timeout on one slow page can stall a whole queue.

With a screenshot API the browser runs on our side. Ruby sends one request from the standard library and receives the finished image. Cookie banners and ads are removed, full-page capture handles lazy loading, and your Gemfile and Dockerfile do not change.

On Rails, the Rails guide shows the Active Job plus Active Storage version: capture in a background job, attach the bytes to a record, serve from your storage service.

Works in Heroku, Fly.io, Render, and slim Docker images

Because the integration is a GET request, it runs anywhere Ruby can open an outbound HTTPS connection. No buildpack for Chrome, no apt-get list in the Dockerfile, and no memory headroom reserved for a browser next to Puma.

SnapRender API Ferrum / Selenium in production
Dependencies Net::HTTP (stdlib) Gem plus Chrome plus chromedriver
Docker image Unchanged Hundreds of MB larger
Worker memory Not your problem Browser process per job
Cookie banners and ads Blocked by default Custom code per site
Caching cache=true, hits are free Build it yourself
Browser updates Handled for you Yours to test and ship

Want to see the output first? Try the free website screenshot tool. It uses the same rendering engine as the API.

Ruby screenshot API FAQ

How do I take a screenshot of a website in Ruby?+

Build a URI for the /v1/screenshot endpoint with the target in the url parameter, send a Net::HTTP GET with your key in the X-API-Key header, and write response.body to a file. The page renders in real Chrome on SnapRender's servers, so your Ruby process never starts a browser.

Do I need Selenium, Ferrum, or Chrome installed?+

No. The only dependency is Net::HTTP from the standard library. There is no chromedriver to match to a Chrome version, no browser to add to your Docker image, and no Xvfb on the server.

Is there a SnapRender gem?+

Not yet. The API is a plain HTTP endpoint, so the examples here use Net::HTTP and work unchanged with Faraday or HTTParty. A gem is planned; the request format will not change when it ships.

How do I attach a screenshot to an Active Storage record in Rails?+

Wrap response.body in StringIO and call record.image.attach(io:, filename:, content_type:). Do the capture inside an Active Job so the request cycle is not blocked by a render that takes a few seconds. The Rails guide on this site walks through the job.

Is there a free tier for the Ruby screenshot API?+

Yes. The free plan includes 200 screenshots per month with every feature: all formats including PDF, full-page capture, device emulation, dark mode, and caching. No credit card is required to sign up.

Not writing Ruby today?

The same API has quickstarts for other languages.

Related guides: Screenshots in Rails with Active Storage and serverless runtimes.

Or skip the setup entirely

Everything in this guide is one GET request with SnapRender. No browser to babysit, no timeouts to tune.

200 screenshots a month free. First render in under a minute.

One Net::HTTP call between you and the screenshot

Grab a key, paste the snippet, done. 200 free screenshots per month, no credit card.