ScreenshotNeo

BlogGuides

Ruby SDK Examples for Website Screenshot APIs

Compare Ruby screenshot SDKs and learn to capture full pages, elements, PDFs and signed images with runnable Ruby, Rails, cURL, Python and Node.js examples.

By the ScreenshotNeo team1 October 20262 min read

To take a website screenshot in Ruby, keep your API key on the server, initialize a screenshot provider client, pass a public URL and rendering options, then save the returned bytes or hosted URL. The shortest production path is an SDK such as ScreenshotOne or html2img; Urlbox is useful when you need to construct and sign requests yourself.

This guide covers Ruby SDK setup, Rails jobs and Active Storage, full-page and selector captures, PDFs, HMAC signing, retries, webhooks, provider comparison, and a no-browser-setup option with ScreenshotNeo.

1. Choose a Ruby screenshot API

Provider Ruby client Useful capabilities Best fit
ScreenshotNeo HTTP API and MCP server Clean shots, full page, selectors, CSS/JavaScript, PDFs, devices, caching, bulk and async jobs Applications that want clean captures and simple billing
ScreenshotOne screenshotone / ScreenshotOne::Client Option builder, validation, full page, delay, geolocation, URL generation or bytes Minimal Ruby SDK integration
html2img html2img-client / Html2img::Client Ruby 3.1+, selectors, CSS injection, PDFs, Rails, Active Storage, retries and webhooks Rails production workflows
Urlbox Net::HTTP and OpenSSL HMAC-SHA256 signed URLs, viewport, full page, thumbnail, quality and PNG/JPG Low-level signed requests
ScreenshotAPI screenshotapi_to / ScreenshotAPI::Client No runtime dependencies, raw/save methods and typed errors Dependency-free Ruby services
Screenshot Scout screenshotscout / ScreenshotScout::Client Official gem and capture method Projects on Ruby 3.4+

ScreenshotNeo is the first service to try when you want clean shots, billing only for clean results, and a paid plan starting at $5. The examples below show provider-specific Ruby integrations before the ScreenshotNeo alternative.

2. ScreenshotOne Ruby SDK: bytes, validation and full pages

Add the gem and install dependencies:

# Gemfile
gem "screenshotone"

# shell
bundle install

Configure credentials as server-side environment variables. ScreenshotOne documents an access key and optional secret key; its documentation also reminds users to sign up for both keys.

client = ScreenshotOne::Client.new(
  ENV.fetch("SCREENSHOTONE_ACCESS_KEY"),
  ENV["SCREENSHOTONE_SECRET_KEY"]
)

options = ScreenshotOne::TakeOptions.new(url: "https://example.com")
  .full_page(true)
  .delay(2)

raise ArgumentError, "invalid options" unless options.valid?

File.binwrite("screenshot.jpg", client.take(options))

Use client.generate_take_url(options) when another service should fetch a generated URL. Use client.take(options) when your Ruby process should receive binary image bytes directly.

Geolocation and rendering options

options = ScreenshotOne::TakeOptions.new(url: "https://example.com")
  .full_page(true)
  .delay(2)
  .geolocation_latitude(40.7128)
  .geolocation_longitude(-74.0060)
  .geolocation_accuracy(100)

Keep delays as short as the page requires. A delay allows client-side rendering to settle, while full-page mode captures content beyond the initial viewport.

3. Rails integration with html2img

The html2img Ruby client requires Ruby 3.1 or newer and reads HTML2IMG_API_KEY by default. It supports public URLs, selector crops, CSS injection, full-page images, PDFs, CDN URLs, byte downloads, file saves and Active Storage attachments.

# Gemfile
gem "html2img-client"

# shell
bundle install

A typical Rails service keeps the key on the server and asks the client for a capture. Consult the provider's current API reference for the exact method signature and output object for your installed release.

class WebsiteCapture
  def initialize(client: Html2img::Client.new)
    @client = client
  end

  def call(url:, selector: nil, css: nil)
    @client.capture(
      url: url,
      selector: selector,
      css: css,
      full_page: true
    )
  end
end

