Screenshot API for Ruby: Net::HTTP, Faraday & Rails Integration

Capture screenshots, scrape web pages, and generate PDFs from Ruby applications. Works with Rails, Sinatra, and standalone scripts — no headless browser setup required.

Start Free — 200 captures/moView Docs

Screenshot API for Ruby — Net::HTTP Quickstart

Ruby's standard library includes everything you need to call SnapAPI. The Net::HTTP class handles HTTPS requests without any gems. Here is a minimal function that returns a JPEG screenshot as a binary string:

require 'net/http'
require 'uri'

def capture_screenshot(url, api_key)
  params = URI.encode_www_form(
    url: url,
    full_page: true,
    format: 'jpeg',
    block_ads: true
  )
  uri = URI("https://api.snapapi.pics/v1/screenshot?#{params}")
  req = Net::HTTP::Get.new(uri)
  req["X-Api-Key"] = api_key

  Net::HTTP.start(uri.host, uri.port, use_ssl: true) do |http|
    http.read_timeout = 30
    response = http.request(req)
    raise "SnapAPI #{response.code}: #{response.body}" unless response.is_a?(Net::HTTPSuccess)
    response.body
  end
end

# Save screenshot to file
image_data = capture_screenshot("https://example.com", ENV["SNAPAPI_KEY"])
File.write("screenshot.jpg", image_data, mode: "wb")
puts "Captured #{image_data.bytesize} bytes"

The use_ssl: true option enables HTTPS. Setting read_timeout to 30 seconds is appropriate for most screenshot requests. For video recording endpoints, increase the timeout to 120 seconds to accommodate longer render times.

Using Faraday for a Cleaner Interface

If your project already uses Faraday, the integration is even more concise. Faraday handles connection pooling, retries, and middleware transparently:

require 'faraday'

conn = Faraday.new(url: "https://api.snapapi.pics") do |f|
  f.request :retry, max: 3, interval: 2, backoff_factor: 2,
    exceptions: [Faraday::TimeoutError, Faraday::ConnectionFailed]
  f.options.timeout = 30
end

response = conn.get("/v1/screenshot") do |req|
  req.headers["X-Api-Key"] = ENV["SNAPAPI_KEY"]
  req.params[:url] = "https://example.com"
  req.params[:full_page] = true
  req.params[:block_cookies] = true
end

raise "Error: #{response.status}" unless response.success?
File.write("screenshot.jpg", response.body, mode: "wb")

The retry middleware handles transient 5xx errors and network timeouts automatically. Exponential backoff via backoff_factor: 2 doubles the wait between each retry attempt, reducing load on the API during periods of high demand.

Rails Integration: ActiveJob, Background Workers & Active Storage

In a Rails application, screenshot generation typically happens asynchronously to avoid blocking web requests. Wrap the SnapAPI call in an ActiveJob class and enqueue it from your controller:

class ScreenshotJob < ApplicationJob
  queue_as :default

  def perform(record_id, target_url)
    record = Page.find(record_id)
    image_data = SnapApiClient.capture(target_url)

    # Store using Active Storage
    record.screenshot.attach(
      io: StringIO.new(image_data),
      filename: "screenshot-#{record_id}.jpg",
      content_type: "image/jpeg"
    )
    record.update!(screenshot_taken_at: Time.current)
  end
end

# In your controller:
ScreenshotJob.perform_later(page.id, page.url)

Active Storage handles the upload to S3, GCS, or Azure Blob Storage transparently. The StringIO wrapper converts the binary string returned by SnapAPI into an IO object that Active Storage can read.

Scraping and Data Extraction in Ruby

SnapAPI's scrape and extract endpoints return structured JSON, making them a natural fit for Ruby's hash-based data handling. The extract endpoint accepts a CSS selector and returns the matched text directly:

require 'net/http'
require 'json'

