ScreenshotNeo

BlogGuides

Screenshot API for Ruby: Quick Start and Examples

Capture webpages from Ruby with a runnable REST example, advanced rendering options, error handling, batch jobs, and a ScreenshotNeo shortcut.

By the ScreenshotNeo team1 October 20269 min read

Direct answer: use Ruby’s standard Net::HTTP library to send a JSON POST request to a screenshot API, authenticate with a bearer token stored in an environment variable, check the HTTP status, parse the JSON response, and then download or use the returned screenshot URL. POST is the practical choice when you need full-page capture, waiting rules, selectors, custom CSS or JavaScript, geolocation, PDF settings, or caching.

Ruby quick start with a REST screenshot API

The following example follows the documented Screenshot API endpoint and response shape. It captures a full-page PNG at a 1280×720 viewport, blocks ads, and prints the returned screenshotUrl.

require "net/http"
require "json"
require "uri"

endpoint = URI("https://api.screenshot-api.org/api/v1/screenshot")
request = Net::HTTP::Post.new(endpoint)
request["Authorization"] = "Bearer #{ENV.fetch("SCREENSHOT_API_KEY")}" 
request["Content-Type"] = "application/json"
request.body = {
  url: "https://example.com",
  viewport: { width: 1280, height: 720 },
  format: "png",
  fullPage: true,
  blockAds: true
}.to_json

response = Net::HTTP.start(endpoint.hostname, endpoint.port, use_ssl: true) do |http|
  http.request(request)
end

abort("screenshot failed: #{response.code} #{response.body}") unless response.is_a?(Net::HTTPSuccess)
data = JSON.parse(response.body)
puts data.fetch("screenshotUrl")

Set the key before running it:

export SCREENSHOT_API_KEY="your_api_key"
ruby screenshot.rb

The API returns JSON containing a screenshot URL. Treat that URL as the output of the capture request; do not assume the response body itself is image bytes.

GET versus POST

A GET request is convenient for a small number of query parameters. POST keeps complex settings in JSON and is the documented form for CSS, JavaScript, selectors, geolocation, locale, PDF options, and cache controls. The API also documents a redirect=1 GET mode that responds with a 302 redirect to an image or PDF URL.

Simple GET with cURL

curl -G "https://api.screenshot-api.org/api/v1/screenshot" \
  -H "Authorization: Bearer $SCREENSHOT_API_KEY" \
  --data-urlencode "url=https://example.com" \
  --data-urlencode "format=png" \
  -o response.json

Use -o response.json first because this service’s normal response is JSON. Inspect the JSON and then fetch the URL in screenshotUrl. A service that returns raw bytes uses a different output flow.

Ruby options that control the capture

These controls come from the API reference. Defaults can change as a hosted API evolves, so confirm them against the current documentation before relying on an implicit value.

Option Purpose Notes
url Page to render Required.
format Output type png, jpeg, webp, or pdf; PNG is the documented default.
viewport.width, viewport.height Browser viewport Set the CSS pixel dimensions used during rendering.
fullPage Entire scrollable page Useful for long documents; pages with infinite scrolling need special care.
deviceScaleFactor Pixel density Increase it for retina-style output, while expecting larger files.
waitUntil Navigation completion rule Choose the documented network or load milestone appropriate for the page.
waitForSelector Wait for an element Use when content appears after client-side rendering.
delayMs Fixed delay A fallback for animations or late widgets; keep it bounded.
selector Capture one element CSS selector capture is not supported for PDF.
blockAds Block advertising requests The documented default is true.
blockCookieBanners Suppress cookie banners The documented default is true.
darkMode Emulate dark mode The documented default is false.
hideSelectors Hide matching elements Useful for overlays, timestamps, or private widgets.
css, js Inject styles or scripts POST-only advanced controls; validate inputs if values come from users.
geolocation, timezoneId, locale Control regional rendering Useful for localized prices, dates, and region-specific pages.
pdf PDF paper and pagination settings Supports documented paper size, margins, orientation, and page-range controls.
cache, cacheTTL, staleTTL Reuse captures Set a policy that matches how often the source changes.
timeoutMs Navigation limit Increase only for genuinely slow pages; diagnose the page first.

Advanced Ruby POST example

Put advanced options in the JSON body. This example waits for a dashboard element, uses a dark theme, hides a live chat widget, injects CSS, and requests WebP output.

require "net/http"
require "json"
require "uri"