For an Action View template, render the template on the server and submit the resulting HTML through the client's Rails integration. For Active Storage, attach the returned bytes to a model after checking the response and content type.

Background jobs and webhooks

Screenshot rendering can exceed a web request budget. Put captures in Active Job or Sidekiq, retry transient server or connection errors, discard validation errors, and use a webhook for renders that may run longer than a synchronous request.

class CaptureWebsiteJob < ApplicationJob
  retry_on Net::ReadTimeout, wait: :exponentially_longer, attempts: 4
  retry_on Errno::ECONNRESET, wait: :exponentially_longer, attempts: 4

  discard_on ActiveRecord::RecordInvalid

  def perform(capture_id, url)
    capture = Capture.find(capture_id)
    result = WebsiteCapture.new.call(url: url)
    capture.file.attach(
      io: StringIO.new(result.bytes),
      filename: "capture-#{capture.id}.png",
      content_type: "image/png"
    )
    capture.update!(status: "complete")
  end
end

4. Urlbox: signed Ruby requests with HMAC-SHA256

Urlbox's low-level pattern uses openssl, uri and net/http. Build a query string, URL-encode the target URL and options, compute an HMAC-SHA256 digest with your secret, put the token in the API path, and retrieve PNG or JPG bytes.

require "openssl"
require "uri"
require "net/http"

key = ENV.fetch("URLBOX_KEY")
secret = ENV.fetch("URLBOX_SECRET")
target = "https://example.com"

params = {
  "url" => target,
  "full_page" => "true",
  "viewport" => "1280x800",
  "quality" => "80"
}
query = URI.encode_www_form(params)
token = OpenSSL::HMAC.hexdigest("sha256", secret, query)
uri = URI("https://api.urlbox.io/v1/#{key}/#{token}/png?#{query}")

bytes = Net::HTTP.get(uri)
File.binwrite("urlbox.png", bytes)

Sign the exact query string you send. Changing parameter order, encoding or values after calculating the digest causes signature failures.

5. Complete request options to plan for

Requirement Options to look for Implementation note
Page extent Full page, viewport width and height Full-page captures may be taller and slower.
Dynamic content Delay, selector wait or network idle Wait for the condition that proves the page is ready.
Target region CSS selector or element crop Fail clearly when the selector does not exist.
Appearance Dark mode, device preset, retina scale, quality and format Use WebP for smaller files when consumers support it.
Page cleanup Custom CSS, JavaScript, hide selectors, ad or tracker blocking Keep cleanup rules versioned with your application.
Location and identity Headers, cookies, user agent, Authorization, timezone and geolocation Never expose private cookies or authorization values in logs.
Documents PDF paper size, margins, landscape and page ranges Set print styles and verify page breaks.
Delivery Raw bytes, hosted URL, cache TTL, signed links, webhooks and bulk calls Store durable files in your own object storage when retention matters.

6. cURL, Python and Node.js equivalents

The same architecture applies outside Ruby: send credentials from a server, pass a public URL, check the response, then persist bytes or a URL.

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
import requests
r = requests.get(
    "https://api.screenshotneo.com/v1/shot",
    params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"},
    timeout=90,
)
r.raise_for_status()
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const buffer = Buffer.from(await res.arrayBuffer());
await import('node:fs/promises').then(fs => fs.writeFile('shot.webp', buffer));

See the ScreenshotNeo documentation for request options and response details.

7. Or skip the browser setup

ScreenshotNeo provides a website screenshot API and MCP server. One GET request returns PNG, JPEG, WebP or PDF. It accepts cookie and consent banners like a visitor, removes more than 60 known consent platforms plus newsletter popups and chat widgets, and lets you turn each cleanup step off. Bot checks, blank pages, timeouts, failed loads and cache hits cost nothing; response headers identify the page verdict and whether it was billed.

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp

It also supports full-page captures with lazy images loaded, CSS selector captures, dark mode, 12 device presets or custom viewports, retina scale, PDF controls, custom CSS and JavaScript, pre-capture clicks, waits, blocking rules, headers, cookies, user agents, Authorization, timezone, geolocation, transparent backgrounds, resizing, chosen cache TTLs, signed links, asynchronous jobs with signed webhooks, bulk capture of up to 100 URLs per call, a usage API and an OpenAPI specification. Parameter names used by other screenshot APIs also work, which helps migrations.

