ipwhois

package module
v1.2.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 10, 2026 License: MIT Imports: 10 Imported by: 0

README

ipwhois-go

Go Reference Go Version License

Official, dependency-free Go 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 panics, never returns a Go error — all errors come back as Success: false
  • ✅ No external dependencies — only the Go standard library
  • ✅ Go 1.21+

Installation

go get github.com/IPWhois/ipwhois-go

Free vs Paid plan

The same Client 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.New()              // Free plan — no API key
paid := ipwhois.New("YOUR_API_KEY") // Paid plan — with API key

Everything else (Lookup, options, error handling) is identical.

Quick start — Free plan (no API key)

package main

import (
    "fmt"

    "github.com/IPWhois/ipwhois-go"
)

func main() {
    client := ipwhois.New() // no API key

    info := client.Lookup("8.8.8.8")
    if !info.Success {
        fmt.Println("Lookup failed:", info.Message)
        return
    }

    fmt.Printf("%s %s\n", info.Country, info.Flag.Emoji)
    // → United States 🇺🇸

    fmt.Printf("%s, %s\n", 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 New:

package main

import (
    "fmt"

    "github.com/IPWhois/ipwhois-go"
)

func main() {
    client := ipwhois.New("YOUR_API_KEY") // with API key

    info := client.Lookup("8.8.8.8")
    if !info.Success {
        fmt.Println("Lookup failed:", info.Message)
        return
    }

    fmt.Printf("%s %s\n", info.Country, info.Flag.Emoji)
    // → United States 🇺🇸

    fmt.Printf("%s, %s\n", info.City, info.Region)
    // → Mountain View, California
}

ℹ️ Pass an empty string 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
Language string Free + Paid One of: en, ru, de, es, pt-BR, fr, zh-CN, ja
Fields []string Free + Paid Restrict the response to specific fields (e.g. []string{"country", "city"}). nil = inherit, []string{} = clear default whitelist
Rate *bool Basic and above Include the rate block (Limit, Remaining). nil = inherit, &true/&false = override
Security *bool Business and above Include the security block (proxy/vpn/tor/hosting). nil = inherit, &true/&false = override

Security and Rate are pointers, and Fields distinguishes nil from an empty slice, so per-call options can always override or reset client-wide defaults. Use the Bool helper to keep things tidy:

client := ipwhois.New("KEY").
    SetSecurity(true).                            // default: on for every call
    SetFields([]string{"country", "city"})        // default: only these two

client.Lookup("8.8.8.8")                                                       // security on, fields filtered
client.Lookup("1.1.1.1", ipwhois.LookupOptions{Security: ipwhois.Bool(false)}) // security explicitly off
client.Lookup("9.9.9.9", ipwhois.LookupOptions{Fields: []string{}})            // every field returned
Setting defaults once

Every option can be passed two ways: per call (as the second argument to Lookup / BulkLookup) 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 — SetLanguage, SetFields, SetSecurity, SetRate, SetTimeout, SetConnectTimeout, SetUserAgent, SetSSL, SetHTTPClient — and can be chained:

// Free plan
client := ipwhois.New().
    SetLanguage("en").
    SetFields([]string{"success", "country", "city", "flag.emoji"}).
    SetTimeout(8 * time.Second)
// Paid plan
client := ipwhois.New("YOUR_API_KEY").
    SetLanguage("en").
    SetFields([]string{"success", "country", "city", "flag.emoji"}).
    SetTimeout(8 * time.Second)

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=8s
client.Lookup("1.1.1.1", ipwhois.LookupOptions{Language: "de"}) // overrides lang for this single call only

⚠️ When you restrict fields with SetFields (or per-call LookupOptions.Fields), the API only returns the fields you ask for. Always include "success" in the list if you rely on info.Success for error checking — otherwise the field will be missing on responses.

ℹ️ SetSecurity(true) requires Business+ and SetRate(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), call SetSSL(false):

// Free plan
client := ipwhois.New().SetSSL(false)
// Paid plan
client := ipwhois.New("YOUR_API_KEY").SetSSL(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.New("YOUR_API_KEY")

results := client.BulkLookup([]string{
    "8.8.8.8",
    "1.1.1.1",
    "208.67.222.222",
    "2c0f:fb50:4003::", // IPv6 is fine — mix freely
})

if !results.Success {
    // Whole-batch failure — network down, bad API key, rate limit, …
    fmt.Println("bulk failed:", results.Message)
    return
}

for _, row := range results.Results {
    if !row.Success {
        // Per-IP errors (e.g. "Invalid IP address") are returned inline.
        // The rest of the batch is still usable.
        fmt.Printf("skip %s: %s\n", row.IP, row.Message)
        continue
    }
    fmt.Printf("%s → %s\n", row.IP, row.Country)
}

ℹ️ Bulk requires an API key. Calling BulkLookup without one will fail at the API level.

Error handling

The library never panics and never returns a Go error. Every failure — invalid IP, bad API key, rate limit, network outage, bad options — comes back inside the response with Success: false and a Message. Just check info.Success after every call:

info := client.Lookup("8.8.8.8")

if !info.Success {
    log.Printf("Lookup failed: %s", info.Message)
    return
}

fmt.Println(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 has Success: false, a human-readable Message, and an ErrorType so you can branch on the category of the failure. Some errors include extra fields you can branch on:

Field When it's present
Message Always — human-readable description of what went wrong
ErrorType Always — one of "api", "network", "environment", or "invalid_argument"
HTTPStatus On HTTP 4xx / 5xx responses
RetryAfter On HTTP 429 — free plan only (the paid endpoint does not send a Retry-After header)
info := client.Lookup("8.8.8.8")

if !info.Success {
    if info.HTTPStatus == 429 {
        time.Sleep(time.Duration(info.RetryAfter) * time.Second)
        // …retry
    }
    if info.ErrorType == ipwhois.ErrorTypeNetwork {
        // DNS failure, connection refused, timeout, …
    }
    log.Printf("Error: %s", info.Message)
    return
}

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
    }
}

The library exposes every documented field on the Response struct (Country, City, Flag.Emoji, Connection.ISP, …). The full raw body is always available in Response.Raw as []byte — useful as an escape hatch for fields not yet typed by this struct.

For the full field reference, see the official documentation.

An error response looks like:

{
    "success": false,
    "message": "Invalid IP address",
    "error_type": "api",       // 'api' / 'network' / 'environment' / 'invalid_argument'
    "http_status": 400         // present for HTTP 4xx / 5xx
    // "retry_after": 60       // additionally present on HTTP 429 — free plan only
}

Concurrency

A Client is safe for concurrent use by multiple goroutines once it has been fully configured (i.e. after the last Set*** call). Configure it during application startup, then share the same instance across handlers.

Custom HTTP client

For advanced cases — proxies, custom transports, mocking — you can supply your own *http.Client:

client := ipwhois.New("YOUR_API_KEY").
    SetHTTPClient(&http.Client{
        Timeout:   15 * time.Second,
        Transport: myCustomTransport,
    })

When set, the library defers entirely to your client for deadline management: SetTimeout and SetConnectTimeout are ignored, and the library does not add a context timeout of its own on top. If your custom client has no Timeout, the request has no client-side deadline.

Requirements

  • Go 1.21 or newer
  • No external dependencies

Contributing

Issues and pull requests are welcome on GitHub.

License

MIT © ipwhois.io

Documentation

Overview

Package ipwhois is the official Go client for the ipwhois.io IP Geolocation API.

Quick start

// Free plan (no API key, ~1 request/second per client IP).
client := ipwhois.New()
info := client.Lookup("8.8.8.8")

// Paid plan (with API key, higher limits, bulk, security data, ...).
client := ipwhois.New("YOUR_API_KEY")
info := client.Lookup("8.8.8.8", ipwhois.LookupOptions{
    Language: "en",
    Security: ipwhois.Bool(true),
})

// Bulk lookup — up to 100 IPs in one call (paid only).
bulk := client.BulkLookup([]string{"8.8.8.8", "1.1.1.1", "208.67.222.222"})

// HTTPS is enabled by default. Use SetSSL(false) to fall back to HTTP.

Error handling

The library never returns a Go error. All errors — invalid input, network failure, API-level errors (bad IP, bad key, rate limit, …) — are returned in the response with Success=false and a Message. Just check info.Success after every call.

Index

Constants

View Source
const (
	// HostFree is the free-plan endpoint host (used when no API key is provided).
	HostFree = "ipwho.is"

	// HostPaid is the paid-plan endpoint host (used when an API key is provided).
	HostPaid = "ipwhois.pro"
)

API endpoints.

View Source
const (
	// ErrorTypeAPI marks errors returned by the ipwhois.io API itself —
	// HTTP 4xx / 5xx responses, malformed JSON bodies, and HTTP 2xx
	// responses where the API set Success=false (e.g. "Invalid IP
	// address", "Reserved range").
	ErrorTypeAPI = "api"

	ErrorTypeNetwork         = "network"
	ErrorTypeEnvironment     = "environment"
	ErrorTypeInvalidArgument = "invalid_argument"
)

Error type values used in Response.ErrorType / BulkResponse.ErrorType.

View Source
const BulkLimit = 100

BulkLimit is the maximum number of IP addresses allowed in a single bulk request.

View Source
const Version = "1.2.0"

Version is the library version, used in the default User-Agent header.

Variables

View Source
var SupportedLanguages = []string{"en", "ru", "de", "es", "pt-BR", "fr", "zh-CN", "ja"}

SupportedLanguages lists languages supported by the Language option.

Functions

func Bool

func Bool(b bool) *bool

Bool returns a pointer to b. Useful for setting LookupOptions.Security or LookupOptions.Rate inline:

client.Lookup("8.8.8.8", ipwhois.LookupOptions{
    Security: ipwhois.Bool(true),
})

Types

type BulkResponse

type BulkResponse struct {
	// Success indicates whether the bulk request as a whole succeeded.
	// When true, iterate Results. When false, check Message.
	Success bool

	// Message contains a human-readable error description on whole-batch
	// failure, or is empty on success.
	Message string

	// ErrorType categorises the error. See Response.ErrorType for the
	// full list of values.
	ErrorType string

	// HTTPStatus is set on HTTP 4xx / 5xx whole-batch responses.
	HTTPStatus int

	// RetryAfter is set on HTTP 429 responses from the free-plan endpoint
	// (ipwho.is) when the API sent a Retry-After header. Value is in
	// seconds. The paid endpoint (ipwhois.pro) does not send the header,
	// so this field is not populated on paid-plan rate-limit responses.
	RetryAfter int

	// Results contains per-IP results. Populated only when Success is true.
	// Each entry has its own Success flag — check it before reading the
	// geolocation fields.
	Results []*Response

	// Raw is the unmodified response body.
	Raw []byte
}

BulkResponse is the result of a multi-IP lookup.

On whole-batch failure (network error, bad API key, rate limit, …) Success is false and Message describes the failure. Otherwise Results holds one entry per requested IP, each with its own Success flag — per-IP failures (e.g. "Invalid IP address") are returned inline and the rest of the batch remains usable.

type Client

type Client struct {
	// contains filtered or unexported fields
}

Client is a thin HTTP client for the ipwhois.io API.

Construct one with New() (free plan) or New("YOUR_API_KEY") (paid plan). All Set*** methods return the receiver to allow fluent chaining.

A Client is safe for concurrent use by multiple goroutines once configured (after the last setter call).

func New

func New(apiKey ...string) *Client

New creates a new Client.

Pass nothing for the free plan (no API key required), or pass your API key for the paid plan:

free := ipwhois.New()
paid := ipwhois.New("YOUR_API_KEY")

At most one API key may be passed; additional arguments are ignored.

func (*Client) BulkLookup

func (c *Client) BulkLookup(ips []string, opts ...LookupOptions) *BulkResponse

BulkLookup looks up information for multiple IP addresses in a single request.

Uses the GET / comma-separated form documented at https://ipwhois.io/documentation/bulk — up to BulkLimit (100) addresses per call. Each address counts as one credit.

Available on the Business and Unlimited plans only.

Per-IP errors are returned inline in BulkResponse.Results with their own Success=false flag; the rest of the batch is still usable. If the whole call fails (network error, bad API key, rate limit, …) BulkResponse.Success is false and BulkResponse.Results is nil.

The library never returns a Go error.

func (*Client) Lookup

func (c *Client) Lookup(ip string, opts ...LookupOptions) *Response

Lookup looks up information for a single IP address.

Pass an empty string to look up the caller's own public IP, as documented at https://ipwhois.io/documentation.

The library never returns a Go error — check Response.Success after every call. On any failure (API error, network error, missing extension, bad input) Success is false and Message describes the cause.

func (*Client) SetConnectTimeout

func (c *Client) SetConnectTimeout(d time.Duration) *Client

SetConnectTimeout sets the connection timeout (default: 5s).

Has no effect once SetHTTPClient has been called with a non-nil client.

func (*Client) SetFields

func (c *Client) SetFields(fields []string) *Client

SetFields restricts every response to a fixed set of fields by default, for example []string{"success", "country", "city", "flag.emoji"}.

Include "success" in the list if you rely on Response.Success for error checking — when Fields is set, the API only returns the fields you ask for.

func (*Client) SetHTTPClient

func (c *Client) SetHTTPClient(h *http.Client) *Client

SetHTTPClient installs a custom *http.Client for outgoing requests.

When non-nil, the provided client is used as-is and the Timeout / ConnectTimeout values configured via SetTimeout / SetConnectTimeout are ignored. Pass nil to revert to the built-in client.

func (*Client) SetLanguage

func (c *Client) SetLanguage(lang string) *Client

SetLanguage sets the default language used when none is supplied per call. Use one of the values listed in SupportedLanguages.

func (*Client) SetRate

func (c *Client) SetRate(enabled bool) *Client

SetRate enables or disables the rate block in responses by default. Per-call LookupOptions.Rate overrides this.

func (*Client) SetSSL

func (c *Client) SetSSL(enabled bool) *Client

SetSSL enables (default) or disables HTTPS for outgoing requests.

HTTPS is strongly recommended for production traffic — your API key is sent in the query string and would otherwise travel in clear text.

func (*Client) SetSecurity

func (c *Client) SetSecurity(enabled bool) *Client

SetSecurity enables or disables threat-detection data on every call by default. Per-call LookupOptions.Security overrides this.

func (*Client) SetTimeout

func (c *Client) SetTimeout(d time.Duration) *Client

SetTimeout sets the per-request total timeout (default: 10s).

Has no effect once SetHTTPClient has been called with a non-nil client.

func (*Client) SetUserAgent

func (c *Client) SetUserAgent(ua string) *Client

SetUserAgent overrides the User-Agent header sent with every request.

type Connection

type Connection struct {
	ASN    int    `json:"asn,omitempty"`
	Org    string `json:"org,omitempty"`
	ISP    string `json:"isp,omitempty"`
	Domain string `json:"domain,omitempty"`
}

Connection describes the network connection block.

type Currency

type Currency struct {
	Name         string  `json:"name,omitempty"`
	Code         string  `json:"code,omitempty"`
	Symbol       string  `json:"symbol,omitempty"`
	Plural       string  `json:"plural,omitempty"`
	ExchangeRate float64 `json:"exchange_rate,omitempty"`
}

Currency describes the currency block.

type Flag

type Flag struct {
	Img          string `json:"img,omitempty"`
	Emoji        string `json:"emoji,omitempty"`
	EmojiUnicode string `json:"emoji_unicode,omitempty"`
}

Flag describes the country flag block.

type LookupOptions

type LookupOptions struct {
	// Language restricts country/city/region names to a language.
	// One of: en, ru, de, es, pt-BR, fr, zh-CN, ja.
	Language string

	// Fields restricts the response to specific top-level or nested fields,
	// for example []string{"success", "country", "city", "flag.emoji"}.
	//
	// Include "success" in the list if you rely on Response.Success for
	// error checking — when Fields is set, the API only returns the fields
	// you ask for.
	//
	// nil = inherit client default; non-nil (including an empty slice)
	// overrides — pass []string{} to clear a default field whitelist
	// for a single call and get every field back.
	Fields []string

	// Security includes the security block (proxy/vpn/tor/hosting).
	// Available on Business and Unlimited plans.
	//
	// nil = inherit client default; &true / &false = override.
	Security *bool

	// Rate includes the rate block (limit/remaining).
	// Available on Basic and above.
	//
	// nil = inherit client default; &true / &false = override.
	Rate *bool
}

LookupOptions are per-call (or default) options for IP lookups.

Every field is optional. When passed per call, non-zero values override the corresponding client-wide default.

Security and Rate are *bool to allow three states: nil ("inherit the default"), &true ("force on"), &false ("force off"). Use the Bool helper for ergonomics:

opts := ipwhois.LookupOptions{Security: ipwhois.Bool(false)}

type Rate

type Rate struct {
	Limit     int `json:"limit,omitempty"`
	Remaining int `json:"remaining,omitempty"`
}

Rate describes the rate-limit block.

type Response

type Response struct {
	// Success indicates whether the lookup completed successfully.
	// On any failure this is false and Message describes the cause.
	Success bool `json:"success"`

	// Message contains a human-readable error description on failure,
	// or is empty on success.
	Message string `json:"message,omitempty"`

	// ErrorType categorises the error so callers can branch on the cause:
	//   - ErrorTypeAPI              — error returned by the ipwhois.io API
	//                                 (HTTP 4xx/5xx, malformed JSON, or a
	//                                 2xx body with success=false such as
	//                                 "Invalid IP address" / "Reserved range")
	//   - ErrorTypeNetwork          — DNS / connection / timeout
	//   - ErrorTypeEnvironment      — missing dependency or runtime error
	//   - ErrorTypeInvalidArgument  — bad option passed to the library
	ErrorType string `json:"error_type,omitempty"`

	// HTTPStatus is set on HTTP 4xx / 5xx responses from the API.
	HTTPStatus int `json:"http_status,omitempty"`

	// RetryAfter is set on HTTP 429 responses from the free-plan endpoint
	// (ipwho.is) when the API sent a Retry-After header. Value is in
	// seconds. The paid endpoint (ipwhois.pro) does not send the header,
	// so this field is not populated on paid-plan rate-limit responses.
	RetryAfter int `json:"retry_after,omitempty"`

	IP            string  `json:"ip,omitempty"`
	Type          string  `json:"type,omitempty"`
	Continent     string  `json:"continent,omitempty"`
	ContinentCode string  `json:"continent_code,omitempty"`
	Country       string  `json:"country,omitempty"`
	CountryCode   string  `json:"country_code,omitempty"`
	Region        string  `json:"region,omitempty"`
	RegionCode    string  `json:"region_code,omitempty"`
	City          string  `json:"city,omitempty"`
	Latitude      float64 `json:"latitude,omitempty"`
	Longitude     float64 `json:"longitude,omitempty"`
	IsEU          bool    `json:"is_eu,omitempty"`
	Postal        string  `json:"postal,omitempty"`
	CallingCode   string  `json:"calling_code,omitempty"`
	Capital       string  `json:"capital,omitempty"`
	Borders       string  `json:"borders,omitempty"`

	Flag       *Flag       `json:"flag,omitempty"`
	Connection *Connection `json:"connection,omitempty"`
	Timezone   *Timezone   `json:"timezone,omitempty"`
	Currency   *Currency   `json:"currency,omitempty"`
	Security   *Security   `json:"security,omitempty"`
	Rate       *Rate       `json:"rate,omitempty"`

	// Raw is the unmodified response body. Useful as an escape hatch for
	// fields not yet typed by this struct, e.g. when the API adds a new
	// field that hasn't made it into the Go struct definition yet.
	Raw []byte `json:"-"`
}

Response is the result of a single-IP lookup.

Always check Success first. On success, the typed fields below carry the API data. On failure, Message (and optionally ErrorType, HTTPStatus, RetryAfter) describe the error.

The library never panics and never returns a Go error — every failure (API error, network error, bad input, missing dependency) comes back as a Response with Success=false and a Message.

type Security

type Security struct {
	Anonymous bool `json:"anonymous,omitempty"`
	Proxy     bool `json:"proxy,omitempty"`
	VPN       bool `json:"vpn,omitempty"`
	Tor       bool `json:"tor,omitempty"`
	Hosting   bool `json:"hosting,omitempty"`
}

Security describes the threat-detection block (paid plan).

type Timezone

type Timezone struct {
	ID          string `json:"id,omitempty"`
	Abbr        string `json:"abbr,omitempty"`
	IsDST       bool   `json:"is_dst,omitempty"`
	Offset      int    `json:"offset,omitempty"`
	UTC         string `json:"utc,omitempty"`
	CurrentTime string `json:"current_time,omitempty"`
}

Timezone describes the timezone block.

Directories

Path Synopsis
examples
basic command
bulk command
defaults command

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL