Go SDK

ip-api.io Go SDK
go get github.com/ip-api-io/ipapi-go

ipapi-go is the official, zero-dependency client for Go — standard library only and fully context-aware. One typed client wraps IP geolocation, VPN/proxy/Tor detection, email validation, and fraud risk scoring, with typed error values and batch helpers, so you write the call, not the plumbing.

Location
Mountain View, US
Threat signals
No VPN or proxy
Dependencies
Zero — stdlib only
client.LookupIP(ctx, "8.8.8.8")
{
"ip": "8.8.8.8",
"isp": "Google LLC",
"location": {
"country": "United States",
"city": "Mountain View",
"timezone": "America/Los_Angeles"
},
"suspicious_factors": {
"is_vpn": false,
"is_datacenter": true
}
}

What you'll build

  • Install and construct the typed, context-aware client
  • Look up any IP — or the caller's IP — with one call
  • Detect VPN, proxy, Tor, and threats; batch up to 100 IPs
  • Handle rate limits and errors the production-ready way
Trusted by thousands of businesses
Fast JSON API responses
Real-time validation
Simple integration, SDKs & examples

Prerequisites

  • Go 1.21 or higher
  • An ip-api.io API key — get one free
  • No other dependencies — the client is standard library only
1

Install the module

Add the official ipapi-go module to your project. It has zero dependencies — nothing to pull in beyond the standard library.

go get github.com/ip-api-io/ipapi-go
2

Construct the client

Import the package and build one client with your API key. Read the key from an environment variable — never hardcode it. The API rejects keyless requests with 401.

package main

import (
	"context"
	"os"

	ipapi "github.com/ip-api-io/ipapi-go"
)

func main() {
	client := ipapi.NewClient(ipapi.WithAPIKey(os.Getenv("IP_API_IO_KEY")))
	ctx := context.Background()
	_ = client
	_ = ctx
}

Every method takes a context.Context first, so you can attach per-request timeouts, deadlines, and cancellation. Reuse a single client across goroutines — it is safe for concurrent use.

3

Look up an IP address

Call LookupIP(ctx, ip) for any IPv4 or IPv6 address, or Lookup(ctx) to resolve the caller's own IP. Responses are typed structs; nullable fields are pointers — guard with a nil check before dereferencing.

info, err := client.LookupIP(ctx, "8.8.8.8")
if err != nil {
	log.Fatal(err)
}

fmt.Println(info.IP)                              // "8.8.8.8"
if info.ISP != nil {
	fmt.Println(*info.ISP)                       // "Google LLC"
}
if info.Location.Country != nil {
	fmt.Println(*info.Location.Country)          // "United States"
}
fmt.Println(info.SuspiciousFactors.IsDatacenter) // true

// Resolve the caller's own IP:
me, _ := client.Lookup(ctx)
fmt.Println(me.IP)
Pointers for nullable fields: location fields like Country and City are *string because they're absent for private ranges or unrecognized addresses. Boolean flags such as info.SuspiciousFactors.IsVPN are plain bool values.
4

Detect VPN, proxy, Tor & batch lookups

The SuspiciousFactors struct carries seven boolean flags on every lookup — this is what sets ip-api.io apart from plain geolocation APIs. You get security intelligence in the same call, at no extra cost.

info, _ := client.LookupIP(ctx, "185.220.101.45") // example Tor exit node
f := info.SuspiciousFactors

if f.IsVPN || f.IsProxy || f.IsTorNode {
	fmt.Println("Anonymized traffic — consider a CAPTCHA or block")
}
if f.IsDatacenter {
	fmt.Println("Cloud/datacenter IP — likely automated")
}
if f.IsThreat {
	fmt.Println("Active threat signal — block this request")
}

isRisky := f.IsVPN || f.IsProxy || f.IsTorNode || f.IsThreat

Need to check many IPs at once? LookupBatch takes up to 100 addresses in a single request (it returns an error if the slice is empty or longer than 100):

batch, err := client.LookupBatch(ctx, []string{"8.8.8.8", "1.1.1.1", "9.9.9.9"})
if err != nil {
	log.Fatal(err)
}

fmt.Println(batch.TotalProcessed, batch.SuccessfulLookups)

