Go SDK

github.com/netloc8/netloc8-go — Zero-dependency, context-native IP geolocation and account management client for Go.

Installation

Bash
go get github.com/netloc8/netloc8-go

Requires Go 1.22+. Zero external dependencies — only the Go standard library.

Quick Start

go
package main

import (
    "context"
    "fmt"
    "log"

    "github.com/netloc8/netloc8-go"
)

func main() {
    client := netloc8.NewClient( "sk_your_secret_key" )

    geo, err := client.LookupIP( context.Background(), "8.8.8.8" )
    if err != nil {
        log.Fatal( err )
    }

    fmt.Println( geo.CountryCode() )  // "US"
    fmt.Println( geo.CityName() )     // "Mountain View"
    fmt.Println( geo.TZ() )           // "America/Los_Angeles"
    fmt.Println( geo.ASN() )          // "AS15169"
    fmt.Println( geo.Org() )          // "Google LLC"
}

Geolocation Methods

MethodReturnsDescription
LookupIP( ctx, ip )*Geo, errorFull geolocation for a specific IPv4 or IPv6 address
LookupMe( ctx )*Geo, errorFull geolocation for the caller's own IP
Timezone( ctx, ip )string, errorIANA timezone string only (lightweight)
MyTimezone( ctx )string, errorIANA timezone for the caller's own IP
Validate( ctx, ip )bool, errorCheck if a string is a valid IP address (via API)
go
// Lightweight timezone lookup (no full geo response)
tz, err := client.Timezone( ctx, "8.8.8.8" )
// tz == "America/Los_Angeles"

// Validate before looking up
valid, err := client.Validate( ctx, userInput )
if valid {
    geo, err := client.LookupIP( ctx, userInput )
}

Account Management

Manage API keys, usage, audit logs, and allowed origins. Requires a secret key (sk_).

MethodDescription
GetProfile( ctx )Fetch the authenticated user's profile (ID, email, name)
ListKeys( ctx )List all API keys (metadata only — raw values never returned)
CreateKey( ctx, name, ...opts )Create a new key — raw value returned once
DeleteKey( ctx, keyID )Permanently revoke an API key
RenewKey( ctx, keyID )Extend a key's expiration
GetUsage( ctx )Usage stats for the current billing period
GetAuditLog( ctx, ...opts )Paginated audit log with optional filters
ListSites( ctx )List allowed origins for publishable keys
CreateSite( ctx, origin )Add an allowed origin
DeleteSite( ctx, siteID )Remove an allowed origin
go
client := netloc8.NewClient( "sk_your_secret_key" )

// Create a new API key
created, err := client.CreateKey( ctx, "CI Pipeline",
    netloc8.WithKeyType( "secret" ),
)
fmt.Println( created.RawKey )  // store this — only shown once

// Check usage
usage, err := client.GetUsage( ctx )
fmt.Printf( "%d / %d requests (%s)\n", usage.Total, usage.Cap, usage.Period )

// Query audit log with filters
log, err := client.GetAuditLog( ctx,
    netloc8.WithLimit( 25 ),
    netloc8.WithAction( "key.created" ),
)
for _, entry := range log.Entries {
    fmt.Printf( "%s: %s by %s\n", entry.CreatedAt, entry.Action, entry.ActorLabel )
}

Functional options

CreateKey accepts WithKeyType( "secret" | "publishable" ). GetAuditLog accepts WithLimit, WithOffset, and WithAction.

LookupMe — Discover Exit IP

LookupMe calls GET /v1/ip/me to geolocate the IP the API sees. With a proxy transport, this discovers the exit IP of the proxy:

go
import (
    "net/http"
    "net/url"

    "github.com/netloc8/netloc8-go"
)

proxyURL, _ := url.Parse( "socks5://proxy.example.com:1080" )
transport := &http.Transport{ Proxy: http.ProxyURL( proxyURL ) }

client := netloc8.NewClient( "sk_your_key",
    netloc8.WithHTTPClient( &http.Client{ Transport: transport } ),
)