def extract_price(product_url, api_key)
  uri = URI("https://api.snapapi.pics/v1/extract")
  req = Net::HTTP::Post.new(uri, "Content-Type" => "application/json")
  req["X-Api-Key"] = api_key
  req.body = {
    url: product_url,
    selector: ".price, [data-price], .product-price",
    wait_for: ".price"
  }.to_json

  Net::HTTP.start(uri.host, uri.port, use_ssl: true) do |http|
    response = http.request(req)
    JSON.parse(response.body)["text"]
  end
end

The wait_for parameter tells the API to wait until the specified selector is present in the DOM before extracting. This handles JavaScript-rendered pages and lazy-loaded content that Nokogiri-based scrapers miss entirely.

PDF Generation in Ruby with SnapAPI

Generating PDFs from HTML templates is a common Rails use case: invoices, reports, certificates, account statements. SnapAPI's PDF endpoint accepts a URL or raw HTML and returns a binary PDF with full CSS support, custom page sizes, and optional headers and footers:

def generate_invoice_pdf(invoice_url, api_key)
  uri = URI("https://api.snapapi.pics/v1/pdf")
  req = Net::HTTP::Post.new(uri, "Content-Type" => "application/json")
  req["X-Api-Key"] = api_key
  req.body = {
    url: invoice_url,
    page_size: "A4",
    margin_top: "20mm",
    margin_bottom: "20mm",
    print_background: true
  }.to_json

  Net::HTTP.start(uri.host, uri.port, use_ssl: true, read_timeout: 60) do |http|
    response = http.request(req)
    raise "PDF error: #{response.code}" unless response.is_a?(Net::HTTPSuccess)
    response.body
  end
end

PDF generation is typically slower than screenshots because the browser must lay out the entire document for print media. The 60-second timeout accommodates complex invoice templates with embedded charts or custom fonts. For simpler one-page PDFs, 30 seconds is usually sufficient.

Try SnapAPI free — 200 captures per month at snapapi.pics, no credit card required. Ruby integration requires zero additional gems when using Net::HTTP. Sign up, copy your API key from the dashboard, and make your first screenshot call in under five minutes.

Batch Screenshot Capture in Ruby

Many use cases require capturing dozens or hundreds of URLs in bulk — SEO audits, competitor monitoring, visual regression suites, or product catalogue snapshots. Ruby handles concurrent HTTP requests well using threads. The following pattern runs multiple SnapAPI calls in parallel while capping concurrency to avoid hitting rate limits:

require 'net/http'
require 'uri'
require 'fileutils'

def batch_capture(urls, api_key, output_dir, workers: 5)
  FileUtils.mkdir_p(output_dir)
  queue = Queue.new
  urls.each { |u| queue << u }

  threads = workers.times.map do
    Thread.new do
      until queue.empty?
        url = queue.pop(true) rescue nil
        next unless url
        data = capture_screenshot(url, api_key)
        slug = url.gsub(/[^a-z0-9]/, '-')[0..60]
        File.write("#{output_dir}/#{slug}.jpg", data, mode: 'wb')
        puts "Done: #{url}"
      end
    end
  end
  threads.each(&:join)
end

urls = File.readlines('urls.txt').map(&:strip)
batch_capture(urls, ENV['SNAPAPI_KEY'], 'screenshots', workers: 5)

Ruby threads are suitable for I/O-bound workloads like HTTP requests because they release the GIL while waiting for network responses. Five concurrent workers is a safe default for the Starter plan. Increase to 20 for Pro accounts.

Storing Screenshots with Shrine or Paperclip

If you prefer using a file attachment library rather than Active Storage, both Shrine and Paperclip accept IO objects. Wrap the binary screenshot data in StringIO and pass it to your uploader:

# Using Shrine
uploader = ScreenshotUploader.new(:store)
io = StringIO.new(capture_screenshot(url, api_key))
uploaded_file = uploader.upload(io, metadata: { filename: 'capture.jpg', mime_type: 'image/jpeg' })
record.update!(screenshot_data: uploaded_file.to_json)