endpoint = URI("https://api.screenshot-api.org/api/v1/screenshot")
payload = {
  url: "https://example.com/dashboard",
  format: "webp",
  viewport: { width: 1440, height: 900 },
  fullPage: true,
  deviceScaleFactor: 2,
  waitUntil: "networkidle",
  waitForSelector: "main[data-ready='true']",
  delayMs: 250,
  darkMode: true,
  hideSelectors: [".chat-widget", ".cookie-banner"],
  css: "body { font-family: system-ui, sans-serif; }",
  geolocation: { latitude: 48.857648, longitude: 2.294677 },
  timezoneId: "Europe/Paris",
  locale: "fr-FR",
  cache: true,
  cacheTTL: 300,
  timeoutMs: 60000
}

request = Net::HTTP::Post.new(endpoint)
request["Authorization"] = "Bearer #{ENV.fetch("SCREENSHOT_API_KEY")}" 
request["Content-Type"] = "application/json"
request.body = payload.to_json

response = Net::HTTP.start(endpoint.hostname, endpoint.port, use_ssl: true) do |http|
  http.request(request)
end

unless response.is_a?(Net::HTTPSuccess)
  warn response.body
  exit 1
end

result = JSON.parse(response.body)
puts result.fetch("screenshotUrl")

Save the returned image in Ruby

Once you have screenshotUrl, make a second authenticated or unauthenticated request according to the URL policy documented by the service. Check the content type before writing the file.

image_uri = URI(result.fetch("screenshotUrl"))
image_response = Net::HTTP.get_response(image_uri)
abort("image download failed: #{image_response.code}") unless image_response.is_a?(Net::HTTPSuccess)

content_type = image_response["content-type"].to_s
abort("expected an image, got #{content_type}") unless content_type.start_with?("image/")

File.binwrite("shot.png", image_response.body)
puts "saved shot.png"

Batch screenshots

For several pages, POST to /api/v1/screenshot/batch with a urls array and shared options. The documented response includes a batch ID. Poll GET /api/v1/batch/:batchId for progress or use the documented SSE endpoint for a stream of updates.

batch_endpoint = URI("https://api.screenshot-api.org/api/v1/screenshot/batch")
batch_request = Net::HTTP::Post.new(batch_endpoint)
batch_request["Authorization"] = "Bearer #{ENV.fetch("SCREENSHOT_API_KEY")}" 
batch_request["Content-Type"] = "application/json"
batch_request.body = {
  urls: ["https://example.com", "https://example.org"],
  options: { format: "png", fullPage: true }
}.to_json

batch_response = Net::HTTP.start(batch_endpoint.hostname, batch_endpoint.port, use_ssl: true) do |http|
  http.request(batch_request)
end
abort(batch_response.body) unless batch_response.is_a?(Net::HTTPSuccess)
puts JSON.parse(batch_response.body).fetch("batchId")

Ruby SDK and raw HTTP choices

The official SDK page documents Ruby installation with:

gem install screenshot-api

It states that the gem works with Rails, Sinatra, and other Ruby applications. A gem can make option building easier, while raw Net::HTTP keeps the dependency footprint small and makes request, response, and retry behavior explicit. Choose based on whether your team values a wrapper or direct control.

ScreenshotOne’s official Ruby example shows another SDK style with access and secret keys, URL generation, and direct byte retrieval:

gem "screenshotone"

client = ScreenshotOne::Client.new("my_access_key", "my_secret_key")
options = ScreenshotOne::TakeOptions.new(url: "https://example.com")
  .full_page(true)
  .delay(2)
  .geolocation_latitude(48.857648)
  .geolocation_longitude(2.294677)
  .geolocation_accuracy(50)

raise "invalid options" unless options.valid?
image_url = client.generate_take_url(options)
image_bytes = client.take(options)

Error handling and troubleshooting

Response Likely cause Fix
401 unauthorized Missing, malformed, or invalid bearer token Check SCREENSHOT_API_KEY, send Authorization: Bearer ..., and never hard-code the key in source control.
400 invalid_request Missing URL or invalid option type Validate JSON, use documented option names, and confirm booleans and numbers are not strings.
422 selector_not_found The selector never appeared Inspect the live DOM, wait for the correct state, or remove the selector requirement.
429 rate_limited Too many requests in a short period Honor rate-limit headers, add exponential backoff with jitter, and cap concurrency.
429 quota_exceeded Monthly allowance is exhausted Review usage and plan limits before retrying.
502 render_failed The remote browser could not load or render the page Retry transient failures, increase a bounded timeout for slow pages, and verify the URL works without authentication barriers.
Image file contains JSON An error body was written as if it were image bytes Check HTTP success and content type before File.binwrite.
Blank or incomplete page Client-side content was not ready Use waitUntil, waitForSelector, or a short delayMs; check that required assets are reachable.