for ip, info := range batch.Results {
	fmt.Println(ip, info.SuspiciousFactors.IsVPN)
}
Tip: want a single block / review / allow decision instead of raw flags? Use client.RiskScoreIP(ctx, ip) for a combined fraud risk score from 0 to 100.
5

Production-ready error handling

Every method returns (*T, error). Use errors.As to inspect the typed error — the client never retries on its own, so on a rate limit RateLimitError.Reset tells you exactly when to try again.

import "errors"

info, err := client.LookupIP(ctx, "8.8.8.8")
if err != nil {
	var rateErr *ipapi.RateLimitError
	var authErr *ipapi.AuthenticationError
	var invErr *ipapi.InvalidRequestError
	var srvErr *ipapi.ServerError
	switch {
	case errors.As(err, &rateErr):
		fmt.Printf("Rate limited. Remaining: %d. Resets at %d\n", rateErr.Remaining, rateErr.Reset)
	case errors.As(err, &authErr):
		fmt.Println("Invalid or missing API key — get one free at https://ip-api.io")
	case errors.As(err, &invErr):
		fmt.Println("Bad request:", invErr.Message)
	case errors.As(err, &srvErr):
		fmt.Println("ip-api.io is having issues — try again later")
	default:
		fmt.Println("transport / decode error:", err)
	}
	return
}
_ = info
No automatic retries: wrap calls in your own retry/backoff logic and use RateLimitError.Reset (a Unix timestamp) to schedule the next attempt. Transport failures (DNS, connect, timeout, decode) are wrapped standard errors and fall through to the default branch — they are not an *APIError.

In an HTTP server, run the lookup server-side and pass the real client IP from the X-Forwarded-For header:

func checkHandler(client *ipapi.Client) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		clientIP := strings.TrimSpace(strings.Split(r.Header.Get("X-Forwarded-For"), ",")[0])
		if clientIP == "" {
			clientIP, _, _ = net.SplitHostPort(r.RemoteAddr)
		}

		info, err := client.LookupIP(r.Context(), clientIP)
		if err != nil {
			// Treat ip-api.io as a non-critical dependency
			json.NewEncoder(w).Encode(map[string]any{"ip": clientIP, "country": nil})
			return
		}
		json.NewEncoder(w).Encode(map[string]any{
			"ip":        info.IP,
			"country":   info.Location.Country,
			"is_vpn":    info.SuspiciousFactors.IsVPN,
			"is_threat": info.SuspiciousFactors.IsThreat,
		})
	}
}
Reference

Response & method reference

LookupIP() returns a typed *IPInfo with three parts: the top-level IP/ISP/ASN, a SuspiciousFactors struct, and a Location struct. Nullable fields are pointers (e.g. *string) — nil for private ranges or unrecognized addresses.

client .LookupIP(ctx, "78.55.53.58")
{
  "ip": "78.55.53.58",
  "isp": "Deutsche Telekom AG",
  "asn": "AS3320",
  "suspicious_factors": {
    "is_proxy":      false,
    "is_tor_node":   false,
    "is_spam":       false,
    "is_crawler":    false,
    "is_datacenter": false,
    "is_vpn":        false,
    "is_threat":     false
  },
  "location": {
    "country":           "Germany",
    "country_code":      "DE",
    "city":              "Berlin",
    "latitude":          52.5694,
    "longitude":         13.3753,
    "zip":               "13409",
    "timezone":          "Europe/Berlin",
    "local_time":        "2024-05-20T22:16:52+02:00",
    "local_time_unix":   1716236212,
    "is_daylight_savings": true
  }
}

SuspiciousFactors — security signals (all bool)

FieldDescription
IsVPNVPN service, corporate gateway, or self-hosted VPN detected
IsProxyHTTP, HTTPS, or SOCKS proxy (~99.5% accuracy)
IsTorNodeTor exit node, relay, or bridge (updated in real time)
IsDatacenterCloud provider (AWS, GCP, Azure), VPS, or hosting facility
IsThreatActive security threat — malware C&C, botnet, or DDoS source
IsSpamAssociated with spam, phishing, or malware email campaigns
IsCrawlerKnown web crawler, scraper, or bot

Location — geographic and timezone data