# Using CarrierWave
record.screenshot = CarrierWave::SanitizedFile.new(
  tempfile: StringIO.new(capture_screenshot(url, api_key)),
  filename: 'capture.jpg',
  content_type: 'image/jpeg'
)
record.save!

Both libraries handle the upload to your configured storage backend — S3, GCS, or local disk — transparently. The screenshot binary never touches your web server disk, which matters for containerised deployments with read-only filesystems.

Ruby Gem and SDK Overview

The official SnapAPI Ruby SDK is available at github.com/Sleywill/snapapi-ruby. It wraps all API endpoints — screenshot, scrape, extract, PDF, video, and AI analysis — in a clean Ruby interface with sensible defaults. If you prefer not to add a dependency, the Net::HTTP examples above cover the full API surface with zero additional gems.

SnapAPI supports eight official SDKs: JavaScript, Python, Go, PHP, Swift, Kotlin, Ruby, and C#. All SDKs are MIT-licensed and available on GitHub. They share a consistent interface: instantiate a client with your API key, then call named methods for each endpoint. Error responses raise typed exceptions rather than returning error hashes, making them idiomatic for each language.

Pricing and Free Tier

SnapAPI offers a free tier with 200 captures per month — no credit card required. The Starter plan at $19 per month provides 5,000 captures and is sufficient for small monitoring pipelines, invoice PDF generation, and development teams building screenshot features. The Pro plan at $79 per month covers 50,000 captures with higher concurrency limits and priority processing. Business at $299 per month unlocks 500,000 captures and is used by e-commerce platforms running daily competitor price monitoring across thousands of product pages.

All paid plans include the full API surface: screenshots, scraping, extraction, PDF generation, video recording, and AI page analysis. Sign up at snapapi.pics, grab your key from the dashboard, and make your first Ruby screenshot call in under five minutes.

Testing SnapAPI Integrations in Ruby

Writing reliable tests for code that calls external APIs requires a stubbing strategy. In RSpec, use WebMock to stub the SnapAPI endpoint and return fixture data. This keeps your test suite fast and deterministic without making real HTTP calls:

# spec/services/snap_api_client_spec.rb
require 'webmock/rspec'

RSpec.describe SnapApiClient do
  let(:jpeg_fixture) { File.read('spec/fixtures/screenshot.jpg', mode: 'rb') }

  before do
    stub_request(:get, /api\.snapapi\.pics\/v1\/screenshot/)
      .to_return(status: 200, body: jpeg_fixture,
                 headers: { 'Content-Type' => 'image/jpeg' })
  end

  it "returns binary image data" do
    result = SnapApiClient.capture("https://example.com")
    expect(result.bytesize).to be > 0
  end

  it "raises on API error" do
    stub_request(:get, /api\.snapapi\.pics/).to_return(status: 401, body: 'Unauthorized')
    expect { SnapApiClient.capture("https://example.com") }.to raise_error(RuntimeError, /401/)
  end
end

Keep a small JPEG file as a fixture in spec/fixtures/ so WebMock has realistic binary data to return. Test the error path by stubbing a 401 or 429 response to verify your retry and error-handling logic works correctly before deploying to production.

For integration tests that call the real API, use a dedicated SnapAPI account with the free tier. Run integration tests in CI only on the main branch to keep your monthly quota from being consumed by every pull request.

Why Ruby Developers Choose SnapAPI

Ruby developers value clean APIs with sensible defaults, and SnapAPI delivers exactly that. A single HTTP call replaces hundreds of lines of Playwright or Ferrum setup code. There are no browser binaries to install in your Docker image, no Chromium version conflicts with your CI environment, and no memory management issues from leaked browser processes in long-running Sidekiq workers. The API is stateless, versioned, and backward-compatible, so your Ruby integration keeps working as the underlying browser infrastructure evolves.

Sign up at snapapi.pics — 200 free captures per month, no credit card required. Your first Ruby screenshot call takes under five minutes from registration to a saved JPEG on disk.