ipwhois
Official, dependency-free Ruby client for the ipwhois.io IP Geolocation API.
- ✅ Single and bulk IP lookups (IPv4 and IPv6)
- ✅ Works with both the Free and Paid plans
- ✅ HTTPS by default
- ✅ Localisation, field selection, threat detection, rate info
- ✅ Never raises — all errors returned as
'success' => falsehashes - ✅ No runtime dependencies — Ruby stdlib only (
net/http,uri,json,openssl) - ✅ Ruby 3.0+
Installation
Add to your Gemfile:
gem 'ipwhois'
Then run:
bundle install
Or install directly:
gem install ipwhois
Free vs Paid plan
The same Ipwhois::Client class is used for both plans. The only difference is
whether you pass an API key:
- Free plan — create the client without arguments. No API key, no signup required. Suitable for low-traffic and non-commercial use.
- Paid plan — create the client with your API key from https://ipwhois.io. Higher limits, plus access to bulk lookups and threat-detection data.
free = Ipwhois::Client.new # Free plan — no API key
paid = Ipwhois::Client.new('YOUR_API_KEY') # Paid plan — with API key
Everything else (lookup, options, error handling) is identical.
Quick start — Free plan (no API key)
require 'ipwhois'
client = Ipwhois::Client.new # no API key
info = client.lookup('8.8.8.8')
puts "#{info['country']} #{info.dig('flag', 'emoji')}"
# → United States 🇺🇸
puts "#{info['city']}, #{info['region']}"
# → Mountain View, California
Quick start — Paid plan (with API key)
Get an API key at https://ipwhois.io and pass it to the constructor:
require 'ipwhois'
client = Ipwhois::Client.new('YOUR_API_KEY') # with API key
info = client.lookup('8.8.8.8')
puts "#{info['country']} #{info.dig('flag', 'emoji')}"
# → United States 🇺🇸
puts "#{info['city']}, #{info['region']}"
# → Mountain View, California
ℹ️ Pass nothing to look up your own public IP:
client.lookup— works on both plans.
Lookup options
Every option below can be passed per call, or set once on the client as a default.
| Option | Type | Plans needed | Description |
|---|---|---|---|
:lang |
String | Free + Paid | One of: 'en', 'ru', 'de', 'es', 'pt-BR', 'fr', 'zh-CN', 'ja' |
:fields |
Array | Free + Paid | Restrict the response to specific fields (e.g. %w[country city]) |
:rate |
Boolean | Basic and above | Include the rate block (limit, remaining) |
:security |
Boolean | Business and above | Include the security block (proxy/vpn/tor/hosting) |
Setting defaults once
Every option can be passed two ways: per call (as keyword arguments to
lookup / bulk_lookup) or once as a default on the client. Per-call
options always override the defaults, so it's safe to set sensible defaults
and only override what differs for a specific call.
Defaults are set with fluent setters — set_language, set_fields,
set_security, set_rate, set_timeout, set_connect_timeout,
set_user_agent — and can be chained:
# Free plan
client = Ipwhois::Client.new
.set_language('en')
.set_fields(%w[success country city flag.emoji])
.set_timeout(8)
# Paid plan
client = Ipwhois::Client.new('YOUR_API_KEY')
.set_language('en')
.set_fields(%w[success country city flag.emoji])
.set_timeout(8)
Either client behaves the same way at call time — per-call options always win over the defaults:
client.lookup('8.8.8.8') # uses lang=en, the field whitelist, and timeout=8
client.lookup('1.1.1.1', lang: 'de') # overrides lang for this single call only
⚠️ When you restrict fields with
set_fields(or the per-call:fieldsoption), the API only returns the fields you ask for. Always include'success'in the list if you rely oninfo['success']for error checking — otherwise the field will be missing on responses.ℹ️
set_security(true)requires Business+ andset_rate(true)requires Basic+. See the table above for what's available where.
HTTPS Encryption
By default, all requests are sent over HTTPS. If you need to disable it (for
example, in environments without an up-to-date CA bundle), pass ssl: false
to the constructor:
# Free plan
client = Ipwhois::Client.new(nil, ssl: false)
# Paid plan
client = Ipwhois::Client.new('YOUR_API_KEY', ssl: false)
ℹ️ HTTPS is strongly recommended for production traffic — your API key is sent in the query string and would otherwise travel in clear text.
Bulk lookup (Paid plan only)
The bulk endpoint sends up to 100 IPs in a single GET request. Each address counts as one credit. Available on the Business and Unlimited plans.
client = Ipwhois::Client.new('YOUR_API_KEY')
results = client.bulk_lookup([
'8.8.8.8',
'1.1.1.1',
'208.67.222.222',
'2c0f:fb50:4003::' # IPv6 is fine — mix freely
])
results.each do |row|
if row['success'] == false
# Per-IP errors (e.g. "Invalid IP address") are returned inline,
# they do NOT raise — the rest of the batch is still usable.
puts "skip #{row['ip']}: #{row['message']}"
next
end
puts "#{row['ip']} → #{row['country']}"
end
ℹ️ Bulk requires an API key. Calling
bulk_lookupwithout one will fail at the API level.
Error handling
The library never raises. Every failure — invalid IP, bad API key, rate
limit, network outage, bad options — comes back inside the response hash
with 'success' => false and a 'message'. Just check info['success']
after every call:
info = client.lookup('8.8.8.8')
unless info['success']
warn "Lookup failed: #{info['message']}"
return
end
puts info['country']
This means an outage of the ipwhois.io API (or of your server's DNS, connection, etc.) will never surface as a fatal error in your application — you decide how to react.
Error response fields
Every error response contains 'success' => false, a human-readable
'message', and an 'error_type' so you can branch on the category of the
failure. Some errors include extra fields you can branch on:
| Field | When it's present |
|---|---|
'success' |
Always — false for error responses (true for successful responses) |
'message' |
Always — human-readable description of what went wrong |
'error_type' |
Always — one of 'api', 'network', or 'invalid_argument' |
'http_status' |
On HTTP 4xx / 5xx responses |
'retry_after' |
On HTTP 429 — free plan only (the paid endpoint does not send a Retry-After header) |
info = client.lookup('8.8.8.8')
unless info['success']
if info['http_status'] == 429
sleep(info['retry_after'] || 60)
# …retry
end
if info['error_type'] == 'network'
# DNS failure, connection refused, timeout, …
end
warn "Error: #{info['message']}"
return
end
Response shape
A successful response includes (depending on your plan and selected options):
{
"ip": "8.8.4.4",
"success": true,
"type": "IPv4",
"continent": "North America",
"continent_code": "NA",
"country": "United States",
"country_code": "US",
"region": "California",
"region_code": "CA",
"city": "Mountain View",
"latitude": 37.3860517,
"longitude": -122.0838511,
"is_eu": false,
"postal": "94039",
"calling_code": "1",
"capital": "Washington D.C.",
"borders": "CA,MX",
"flag": {
"img": "https://cdn.ipwhois.io/flags/us.svg",
"emoji": "🇺🇸",
"emoji_unicode": "U+1F1FA U+1F1F8"
},
"connection": {
"asn": 15169,
"org": "Google LLC",
"isp": "Google LLC",
"domain": "google.com"
},
"timezone": {
"id": "America/Los_Angeles",
"abbr": "PDT",
"is_dst": true,
"offset": -25200,
"utc": "-07:00",
"current_time": "2026-05-08T14:31:48-07:00"
},
"currency": {
"name": "US Dollar",
"code": "USD",
"symbol": "$",
"plural": "US dollars",
"exchange_rate": 1
},
"security": {
"anonymous": false,
"proxy": false,
"vpn": false,
"tor": false,
"hosting": false
},
"rate": {
"limit": 250000,
"remaining": 50155
}
}
Responses are parsed with the stdlib JSON module, so all hashes use string
keys (info['country'], not info[:country]). Use Hash#dig for safe
access to nested keys: info.dig('flag', 'emoji').
For the full field reference, see the official documentation.
An error response looks like:
{
"success": false,
"message": "Rate limit exceeded",
"error_type": "api", // 'api' / 'network' / 'invalid_argument'
"http_status": 429, // present for HTTP 4xx / 5xx
"retry_after": 60 // additionally present on HTTP 429 — free plan only
}
Requirements
- Ruby 3.0 or newer
- No runtime gem dependencies (uses stdlib only)
Development
git clone https://github.com/IPWhois/ipwhois-ruby.git
cd ipwhois-ruby
bundle install
rake test
Contributing
Issues and pull requests are welcome on GitHub.
License
MIT © ipwhois.io