Using Ruby with a Screenshot API
Capture public web pages from Ruby with an SDK or HTTP, handle credentials and private routes, and automate reliable screenshots in Rails jobs.
Ruby can capture a website through a hosted screenshot API in two ways: install the provider’s Ruby gem, or send the documented HTTP request yourself. The API handles browser rendering; your Ruby code supplies the URL, capture options, credentials, and destination for the returned image.
SDK method names, authentication, output formats, and supported options differ between providers. Keep your API key on the server, verify that the target page is publicly reachable or that the provider supports its authentication method, then save or stream the response bytes.
1. Choose an integration style
| Approach | Use it when | Trade-off |
|---|---|---|
| Official Ruby SDK | The provider maintains a gem and exposes the controls you need | Less HTTP code, but the gem’s API is provider-specific |
| Direct HTTP | You need a missing option, a small dependency footprint, or a shared API client | You must implement request construction, timeouts, status handling, and retries |
| Self-hosted browser | You need complete browser control or access to an internal network | You operate Chromium, fonts, resources, concurrency, and failures yourself |
For a hosted example, ScreenshotOne documents a Ruby client and URL-generation flow in its Ruby SDK guide and SDK repository. The html2img Ruby integration demonstrates another provider’s options, including viewport size, selectors, CSS injection, DPI, full-page capture, waits, and delays (integration guide). Treat those names as vendor-specific rather than a universal Ruby API.
2. Use an official Ruby SDK
ScreenshotOne’s documented flow installs the screenshotone gem, creates a client with an access key and optional secret key, builds TakeOptions, and either generates a take URL or downloads the image response.
# Gemfile
gem 'screenshotone'
require 'screenshotone'
client = ScreenshotOne::Client.new(
access_key: ENV.fetch('SCREENSHOTONE_ACCESS_KEY'),
secret_key: ENV['SCREENSHOTONE_SECRET_KEY']
)
options = ScreenshotOne::TakeOptions.new(
url: 'https://example.com',
full_page: true,
delay: 2
)
# Option A: generate a signed image URL
image_url = client.generate_take_url(options)
puts image_url
# Option B: download the response body and save it
response = client.take(options)
File.binwrite('example.png', response.body)
Check the provider’s current gem documentation before copying this example: option names, response objects, Ruby support, and authentication details can change. The SDK repository also illustrates controls such as full_page, delay, and geolocation; use only options documented by your selected provider.
3. Call a screenshot API with Ruby HTTP
An SDK is optional. Ruby’s standard library is enough when the provider accepts a normal HTTP request. The endpoint, method, authentication header or query parameter, payload, and response format must come from that provider’s reference.
require 'net/http'
require 'uri'
api_uri = URI(ENV.fetch('SCREENSHOT_API_ENDPOINT'))
params = {
'url' => 'https://example.com',
'full_page' => 'true',
'format' => 'png'
}
api_uri.query = URI.encode_www_form(params)
request = Net::HTTP::Get.new(api_uri)
request['Authorization'] = "Bearer #{ENV.fetch('SCREENSHOT_API_KEY')}"
http = Net::HTTP.new(api_uri.host, api_uri.port)
http.use_ssl = api_uri.scheme == 'https'
http.open_timeout = 10
http.read_timeout = 90
response = http.request(request)
unless response.is_a?(Net::HTTPSuccess)
warn "Screenshot failed: HTTP #{response.code} #{response.message}"
warn response.body
exit 1
end
File.binwrite('example.png', response.body)
Some services return image bytes directly; others return JSON containing a temporary image URL. Branch on the documented content type and parse JSON only when the response is JSON. Do not assume that a successful HTTP status means the target page rendered correctly; inspect the provider’s verdict or error fields when available.
4. Ruby on Rails example
Keep the key in Rails credentials, an environment variable, or a secret manager. Queue expensive captures in Active Job instead of blocking a web request.
# app/jobs/capture_page_job.rb
class CapturePageJob < ApplicationJob
queue_as :default
def perform(url, output_path)
uri = URI(ENV.fetch('SCREENSHOT_API_ENDPOINT'))
uri.query = URI.encode_www_form('url' => url)
request = Net::HTTP::Get.new(uri)
request['Authorization'] = "Bearer #{ENV.fetch('SCREENSHOT_API_KEY')}"
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = uri.scheme == 'https'
http.open_timeout = 10
http.read_timeout = 90
response = http.request(request)
raise "capture failed with HTTP #{response.code}" unless response.is_a?(Net::HTTPSuccess)
File.binwrite(output_path, response.body)
end
end
# Queue it from a controller or service:
CapturePageJob.perform_later('https://example.com', Rails.root.join('tmp/example.png').to_s)
5. Capture controls you may need
Providers expose different subsets of these controls. Confirm each parameter in the selected API’s reference.
- Viewport: width, height, device presets, device pixel ratio, and orientation.
- Page extent: full-page capture or a single element selected by CSS.
- Timing: a fixed delay, waiting for a selector, or waiting for network idle.
- Rendering: dark mode, custom CSS, custom JavaScript, transparent background, and image format.
- Interaction: click an element before capture and hide selectors.
- Network: block ads, trackers, requests, or resource types.
- Identity and locale: headers, cookies, user agent, Authorization, timezone, and geolocation.
- Output: resizing, PDF paper size and margins, landscape mode, page ranges, caching, signed links, and asynchronous jobs.
html2img’s Ruby documentation is a concrete example of provider-specific controls: it shows viewport dimensions, a selector, injected CSS, DPI, full-page mode, selector waits, and delays. ScreenshotOne’s examples show a different Ruby API. Compare providers by the controls your application actually requires, not by parameter names alone.
6. Credentials, private pages, and browser state
- Read keys from
ENV, Rails credentials, or a secret store. Never put them in browser-delivered JavaScript. The html2img Ruby library explicitly warns that exposing a client-side key lets others spend the account’s credits (repository). - A hosted capture normally starts as an anonymous public-internet request. As html2img explains, an authenticated route can therefore produce the sign-in page instead of the private content (Ruby integration documentation).
- If a page requires authentication, use only mechanisms the provider documents, such as supported cookies, headers, or an authorization token. A user’s local browser session is not automatically available to the API.
- Do not log query strings or headers that contain keys, cookies, or bearer tokens. Redact them from exception reports.
7. Or skip the browser setup
ScreenshotNeo provides a hosted screenshot API and MCP server. One GET request returns PNG, JPEG, WebP, or PDF, while the browser work stays on the service.
See the ScreenshotNeo API documentation for the complete option reference. Ruby can call it with the same HTTP libraries shown above:
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)
raise "ScreenshotNeo failed with HTTP #{response.code}" unless response.is_a?(Net::HTTPSuccess)
File.binwrite('shot.webp', response.body)
Equivalent requests are useful when your Ruby service shares a capture endpoint with other systems:
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)
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}`);
ScreenshotNeo accepts the cookie or consent banner before capture and removes more than 60 known consent platforms, newsletter popups, and chat widgets; each cleanup step can be disabled. It bills only clean shots: bot checks or CAPTCHAs, blank pages, timeouts, failed loads, and cache hits cost nothing, and response headers identify the result with X-Page-Verdict and X-Billed. Its MCP server exposes take_screenshot, get_page_info, and capture_pdf to Claude, Cursor, and other MCP clients.
Other available controls include full-page capture with lazy images loaded, CSS element capture, dark mode, device presets or custom viewports, retina scale, PDF settings, custom CSS and JavaScript, clicks, selector waits, delays, network-idle waits, request blocking, headers, cookies, user agent, Authorization, timezone, geolocation, transparent backgrounds, resizing, chosen-TTL caching, signed links, asynchronous jobs with signed webhooks, bulk capture of up to 100 URLs per call, and a usage API. Every feature is on every plan. The Free plan includes 1,000 shots per month with no card; paid plans start at $5 for 3,000 shots.
Create a free ScreenshotNeo account and start with 1,000 screenshots per month without a card.
8. Reliability and performance checklist
- Set an explicit connect/open timeout and a longer read timeout for browser rendering.
- Use a bounded retry policy for transient network failures. Avoid retrying invalid URLs, authentication failures, or deterministic provider errors.
- Make jobs idempotent: derive an output key from the URL and capture options, or use a request identifier if the provider supports one.
- Wait for a known selector or network idle when JavaScript renders the content you need. A fixed delay is simpler but can waste time or still be too short.
- Prefer the smallest viewport, image format, and page extent that satisfies the consumer. Full pages and PDFs require more browser work and produce larger responses.
- Cache stable pages with a deliberate TTL. Invalidate the cache when source content changes.
- Limit concurrency in worker queues and monitor response status, verdict, byte size, and duration.
- Store binary responses as binary data and verify the content type before presenting them as images.
9. Common errors and fixes
| Symptom | Likely cause | Fix |
|---|---|---|
| 401 or 403 | Missing, invalid, or exposed key | Load the server-side key from secrets, check the account, and avoid logging it. |
| Image is a login page | The hosted browser is anonymous | Use documented cookies, headers, or token authentication, or capture a public route. |
| Cookie banner or popup covers content | Consent or overlay code appears after the initial load | Use a provider’s consent handling, selector hiding, click, wait, or custom JavaScript option. |
| Content is missing | Lazy loading or client-side rendering has not completed | Enable full-page lazy-image loading where available, then wait for a selector, network idle, or a suitable delay. |
| Blank or partial image | Page timeout, blocked resource, bot check, or an invalid URL | Inspect the provider’s error or verdict, test the public URL, increase the read timeout, and adjust blocking rules. |
| Ruby raises an SSL or timeout exception | Network or certificate configuration | Use HTTPS, set open and read timeouts, update the Ruby/OpenSSL runtime, and retry only transient failures. |
| Downloaded file cannot open | JSON error saved as an image, or truncated bytes | Check HTTP status and content type before writing; capture and inspect the response body on errors. |
| Gem method or option is undefined | Provider SDK version or documentation changed | Pin a compatible gem version and consult that provider’s current Ruby reference. |
10. Cost and operational notes
Hosted APIs charge according to their own billing rules, which may count successful captures, output type, bandwidth, or cache behavior differently. Confirm current limits and prices before committing to a provider. ScreenshotNeo’s billing rule is explicit: only clean shots are billed; cache hits and failed or unusable outcomes are not. Its plans are Free (1,000 shots/month), Starter ($5 for 3,000), Growth ($15 for 15,000), Pro ($39 for 60,000), Scale ($99 for 250,000), and Business ($249 for 1,000,000); yearly billing gives two months free.
For any provider, estimate volume from queued jobs, retries, cache misses, and full-page or PDF usage. Keep a usage metric in your Rails application so a traffic spike does not become an unexpected bill.
FAQ
Does Ruby need a special screenshot library?
No. A provider gem is convenient, but Ruby’s HTTP client can call any API that documents HTTP access.
Can I screenshot a page behind my login?
Only if the provider documents a supported authentication mechanism and you supply it securely. A normal hosted capture does not inherit your browser session.
Should I use a screenshot URL or download bytes?
Use a generated URL when another service can fetch it directly and the provider controls its lifetime. Download bytes when you need to store the artifact, attach it to a record, or return it from your own endpoint.
When should a Rails app use a background job?
Use a job when captures can take seconds, when pages are full length, or when you need retries and concurrency limits. Keep a small synchronous path only for latency-sensitive requests with strict timeouts.
What is the simplest hosted option for Ruby?
ScreenshotNeo’s GET endpoint works with Ruby’s standard HTTP library, removes common consent and overlay clutter, reports whether a result was billable, and includes an MCP server for AI agents. Its free tier provides 1,000 shots each month without a card.


