Skip to content

Ruby SDK

Reference
For:
Developers

The HostRepute Ruby gem, hostrepute version 0.1.0, wraps the REST API v1 with immutable models, automatic retries and helpers that start a check and wait for its result. It needs Ruby 3.2 or later and uses only the standard library (net/http, json, openssl).

Gemfile (when it's published)
gem "hostrepute", "~> 0.1"

Then run bundle install, or install it without Bundler with gem install hostrepute.

Hostrepute::Client.new takes a team API token and keyword options. Create the token in Settings, then API tokens with the abilities your code needs; Authentication and API tokens explains them.

app.rb
require "hostrepute"
client = Hostrepute::Client.new(
ENV.fetch("HOSTREPUTE_API_TOKEN"),
timeout: 45,
user_agent: "deploy-checks/1.4"
)
client.hosts.iterate(per_page: 100).each do |host|
puts "#{host.host} #{host.blocklisted? ? "listed" : "not listed"}"
end

To share one client across your app, configure it once and use Hostrepute.client. The configuration reads the token from HOSTREPUTE_API_TOKEN unless you call with_token:

config/initializers/hostrepute.rb
Hostrepute.configure do |config|
config.with_timeout(45)
end

Hostrepute.client without configure also reads HOSTREPUTE_API_TOKEN, and raises an ArgumentError when it’s empty. Client.new raises an ArgumentError for an empty token, an invalid base URL or retry setting, or a protected default header. It ignores options it doesn’t know, so check their spelling.

Option Default What it does
base_url: https://app.hostrepute.com The API’s address. Give the origin only: the SDK adds /api/v1/… to every path.
timeout: 30 seconds The read and write timeout of each request.
open_timeout: 10 seconds The connection timeout.
user_agent: hostrepute-ruby/0.1.0 (ruby <version>) The User-Agent header.
retry: {max_attempts: 3, base_delay_ms: 200, max_delay_ms: 5000} The retry policy. See retries.
default_headers: {} Extra headers on every request. Authorization, Accept, Content-Type and User-Agent aren’t allowed.
logger: none A Logger. The SDK logs each request and response at debug level (method, URL, attempt, status), never the token.

The builder (Hostrepute::Client.builder, also used by configure) has a with_… method for each option.

The client has one method per API resource. The names follow the API, not the portal:

In HostRepute Method Methods API
Domains and IPs client.hosts list, iterate, get, create, update, delete /api/v1/hosts
Blocklists client.data_sources list, iterate, get /api/v1/data-sources
Check profiles client.monitoring_profiles list, iterate, get, create, update, delete /api/v1/data-source-groups
Alert channels client.alerts list, iterate, get, create, update, delete /api/v1/alerts
Delivery hours client.contact_groups list, iterate, get, create, update, delete /api/v1/alert-groups
Blocklist checks client.blacklist check, status, check_and_wait, wait_for /api/v1/blacklist
Certificate checks client.certificates check, status, check_and_wait, wait_for /api/v1/certificates
Email authentication checks client.deliverability check, status, check_and_wait, wait_for /api/v1/deliverability
Prometheus metrics client.metrics get /api/v1/metrics
  • list(page:, per_page:) returns a Hostrepute::Page with data, current_page, last_page, per_page, total and has_next_page?. per_page is 1–100; the API sends 15 when you leave it out. iterate(per_page:) returns a lazy enumerator over every page. Read Pagination.
  • get, update and delete take the uuid, except data_sources.get, which takes the blocklist’s numeric id.
  • update sends PUT with the keywords you pass; nil means “leave it out”.
  • The SDK has no method for sending a test alert. Call POST /api/v1/alerts/{alert}/test directly.

Every domain or IP needs a check profile. The SDK calls it monitoring_profile_uuid: and sends it as data_source_group_uuid:

Add a domain with the default check profile
profile = client.monitoring_profiles.list.data.find(&:is_default)
host = client.hosts.create(
name: "Mail",
host: "mail.example.com",
monitoring_profile_uuid: profile.uuid
)

Delivery hours are contact_groups in the SDK. schedule: is a Hash that the SDK sends unchanged, so give it the shape the API expects:

  • Keys mon, tue, wed, thu, fri, sat and sun, each with enabled, start and end. Times are HH:MM, and an enabled day must end after it starts. Symbol or string keys both work.
  • A day you leave out gets no alerts. Leave schedule: out entirely for delivery hours that are open at any time.
  • update leaves out nil keywords, so it can’t turn existing delivery hours back to any time. Send "schedule": null with the REST API instead.
Weekdays 08:00 to 18:00 in New York
weekday = {enabled: true, start: "08:00", end: "18:00"}
weekend = {enabled: false, start: "08:00", end: "18:00"}
hours = client.contact_groups.create(
name: "Business hours",
timezone: "America/New_York",
schedule: {
mon: weekday, tue: weekday, wed: weekday, thu: weekday, fri: weekday,
sat: weekend, sun: weekend
}
)

An alert channel is alerts. settings: holds the field the channel type needs (email here; Set up alert channels lists them all), and contact_group_uuids: the delivery hours it follows:

An email alert channel that follows the delivery hours
channel = client.alerts.create(
type: Hostrepute::AlertType::EMAIL,
frequency: Hostrepute::AlertFrequency::ONCE_ON_LISTED_DELISTED,
settings: {email: "[email protected]"},
contact_group_uuids: [hours.uuid]
)

For frequency:, use AlertFrequency::ONCE_ON_LISTED_DELISTED (Listed and removed in the portal) or AlertFrequency::ONCE_PER_CHECK_WHILE_LISTED (Only when listed). The SDK also defines AlertFrequency::DAILY, which the API rejects with 422, and AlertFrequency::EVERY_CHECK, a retired value that the API stores as once_on_listed_delisted.

Each check uses 1 credit (credit costs). Blocklist and certificate checks run in the background:

  • check starts the check and returns it with status "pending".
  • status(uuid) reads it again.
  • wait_for(uuid) polls status until the check is completed or failed.
  • check_and_wait is check followed by wait_for.

timeout: sets how long to wait (60 seconds by default) and poll_interval: how often to poll (2 seconds). When the time runs out, the SDK raises Hostrepute::TimeoutError with the check’s scan_uuid and the last status it read in last_observation:

Run a blocklist check and wait up to two minutes
begin
scan = client.blacklist.check_and_wait(host: "mail.example.com", timeout: 120, poll_interval: 5)
puts "#{scan.status}: listed on #{scan.listed_data_source_count} of #{scan.data_source_count} blocklists"
rescue Hostrepute::TimeoutError => e
puts "Still running: #{e.scan_uuid} (#{e.last_observation.status}). Resume with client.blacklist.wait_for."
end

A failed check comes back as a result, not an exception: check status and error_message. The full per-blocklist result is in scan.result. A certificate check works the same way:

Check a mail server's certificate over STARTTLS
cert = client.certificates.check_and_wait(
host: "smtp.example.com",
protocol: Hostrepute::CertificateProtocol::STARTTLS_SMTP,
port: 587
)

To be told when a check ends instead of polling, pass callback_url: to blacklist.check or certificates.check. A callback’s signature can’t be verified yet, so treat the callback as a notification and read the result with status, as Receive webhooks explains. The SDK’s own webhook helpers don’t match what HostRepute sends, so don’t use them. Run checks and wait for results covers the API side of polling and callbacks.

An email authentication check answers in the same request, so deliverability.check already returns the final result. Its status is passed, warning or failed, or skipped when none of the checks applied to the host:

Check SPF and DMARC for a domain
result = client.deliverability.check(
host: "example.com",
checks: [Hostrepute::DeliverabilityCheck::SPF_RECORD, Hostrepute::DeliverabilityCheck::DMARC_RECORD]
)
puts result.status

The SDK retries a failed request up to 3 attempts in total:

  • 429 is retried for every method. The SDK waits for Retry-After (seconds or an HTTP date) when the response has it. A 429 means the API refused the request before running it, so a retried check isn’t charged twice.
  • 5xx and network errors are retried only for GET, HEAD, OPTIONS, PUT and DELETE. A POST that creates something or starts a check isn’t retried, so the SDK never starts a check twice.
  • Backoff without Retry-After is a random delay between zero and 200 ms × 2attempt − 1, capped at 5 seconds.
  • When the attempts run out, you get the exception for the last response, for example a RateLimitError.
Five attempts with longer waits
tuned = Hostrepute::Client.new(
ENV.fetch("HOSTREPUTE_API_TOKEN"),
retry: {max_attempts: 5, base_delay_ms: 500, max_delay_ms: 10_000}
)

retry: {max_attempts: 1} turns retries off. Rate limits lists the limits behind 429.

All SDK exceptions inherit from Hostrepute::Error. API errors inherit from Hostrepute::ApiError, with status, message, errors (field names to messages), body, raw_body, headers and request_id.

Status or cause Exception Extra attributes
401 AuthenticationError
402 PaymentRequiredError required_credits, available_credits
403 AuthorizationError
404 NotFoundError
422 ValidationError errors
429 RateLimitError retry_after, in seconds
5xx ServerError
Any other status ApiError
No response, or an HTTP timeout NetworkError cause, the original exception
A wait helper’s timeout TimeoutError scan_uuid, last_observation
A response the SDK can’t read DecodingError raw_body
Handle the errors a check can return
begin
client.blacklist.check(host: "mail.example.com")
rescue Hostrepute::PaymentRequiredError => e
puts "Needs #{e.required_credits} credits, #{e.available_credits} left"
rescue Hostrepute::ValidationError => e
e.errors.each { |field, messages| puts "#{field}: #{messages.join(", ")}" }
rescue Hostrepute::RateLimitError => e
puts "Rate limited, retry in #{e.retry_after} s"
end

Errors shows the response body behind each status.

client.metrics.get returns the team’s metrics; body (or to_s) is the Prometheus text. The token needs metrics:read.

Print the team's metrics
puts client.metrics.get.body

body has binary encoding; call force_encoding("UTF-8") on it before you work with it as text.