> ## Documentation Index
> Fetch the complete documentation index at: https://developers.scrapeunblocker.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Ruby

> Install the official scrapeunblocker gem, or call the plain HTTP API directly.

The official Ruby client wraps the ScrapeUnblocker API in a small, dependency-free gem (standard library only) with typed errors and automatic retries.

```bash theme={null}
gem install scrapeunblocker
```

Or in a Gemfile: `gem "scrapeunblocker"`. Requires Ruby 2.7+.

Store your API key in an environment variable so the client picks it up automatically:

```bash theme={null}
export SCRAPEUNBLOCKER_KEY="YOUR_API_KEY"
```

## Quickstart

```ruby theme={null}
require "scrapeunblocker"

su = ScrapeUnblocker::Client.new # reads SCRAPEUNBLOCKER_KEY, or Client.new(api_key: "YOUR_API_KEY")

html = su.get_page_source("https://example.com")
```

## Get parsed JSON instead of HTML

```ruby theme={null}
product = su.get_parsed("https://www.amazon.com/dp/B08N5WRWNW")

puts product.page_type # "product"
puts product.source    # how it was extracted
p product.data         # the fields

# If a parse ever comes back wrong, force a fresh set of rules:
fresh = su.get_parsed(url, refresh_rules: true, rules_hint: "price is missing")
```

See the [parsed data guide](/guides/parsed-data) for response shapes.

## Scrape a Google SERP

```ruby theme={null}
serp = su.serp("best running shoes", pages_to_check: 2)

serp["organic"].each do |result|
  puts "#{result['position']} #{result['title']} #{result['url']}"
end
```

## Force a country

```ruby theme={null}
result = su.get_parsed("https://www.amazon.de/dp/B08N5WRWNW", proxy_country: "de")
```

## Capture cookies and the proxy used

```ruby theme={null}
page = su.get_page_with_cookies("https://example.com")
puts page.html
p page.cookies
puts page.proxy
```

## Fetch an image

```ruby theme={null}
bytes = su.get_image("https://example.com/photo.jpg")
File.binwrite("photo.jpg", bytes)
```

## Skyscanner plugins

```ruby theme={null}
locations = su.skyscanner.flight_locations("London")

flights = su.skyscanner.flights(
  origin: "London", dest: "New York",
  depart_date: "2026-09-01", adults: 1, currency: "USD"
)

hotels = su.skyscanner.hotels(destination: "Madrid", checkin: "2026-09-01", checkout: "2026-09-03")
cars = su.skyscanner.carhire(pickup: "Madrid", pickup_datetime: "2026-09-01T10:00", dropoff_datetime: "2026-09-03T10:00")
```

## Error handling

Non-2xx responses raise typed errors, all subclasses of `ScrapeUnblocker::Error`. Transient failures (429, 502, 503, 504 and network errors) are retried automatically; a 401 or 402 is never retried, because it clears when the key or the billing state changes.

```ruby theme={null}
begin
  html = su.get_page_source("https://example.com")
rescue ScrapeUnblocker::BlockedError
  # 403: the target blocked every bypass path (not billed)
rescue ScrapeUnblocker::PaymentRequiredError
  # 402: quota, credit limit, or a failed payment - fix billing
rescue ScrapeUnblocker::RateLimitError
  # 429: slow down
rescue ScrapeUnblocker::UpstreamOutageError
  # 503: the target site itself is down - retry later
end
```

| Error                      | Status | Meaning                                                           |
| -------------------------- | ------ | ----------------------------------------------------------------- |
| `InvalidRequestError`      | 400    | Bad URL, unsupported scheme, or the API key header was not sent   |
| `AuthenticationError`      | 401    | Key not recognised - typo, stray whitespace, or a rotated key     |
| `NoSubscriptionError`      | 401    | Key is fine, but the account has no active plan                   |
| `PaymentRequiredError`     | 402    | Billing block - base class for the three below                    |
| `QuotaExceededError`       | 402    | The plan's requests for this period are used up                   |
| `CreditLimitExceededError` | 402    | Unpaid balance is past the account's credit limit                 |
| `PaymentFailedError`       | 402    | A card payment was declined three times                           |
| `BlockedError`             | 403    | Blocked by bot protection on every path                           |
| `NotFoundError`            | 404    | Page loaded but held no image (`get_image` only)                  |
| `BrowserTimeoutError`      | 408    | Our browser run timed out before the page was ready               |
| `UnsupportedContentError`  | 415    | The URL serves something other than HTML                          |
| `ValidationError`          | 422    | Missing or wrong-typed parameter; `body` holds the `detail` array |
| `RateLimitError`           | 429    | Too many requests                                                 |
| `UpstreamOutageError`      | 503    | The target origin is down                                         |
| `ServerError`              | 5xx    | Unexpected server error, including a 504 upstream timeout         |
| `TimeoutError`             | -      | This client gave up locally before the API answered               |
| `ConnectionError`          | -      | Could not reach the API                                           |

<Note>
  The `402` and `404`-`422` errors were added in **0.1.6**; before that they arrived as a bare `APIError`. Nothing was removed, so `rescue ScrapeUnblocker::APIError` written against earlier versions keeps working. See all status codes in [Errors](/errors).
</Note>

Tune the retry count and timeout on the client:

```ruby theme={null}
ScrapeUnblocker::Client.new(timeout: 180, max_retries: 2)
```

## Raw HTTP (without the gem)

The API is plain HTTP, so Ruby's standard library is enough - no gem required.

```ruby theme={null}
require "net/http"
require "json"

uri = URI("https://api.scrapeunblocker.com/getPageSource?url=https://example.com&parsed_data=true")
req = Net::HTTP::Post.new(uri)
req["x-scrapeunblocker-key"] = ENV["SCRAPEUNBLOCKER_KEY"]
res = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |http| http.request(req) }
payload = JSON.parse(res.body)
puts payload["data"]["page_type"]
```

The endpoints are `getPageSource`, `serpApi`, and `getImage`; every parameter shown above maps to a query parameter of the same name.