An MCP server exposes take_screenshot, get_page_info and capture_pdf to Claude, Cursor and other MCP clients. Plans include 1,000 free shots per month with no card; paid plans start at $5 for 3,000 shots, and every feature is on every plan.

Create a free ScreenshotNeo account and start with 1,000 screenshots each month at no charge.

8. Secure credentials and request handling

  • Store keys in environment variables or a server-side secret manager.
  • Do not place provider keys in browser JavaScript, mobile apps or public repositories.
  • Redact cookies, Authorization headers and signed URLs from logs.
  • Validate target URLs to prevent your service from becoming an unrestricted fetch proxy.
  • Set explicit connect and read timeouts and cap response sizes before writing files.
  • Check HTTP status, content type and provider-specific error fields before treating a response as an image.

9. Reliability, performance and cost

Make captures predictable

  • Use a fixed viewport, device, timezone and geolocation for repeatable output.
  • Wait for a selector or network idle instead of adding a large blind delay.
  • Prefer selector captures when a full page is unnecessary; they transfer fewer bytes.
  • Enable caching with a deliberate TTL for pages that do not change often.
  • Queue bulk or PDF work outside the request cycle and use webhooks for long renders.

Control spend

Provider pricing and quotas change, so verify current terms before committing. Track captures by URL, response status, format and cache outcome. ScreenshotNeo's billing model charges only clean shots; failed loads, bot checks, blank pages, timeouts and cache hits are not billed, and X-Page-Verdict and X-Billed headers explain each response.

10. Troubleshooting

Symptom Likely cause Fix
Invalid ScreenshotOne options Missing URL or unsupported combination Call options.valid?, raise a clear error and check the installed gem documentation.
Blank or half-rendered image JavaScript had not finished Use a selector wait, network-idle condition or a short delay.
Element capture fails Selector is absent, delayed or inside a frame Verify the selector on the public page and wait for it before capture.
Urlbox signature rejected Signed string differs from sent query Sign the exact URL-encoded query, including parameter order and values.
Rails request times out Large page, PDF or slow third-party resources Move work to a background job and use retries or a webhook.
Private page is unauthorized Missing cookies, headers or Authorization Pass them server-side, avoid logging them and confirm the target accepts them.
Output is unexpectedly large Full-page or high retina scale Capture an element, lower scale or choose WebP where supported.
Downloaded file is not an image Error body saved as bytes Check status and content type before File.binwrite.

11. Provider selection checklist

  • Need the smallest Ruby example? Start with ScreenshotOne.
  • Need Rails, Active Storage, CSS injection, PDFs, retries or webhooks? Evaluate html2img.
  • Need explicit request signing? Use the Urlbox HMAC pattern.
  • Need no runtime dependencies? Review ScreenshotAPI.
  • Run Ruby 3.4 or newer and want another official gem? Review Screenshot Scout.
  • Need consent cleanup, MCP access, predictable billing for failed pages or a low entry price? Try ScreenshotNeo first.

FAQ

Can Ruby save screenshot bytes directly?

Yes. ScreenshotOne's client.take(options) returns bytes that you can write with File.binwrite; other clients expose byte or download methods.

Should screenshot requests run in a controller?

Use a background job for slow pages, PDFs, bulk work or webhook-based workflows. A controller is suitable only when the rendering time fits your request timeout.

How do I capture a page that requires login?

Use a provider that accepts cookies, custom headers or Authorization, and keep those values on the server. Confirm that the service can reach the page and redact credentials from logs.

Is a browser gem required?

No. Hosted screenshot APIs run the browser remotely. ScreenshotNeo reduces the integration to an HTTP request and also provides MCP tools for AI agents.

What should I verify before publishing pricing?

Recheck each provider's current gem release, Ruby requirements, limits, pricing and commercial terms. Those details can change independently of the SDK interface.