Screenshot API for Ruby.
Shotium is a hosted screenshot API you call from Ruby with stdlib net/http: no Selenium, no Ferrum, no Chrome binary in your Gemfile or your Docker image. Send a URL, get back PNG, JPEG or WebP bytes — with full-page capture, template-based OG images, and HMAC-signed URLs you can embed in public HTML.
How do I take a screenshot in Ruby?
Stdlib only — no gems. Sign in, create an API key, and this runs as-is:
require "net/http"
uri = URI("https://api.shotium.com/v1/screenshot")
uri.query = URI.encode_www_form(url: "https://example.com", format: "png")
req = Net::HTTP::Get.new(uri)
req["Authorization"] = "Bearer #{ENV.fetch('SHOTIUM_KEY')}"
res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true, read_timeout: 60) do |http|
http.request(req)
end
raise "render failed: #{res.code}" unless res.is_a?(Net::HTTPSuccess)
File.binwrite("shot.png", res.body)Full-page screenshots and viewport control
Set full_page to capture the entire scroll height (up to 20,000px). URI.encode_www_form serializes Ruby's true to the literal the API expects:
uri.query = URI.encode_www_form(
url: "https://news.ycombinator.com",
full_page: true,
format: "webp",
quality: 90,
width: 1280,
)Generate OG images from templates
POST typed parameters into one of five built-in templates and get a finished 1200×630 social card back — no HTML to write:
require "json"
require "net/http"
uri = URI("https://api.shotium.com/v1/og-image")
req = Net::HTTP::Post.new(uri)
req["Authorization"] = "Bearer #{ENV.fetch('SHOTIUM_KEY')}"
req["Content-Type"] = "application/json"
req.body = JSON.generate(
template: "blog",
params: { title: "Shipping fast without breaking things", author: "Ada L." },
format: "png",
)
res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true, read_timeout: 60) do |http|
http.request(req)
end
File.binwrite("og.png", res.body)How do I embed OG images without exposing my API key?
Sign the query string with your signing secret instead of sending your key. ERB::Util.url_encode is RFC 3986 (CGI.escape is not — it turns spaces into +), and Hash#sort gives byte-order keys:
require "openssl"
require "erb"
params = { "template" => "minimal", "title" => "Less, but better.", "uid" => "YOUR-UID" }
canonical = params.sort.map { |k, v|
"#{ERB::Util.url_encode(k)}=#{ERB::Util.url_encode(v)}"
}.join("&")
sig = OpenSSL::HMAC.hexdigest("SHA256", ENV.fetch("SHOTIUM_SIGNING_SECRET"), canonical)
url = "https://api.shotium.com/v1/og-image?#{canonical}&sig=#{sig}"Error handling
Every error is RFC 9457 problem+json with a stable type field you can branch on. Failed renders are never billed:
res = http.request(req)
unless res.is_a?(Net::HTTPSuccess)
problem = JSON.parse(res.body) # RFC 9457 problem+json
case problem["type"]
when "rate_limited"
wait = res["Retry-After"].to_i # back off, then retry
when "render_timeout"
# site took >30s — retry or skip
end
endParameters at a glance
| Param | Default | Notes |
|---|---|---|
url | required | http(s) URL to render |
width / height | 1280 × 800 | Viewport, up to 3840 × 2160 |
full_page | false | Full scroll height, ≤20,000px |
format | png | png | jpeg | webp |
quality | 80 | 1–100, lossy formats only |
Full reference — auth, OG templates, rate limits, error table — in the docs. Template parameters live on /og-templates.
Frequently asked questions
Do I need Ferrum or Selenium to take screenshots in Ruby?
No. Ferrum, Watir and Selenium all need a Chrome binary installed and supervised alongside your app. Shotium runs the browsers server-side — from Ruby it's one stdlib net/http request, nothing in your Gemfile.
How do I take a full-page screenshot in Ruby?
Pass full_page: true to URI.encode_www_form — Ruby serializes it as the literal "true" the API expects. The renderer scrolls, waits for lazy-loaded content, and captures up to 20,000px. Raise read_timeout above the default for tall pages and add format: "webp".
Can I call the API from Rails background jobs?
Yes — it's plain HTTPS, so Sidekiq and ActiveJob workers work unchanged, and the binary response drops straight into Active Storage. Rate limit is 60 requests/minute per key, so cap job concurrency accordingly.
How should Ruby code react to a failed render?
Test with res.is_a?(Net::HTTPSuccess) before binwrite — net/http returns error responses as ordinary objects, so an unchecked body lands in your .png. Failures carry RFC 9457 problem+json: rate_limited means sleep for the Retry-After header, render_timeout means the target took over 30 seconds. Failed renders are never billed.
Try it on your own pages
Sign-up is GitHub OAuth and comes with 100 free render credits — no card. Failed renders never bill. Plans from $15/month on pricing.
Also available for: Python · Node.js · PHP · Go · Java · cURL