FieldTypeDescription
Country*stringFull country name in English (ISO 3166-1)
CountryCode*stringISO 3166-1 alpha-2 code (e.g. "DE", "US")
City*stringCity or municipality name (85–95% accuracy)
Latitude*float64Decimal degrees, WGS84 — ~50 km median accuracy radius
Longitude*float64Decimal degrees, WGS84 — ~50 km median accuracy radius
Zip*stringPostal or ZIP code in country-specific format
Timezone*stringIANA timezone identifier (e.g. "America/Los_Angeles")
LocalTime*stringCurrent local time in ISO 8601 with UTC offset
LocalTimeUnix*int64Unix timestamp (seconds) in local timezone
IsDaylightSavings*boolTrue if currently observing DST; nil if not applicable

Client methods

MethodReturnsWhat it does
Lookup(ctx) / LookupIP(ctx, ip)*IPInfoGeolocate the caller's IP, or a specific IP
LookupBatch(ctx, ips)*BatchIPLookupResponseGeolocate up to 100 IPs in one call
RiskScore(ctx) / RiskScoreIP(ctx, ip)*RiskScoreFraud risk score (0–100) for an IP
TorCheck(ctx, ip)*TorDetectionCheck whether an IP is a Tor node
EmailInfo(ctx, email)*EmailInfoBasic email validation (syntax, MX, disposable)
ValidateEmail(ctx, email)*AdvancedEmailValidationFull validation incl. SMTP deliverability
ASN(ctx, ip)*ASNLookupAutonomous System Number lookup
Whois(ctx, domain)*WhoisWHOIS domain registration info
DomainAge(ctx, domain)*DomainAgeDomain registration date and age in days
RateLimit(ctx)*RateLimitInfoRemaining quota and plan limits
Full surface: the client also exposes ValidateEmailBatch, EmailRiskScore, IPReputation, ReverseDNS, ForwardDNS, MXRecords, DomainAgeBatch, and UsageSummary. See the README and per-feature guides.
FAQ

Frequently asked questions

Common questions about the ip-api.io Go SDK.

Is the ipapi-go module official?

Yes. github.com/ip-api-io/ipapi-go is the official Go client for ip-api.io, maintained by the ip-api.io team and published from github.com/ip-api-io/ipapi-go. It is MIT licensed and not affiliated with ip-api.com or ipapi.com.

How do I install the ip-api.io Go SDK?

Run go get github.com/ip-api-io/ipapi-go, then import and construct a client:

// go get github.com/ip-api-io/ipapi-go
import ipapi "github.com/ip-api-io/ipapi-go"

client := ipapi.NewClient(ipapi.WithAPIKey(os.Getenv("IP_API_IO_KEY")))

It has zero dependencies — standard library only — and works on Go 1.21+.

Is the client context-aware?

Yes. Every method takes a context.Context as its first argument, so you can apply per-request timeouts, deadlines, and cancellation — e.g. client.LookupIP(ctx, ip). Pass context.Background() when you have no parent context.

How are nullable fields represented?

Responses are typed structs. Nullable fields are pointers (e.g. *string) — guard with a nil check before dereferencing, like if info.Location.Country != nil { … *info.Location.Country }. Boolean flags such as info.SuspiciousFactors.IsVPN are plain values.

How do I handle rate limits?

Every method returns (*T, error). Use errors.As to match a *RateLimitError, which exposes Limit, Remaining, and Reset parsed from the response headers. The client never retries automatically — schedule your retry after Reset.

Pricing

Pricing

Start using IP-API.io to make your website safer and more user-friendly. Keep out unwanted bots, show visitors content that's relevant to where they are, and spot risky IP addresses quickly. It's perfect for making online shopping more personal and keeping your site secure. Get started today with one of the plans!

Small
€10 /mo
100,000 geo ip requests
10,000 advanced email validation requests
Location data
Email validation
Risk score calculation
Currency data
Time zone data
Threat data
Unlimited support
HTTPS encryption
Create Account to Subscribe
Medium
€20 /mo
300,000 geo ip requests
25,000 advanced email validation requests
Location data
Email validation
Risk score calculation
Currency data
Time zone data
Threat data
Unlimited support
HTTPS encryption
Create Account to Subscribe

Need support?

Explore how IP-API.io can enhance your security, provide robust bot protection, and improve IP geolocation accuracy for your applications.

Contact Support

Found a bug in the SDK?

The ipapi-go module is open source. Open an issue or pull request on GitHub and we'll take a look.

Open on GitHub