ShotWolf Ruby SDK

Official Ruby client for the ShotWolf API - capture screenshots, render device mockups, generate Open Graph cards, convert HTML to PDF, and run pixel-level visual regression diffs through a single JSON HTTP API.

  • Zero dependencies (standard library only, Ruby 3.0+)
  • Built-in retries with backoff, idempotency keys, and an async polling helper

Install

gem install shotwolf

Or in a Gemfile:

gem "shotwolf"

Quick start

Get an API key at shotwolf.com/dashboard/api_keys.

require "shotwolf"

sw = Shotwolf::Client.new  # reads SHOTWOLF_API_KEY, or pass Shotwolf::Client.new("sw_live_...")

shot = sw.capture(url: "https://example.com", full_page: true, dismiss_cookies: true)
puts shot["image_url"]

Usage

Screenshots

# Sync - returns the image URL inline (~1-4s)
shot = sw.capture(url: "https://stripe.com", device: "iphone_15_pro")

# Async - queue and poll (or pass webhook_url for a callback)
queued = sw.capture_async(url: "https://stripe.com")
done = sw.wait_for(queued["id"])  # polls until status is done

# Batch - one page, many viewports (cheaper than N calls)
batch = sw.batch(url: "https://stripe.com", devices: ["default", "iphone_15_pro", "ipad_pro_12_9"])

Device & social mockups

mockup = sw.mockup(url: "https://stripe.com", frame: "macbook_air", bg: "gradient")

Open Graph cards

og = sw.og(template: "blog_card", variables: { title: "Ship faster", author: "Jane Doe" })

HTML / URL to PDF

pdf = sw.pdf(html: "<h1>Invoice #42</h1>", page_format: "A4", print_background: true)

Visual regression diff

diff = sw.diff(
  before_url: "https://staging.example.com",
  after_url: "https://example.com",
  ignore_regions: [{ x: 0, y: 0, width: 1280, height: 80 }]  # mask a dynamic header
)
puts "#{(diff['diff_ratio'] * 100).round(2)}% changed" unless diff["passed"]

Account balance

 = sw.
puts ["balance_cr"], ["tier"]["name"]

Idempotency

Pass idempotency_key: to make money-charging POSTs safe to retry. A repeat with the same key and body within 24h replays the original response instead of charging again.

require "securerandom"
sw.capture(url: "https://example.com", idempotency_key: SecureRandom.uuid)

Error handling

Any non-2xx response raises Shotwolf::Error carrying the API error type, HTTP status, and message.

begin
  sw.capture(url: "https://example.com")
rescue Shotwolf::Error => e
  # e.type == "insufficient_credits" -> top up at https://shotwolf.com/pricing
  warn [e.status, e.type, e.message, e.request_id].inspect
end

Configuration

sw = Shotwolf::Client.new(
  "sw_live_xxx",
  base_url: "https://shotwolf.com",  # override for self-host / testing
  timeout: 60,                       # per-request timeout in seconds
  max_retries: 2,                    # retries on 429 / 5xx
  retry_backoff: 0.5                 # base backoff, doubled per attempt
)

Retries honor the Retry-After header on 429 responses. 4xx responses other than 429 are never retried.

API coverage

Method Endpoint
capture(**params) POST /api/v1/captures
capture_async(**params) POST /api/v1/captures/async
batch(**params) POST /api/v1/capture/batch
mockup(**params) POST /api/v1/mockups
og(**params) POST /api/v1/og
pdf(**params) POST /api/v1/pdf
diff(**params) POST /api/v1/diffs
get_screenshot(id) GET /api/v1/screenshots/:id
wait_for(id) polls get_screenshot until done/failed
account GET /api/v1/account

Full parameter reference: shotwolf.com/docs.

License

MIT