How to Capture a Webpage Screenshot with a Screenshot API in Ruby
Capture a webpage from Ruby with a hosted screenshot API. Learn the request flow, capture options, error handling, and a no-browser alternative.
To capture a webpage screenshot from Ruby, send the target URL and capture options to a hosted screenshot API, then save or use the image returned by that service. The API runs the browser remotely, so your Ruby application does not need to install or operate a browser. Keep the API key on the server, and check the provider’s documentation for its exact option names, output format, and response shape.
This guide uses the html2img Ruby guide for its provider-specific Ruby example. It also shows the general HTTP request shape and a ScreenshotNeo option. Hosted APIs differ, so do not assume that options from one service work unchanged with another.
1. Install and configure a Ruby client
The documented html2img client requires Ruby 3.1 or newer and an API key. Install the gem using the command in the provider’s current guide, then set the key in your server environment or secret store. Do not embed it in browser JavaScript, a mobile app, or a public repository.
gem install html2img
export HTML2IMG_API_KEY="your_api_key"
Here is the provider’s documented client pattern. It requests a 1200 × 630 viewport screenshot and prints the returned URL:
require "html2img"
client = Html2img::Client.new(api_key: ENV.fetch("HTML2IMG_API_KEY"))
response = client.screenshot(
"https://example.com",
width: 1200,
height: 630
)
puts response.url
This example reflects the documented client interface; it has not been independently executed for this article. The response includes URL and status information according to the provider README. Follow that provider’s instructions for downloading the image from the returned URL, including any expiration or storage behavior that applies.
2. Understand the request and response
A hosted screenshot request typically contains a target URL, authentication, output or viewport settings, and optional readiness instructions. The rendering service opens the page and captures it remotely. Your Ruby process receives a response, which may contain image bytes, a URL to an image, or a job identifier depending on the API.
For html2img, the guide also documents a POST /api/screenshot endpoint. Use the client or raw HTTP request according to your application’s needs, but use the current API documentation as the authority for authentication headers, JSON fields, status codes, and response parsing.
3. Choose what to capture
| Need | Documented html2img option | When to use it |
|---|---|---|
| Set the browser viewport | width, height |
For a screenshot that represents a particular viewport, such as a social preview or desktop view. |
| Capture the whole document | fullpage: true |
For pages taller than the initial viewport. Check current provider behavior for very long pages and lazy-loaded content. |
| Capture one component | selector |
For a chart, product card, or other element identified by a CSS selector. |
| Wait for a known element | wait_for_selector |
When a specific element indicates that client-rendered content is ready. |
| Wait a fixed interval | ms_delay |
When the page needs extra time but has no reliable readiness selector. A fixed delay can waste time or still be too short. |
| Hide an overlay | CSS injection | For page elements such as consent banners or chat widgets, if the provider supports injected CSS. |
Example using full-page capture and a readiness selector:
response = client.screenshot(
"https://example.com/reports",
fullpage: true,
wait_for_selector: "#report-ready"
)
Example cropping to an element:
response = client.screenshot(
"https://example.com/products/widget",
selector: ".product-card"
)
Exact option support and limits vary by provider. The html2img README documents dimensions from 1 to 5000 and says its client validates recognized options locally. Confirm the current limits and accepted option names before relying on them in production.
4. Handle dynamic pages and overlays
For a page rendered by JavaScript, prefer waiting for a selector that appears only after the relevant content is ready. This ties capture timing to the page state. Use a fixed delay when there is no reliable selector, and choose a conservative value based on the page’s behavior. A delay does not guarantee that a slow or failed request has completed.
CSS injection can hide visual clutter before capture. The html2img guide documents CSS injection for overlays; use selectors specific to the target site. If the site’s styles override the injected rule, !important may be needed. Hiding an element is different from accepting a consent banner: CSS only changes what is rendered in the image.
response = client.screenshot(
"https://example.com",
css: ".cookie-banner, .chat-widget { display: none !important; }"
)
Check the provider’s documentation for the exact CSS argument name and whether injected styles run before the screenshot. Avoid broad selectors that hide legitimate page content.
5. Use raw HTTP when you need a direct request
Some screenshot APIs expose a JSON endpoint, so Ruby can call them with its standard HTTP library. The following is a generic sketch of a JSON POST request, not a drop-in html2img or ScreenshotNeo request: replace the endpoint, authentication, and payload fields with those specified by your chosen provider.
require "net/http"
require "json"
require "uri"
uri = URI("https://provider.example/api/screenshot")
request = Net::HTTP::Post.new(uri)
request["Content-Type"] = "application/json"
request["Authorization"] = "Bearer #{ENV.fetch("SCREENSHOT_API_KEY")}"
request.body = JSON.generate({
url: "https://example.com",
width: 1200,
height: 630
})
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = (uri.scheme == "https")
response = http.request(request)
unless response.is_a?(Net::HTTPSuccess)
raise "Screenshot API returned #{response.code}: #{response.body}"
end
puts response.body
Before treating a response as an image, inspect the content type and the documented response format. Some services return binary bytes; others return JSON containing an image URL or asynchronous job information. Do not save a JSON error body with a .png extension.
6. Keep the result reliable
- Set a client timeout: rendering can take longer than a normal API call. Choose a timeout supported by the provider and your job runner.
- Check status and response type: handle non-success responses and parse the documented response before saving output.
- Make retries deliberate: retry transient network failures or provider-documented temporary errors with a limit and backoff. Avoid repeated retries for invalid URLs, authentication failures, or unsupported options.
- Use asynchronous delivery for long renders: html2img documents a 30-second budget for synchronous requests and a webhook workflow for longer work. Handle the processing response and wait for the webhook before expecting a final URL.
- Protect webhook handling: follow the provider’s verification procedure and make job completion idempotent, so duplicate deliveries do not create duplicate work.
- Keep credentials and captured content private: API keys belong in server-side secrets. Consider whether the target page or resulting image contains sensitive information before storing or sharing it.
7. Public pages, private pages, and network access
The html2img guide says its captures are anonymous requests from the public internet. A private route may therefore show a sign-in page instead of the intended content. Do not send credentials or assume an authenticated capture mode exists unless the selected provider documents it and you have reviewed the security implications.
The remote renderer also cannot access resources available only on your machine. The html2img README specifically notes that localhost resources do not resolve from its renderer. A page’s images, scripts, stylesheets, and other required resources must be reachable from the rendering service. For staging systems behind a VPN or firewall, use a documented secure access feature if available; otherwise the remote browser cannot render them.
8. cURL, Python, and Node.js request examples
These examples use the ScreenshotNeo GET endpoint and its documented query parameters. They capture a public URL and write the response body to a WebP file. Replace YOUR_API_KEY with a key stored securely in your environment; for production scripts, avoid putting secrets directly into shell history or source code. See the ScreenshotNeo API documentation for available parameters and response headers.
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,
)
r.raise_for_status()
with open("shot.webp", "wb") as image_file:
image_file.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}`);
if (!res.ok) throw new Error(`Screenshot API returned ${res.status}`);
await Bun.write('shot.webp', new Uint8Array(await res.arrayBuffer()));
The Node.js sample uses Bun’s file writer for a short runnable save step. With Node’s built-in APIs, write the buffer using await import('node:fs/promises').then(fs => fs.writeFile('shot.webp', Buffer.from(await res.arrayBuffer()))) inside an async function. Keep the API key in an environment variable in deployed code.
9. Or skip the browser setup
ScreenshotNeo is a screenshot API and MCP server: one GET request with a URL returns an image or PDF, while its remote browser handles rendering. Here is the Ruby request using the standard library:
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"
)
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
http.read_timeout = 90
response = http.get(uri)
unless response.is_a?(Net::HTTPSuccess)
raise "ScreenshotNeo returned #{response.code}: #{response.body}"
end
File.binwrite("shot.webp", response.body)
For a minimal request, the equivalent cURL call is:
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
ScreenshotNeo removes cookie and consent banners, newsletter popups, and chat widgets before capture; each cleanup step can be turned off. Bot checks, blank pages, timeouts, failed loads, and cache hits are not billed, and response headers report the page verdict and billing status. Its MCP server provides take_screenshot, get_page_info, and capture_pdf for Claude, Cursor, and other MCP clients. The free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000 screenshots. Every feature is available on every plan. See the API documentation for options including full-page capture, element selection, waits, custom CSS and JavaScript, output formats, caching, and asynchronous jobs.
Create a free ScreenshotNeo account for 1,000 screenshots a month with no card.
10. Performance and cost considerations
Capture time depends on the target page, its network resources, rendering work, and the provider’s limits. A large full-page document or a page waiting on dynamic content may take longer than a simple viewport capture. Use a selector-based readiness condition when possible, avoid excessive fixed delays, and move work that may exceed the synchronous budget to an asynchronous workflow.
Hosted APIs avoid operating the browser stack yourself, while browser automation such as Puppeteer Ruby gives more direct control and makes your team responsible for maintaining that software. The cited Puppeteer Ruby reference documents screenshot functionality, but the available research does not establish a performance comparison. Compare providers using their documented capture options, access model, output and storage behavior, synchronous limits, webhook behavior, and pricing; do not infer a service guarantee from an example or a successful response.
11. Troubleshooting
| Symptom | Likely cause | What to do |
|---|---|---|
| The image shows a login page | The renderer visits anonymously and the route requires authentication. | Use a publicly reachable page or a provider-documented authenticated capture feature after reviewing its security model. |
| Images or styles are missing | Resources are private, blocked, or referenced from localhost. |
Make required resources reachable from the remote renderer and verify the target page loads them publicly. |
| The screenshot misses client-rendered content | Capture began before the page finished rendering. | Wait for a meaningful selector; use a fixed delay only when a selector is unavailable. |
| The screenshot is unexpectedly tall or cropped | Viewport and full-page capture have different scopes, or an element selector changed the capture target. | Check whether you need fullpage: true, adjust viewport dimensions, and verify the selector matches one intended element. |
| A CSS rule does not hide an overlay | The selector does not match or site styles override the injected rule. | Inspect the page selector and use a more specific rule; try !important where supported. |
| The request exceeds the synchronous limit | The page takes longer than the provider’s request budget. | For html2img, the README gives a 30-second synchronous budget; use its documented webhook workflow for longer work. |
| The API rejects a dimension or option | The value is outside the provider’s limits or the option name is unsupported. | Check the current provider documentation. html2img documents dimensions from 1 to 5000 and local validation for recognized options. |
| The saved file is not an image | The response is JSON or an error body rather than image bytes. | Check HTTP status and content type, then follow the provider’s documented response format before writing the file. |
| Ruby raises a missing-key error | The environment variable is unset or unavailable to the process. | Set the secret in the same runtime environment that launches the application, and verify its name without printing its value. |
12. Frequently asked questions
Can Ruby capture a screenshot without a screenshot API?
Yes. Ruby browser automation such as Puppeteer Ruby can control a browser and take screenshots. That gives direct control, but your application must operate and maintain the browser software. A hosted API moves rendering to the provider.
Can I use this for a page behind login?
Not with an anonymous renderer unless the provider documents another access method. The html2img guide says it captures as an anonymous public visitor, so a protected page commonly returns its sign-in screen.
Should I use a delay or wait for a selector?
Use a selector when the page has a reliable element that signals the content is ready. A fixed delay is simpler but may waste time and cannot guarantee readiness.
Does a successful API response mean the intended page was captured?
Not necessarily. The request can succeed while the page displays a login screen, bot check, or incomplete content. Inspect the returned image and use provider-specific verdict or status information where available.
Sources
- html2img Ruby guide — installation, client usage, capture options, and anonymous rendering behavior.
- html2img Ruby README — response behavior, dimension limits, synchronous budget, and webhook workflow.
- Puppeteer Ruby project — browser automation and screenshot functionality.
- ScreenshotNeo documentation — API parameters and product behavior.