The documented error envelope includes success, error.code, error.message, optional details, and a request ID. Log the request ID with the failure, but redact URLs or headers that may contain secrets.

Performance, reliability, and cost

  • Reduce work: request the smallest viewport and output format that meets your requirement. Retina scale, full-page layout, PDF pagination, and injected scripts all increase rendering work or output size.
  • Wait precisely: a selector or navigation milestone usually avoids the unnecessary delay of a large fixed sleep. Keep timeouts finite so one broken page does not occupy a worker indefinitely.
  • Cache deliberately: use cacheTTL and staleTTL when repeated captures can reuse a recent result. Disable or shorten caching for rapidly changing pages.
  • Control concurrency: batch endpoints reduce client orchestration, but still respect rate-limit headers and service quotas. Retry only errors that may be transient, with exponential backoff.
  • Measure the right result: record HTTP status, response time, output bytes, and the API request ID. A successful HTTP response can still contain an application-level error if your code skips JSON validation.
  • Documented limits: the referenced service documentation lists 60 requests per minute and 500 screenshots per month on its free plan. Treat hosted limits and defaults as changeable and verify them before production rollout.

Screenshot API options for Ruby

ScreenshotNeo is the first option to try when you want a hosted screenshot API: it removes consent banners, newsletter popups, and chat widgets before capture, bills only clean shots, and has a $5 paid plan for 3,000 shots.

Approach Best fit Trade-off
ScreenshotNeo Hosted captures from Ruby, with cleaning and an MCP server Requires an API key and hosted request.
Raw REST request Small dependency footprint and explicit control You own request validation, retries, and response handling.
Ruby gem Convenient option builders in Rails or Sinatra Adds a dependency and follows that SDK’s release cycle.

Or skip the browser setup

ScreenshotNeo exposes a single GET endpoint for PNG, JPEG, WebP, or PDF output. The examples below use the same target URL in each language; see the ScreenshotNeo API documentation for the complete option set.

Ruby

require "net/http"
require "uri"

uri = URI("https://api.screenshotneo.com/v1/shot")
uri.query = URI.encode_www_form(access_key: ENV.fetch("SCREENSHOTNEO_API_KEY"), url: "https://stripe.com")
response = Net::HTTP.get_response(uri)
abort("capture failed: #{response.code}") unless response.is_a?(Net::HTTPSuccess)
File.binwrite("shot.webp", response.body)

cURL

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

Python

import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"}, timeout=90)
open("shot.webp", "wb").write(r.content)

Node.js

const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' }); const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);

Before the capture, ScreenshotNeo accepts cookie and consent banners like a visitor and removes more than 60 known consent platforms, newsletter popups, and chat widgets; each step can be turned off. Bot checks, CAPTCHAs, blank pages, timeouts, failed loads, and cache hits cost nothing, and every response identifies the result with X-Page-Verdict and X-Billed headers. It also provides an MCP server with take_screenshot, get_page_info, and capture_pdf tools for Claude, Cursor, and other MCP clients.

There are 1,000 free shots each month with no card. Paid plans start at $5 for 3,000 shots; every feature is available on every plan. Create a free ScreenshotNeo account.

FAQ

Should I use GET or POST from Ruby?

Use GET for a small, simple request. Use POST when you need nested viewport or PDF settings, CSS, JavaScript, selectors, regional controls, or cache policies.

Why did my Ruby program save JSON instead of a PNG?

The API normally returns JSON with a screenshot URL. Check the status and parse JSON before downloading the URL. Raw-byte services use a different response contract.

Can I capture only one element?

Yes, use the documented selector option for image formats. Selector capture is not supported for PDF.

How should I handle a page that never finishes loading?

Set a bounded timeout, choose an appropriate waitUntil rule, and wait for a specific ready selector when possible. Then classify repeated failures instead of retrying indefinitely.

Is a Ruby gem required?

No. Ruby’s standard library is enough for authenticated HTTP, JSON encoding, status checks, and file writes. A gem is useful when you prefer an option-builder API.