geo, err := client.LookupMe( ctx )
if err != nil {
    log.Fatal( err )
}
fmt.Println( geo.IP() )           // the proxy's exit IP
fmt.Println( geo.CountryCode() )  // country of the exit node

Nil-Safe Accessors

All accessor methods on *Geo are nil-safe — they never panic, even on partial or empty responses. Returns the zero value when the underlying field is absent.

AccessorReturnsExample
geo.IP()string"8.8.8.8"
geo.CountryCode()string"US"
geo.CountryName()string"United States"
geo.RegionCode()string"CA"
geo.RegionName()string"California"
geo.CityName()string"Mountain View"
geo.TZ()string"America/Los_Angeles"
geo.Lat()float6437.386
geo.Lng()float64-122.084
geo.HasCoordinates()booltrue — distinguishes absent from 0,0
geo.ASN()string"AS15169"
geo.Org()string"Google LLC"

You can also access the full struct directly: geo.Location.Country.Unions, geo.Location.District, geo.Meta.Precision, etc.

Helper Functions

FunctionDescription
netloc8.IsEU( geo )Check if geolocation is in the EU (checks unions for "EU")
netloc8.IsPublicIP( ip )Check whether an IP is publicly routable (IPv4 and IPv6)
netloc8.GetClientIP( r )Extract the real client IP from HTTP request headers
netloc8.NormalizeIP( ip )Strip IPv4-mapped prefix, brackets, whitespace, lowercase
netloc8.Subnet( ip )Derive the /24 CIDR prefix from an IPv4 address
netloc8.IsIPv4( ip ) / IsIPv6( ip )IP version detection
netloc8.ParseIP( ip )Validate and normalize an IP string
go
// EU compliance check
if netloc8.IsEU( geo ) {
    // show cookie consent banner, apply GDPR rules, etc.
}

// Extract client IP behind proxies
ip := netloc8.GetClientIP( r )  // checks X-Forwarded-For, CF-Connecting-IP, etc.

// Derive subnet for IP rotation
subnet := netloc8.Subnet( geo.IP() )  // "203.0.113.0/24"

GetClientIP header priority

GetClientIP checks headers in order: X-Forwarded-For (first public IP), CF-Connecting-IP, True-Client-IP, X-Real-IP, X-Client-IP, Fastly-Client-IP, Fly-Client-IP.

Error Handling

API errors are returned as *netloc8.APIError with machine-readable codes. Use errors.As for typed error handling:

go
import "errors"

geo, err := client.LookupIP( ctx, "not-an-ip" )
if err != nil {
    var apiErr *netloc8.APIError
    if errors.As( err, &apiErr ) {
        fmt.Println( apiErr.Code )       // "INVALID_IP"
        fmt.Println( apiErr.Message )    // "Invalid IP address format"
        fmt.Println( apiErr.Status )     // 400
        fmt.Println( apiErr.RequestID )  // "550e8400-..."
    }
}

Convenience predicates (work with wrapped errors via errors.As):

go
netloc8.IsNotFound( err )    // 404 — IP not in database
netloc8.IsRateLimited( err ) // 429 — rate limit exceeded
netloc8.IsForbidden( err )   // 403 — invalid key or origin mismatch

Configuration Options

OptionDefaultDescription
WithBaseURL( url )https://api.netloc8.comOverride the API base URL (for proxies or self-hosted)
WithHTTPClient( hc )http.DefaultClientCustom http.Client for proxy transports, custom TLS, etc.
WithTimeout( d )10sRequest timeout (safe to use with WithHTTPClient in any order)
WithOrigin( origin )Origin header for publishable key (pk_) authentication
WithUserAgent( ua )Custom User-Agent string (SDK version is appended)
go
client := netloc8.NewClient( "sk_your_key",
    netloc8.WithTimeout( 5 * time.Second ),
    netloc8.WithBaseURL( "https://geo-proxy.internal" ),
    netloc8.WithHTTPClient( &http.Client{
        Transport: &http.Transport{
            Proxy: http.ProxyURL( proxyURL ),
        },
    }),
)

Concurrency safe

Client is safe for concurrent use across goroutines. Create one client and reuse it for the lifetime of your application.