Skip to content

IPWhois/ipwhois-go

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

5 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

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
Success Always — false for error responses (true for successful responses)
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": "Rate limit exceeded",
    "error_type": "api",       // 'api' / 'network' / 'environment' / 'invalid_argument'
    "http_status": 429,         // 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

Packages

 
 
 

Contributors

Languages