Swift SDK

ip-api.io Swift SDK
import IpApiIo

ipapi-swift is the official async client for Swift — built on URLSession and Codable with zero dependencies. One IpApiClient wraps IP geolocation, VPN/proxy/Tor detection, email validation, and fraud risk scoring with async/await, a typed IpApiError enum, and batch helpers, so you write the call, not the plumbing.

Location
Mountain View, US
Threat signals
No VPN or proxy
Runtime
async / await
try await client.lookup(ip: "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

  • Add the package and construct one async client
  • Look up any IP — or the caller's IP — with one call
  • Detect VPN, proxy, Tor, and threats; batch up to 100 IPs
  • Catch the typed IpApiError enum the production-ready way
Trusted by thousands of businesses
Fast JSON API responses
Real-time validation
Simple integration, SDKs & examples

Prerequisites

  • Swift 5.9 or later (Xcode 15+, or the Swift toolchain on Linux)
  • A Swift package or app target to add the dependency to
  • An ip-api.io API key — get one free
1

Add the package

Add the official ipapi-swift package via Swift Package Manager — in Xcode (File → Add Packages) or in your Package.swift. It has zero dependencies.

// Package.swift
dependencies: [
    .package(url: "https://github.com/ip-api-io/ipapi-swift.git", from: "1.0.0")
],
targets: [
    .target(
        name: "YourApp",
        dependencies: [.product(name: "IpApiIo", package: "ipapi-swift")]
    )
]
2

Construct the client

Import the module and build one IpApiClient with your API key. Read the key from the environment — never hardcode it. The API rejects keyless requests with 401.

import IpApiIo

let apiKey = ProcessInfo.processInfo.environment["IP_API_IO_KEY"]!
let client = IpApiClient(apiKey: apiKey)

// ... use the client ...

IpApiClient is backed by a shared URLSession — construct one and reuse it across your app instead of creating a new client per request.

3

Look up an IP address

Call lookup(ip:) for any IPv4 or IPv6 address, or lookup() to resolve the caller's own IP. Responses are Codable structs; nullable fields are optionals — unwrap before use.

let info = try await client.lookup(ip: "8.8.8.8")

print(info.ip)                              // "8.8.8.8"
print(info.isp ?? "")                       // "Google LLC"
print(info.location?.country ?? "")         // "United States"
print(info.suspiciousFactors.isDatacenter)  // true

// Resolve the caller's own IP:
let me = try await client.lookup()
print(me.ip)
Optionals for nullable fields: location fields like country and city are optionals because they're absent for private ranges or unrecognized addresses — use if let or ??. Boolean flags such as info.suspiciousFactors.isVpn are plain Bool.
4

Detect VPN, proxy, Tor & batch lookups

The suspiciousFactors struct carries seven boolean fields 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.

let info = try await client.lookup(ip: "185.220.101.45") // example Tor exit node
let f = info.suspiciousFactors

if f.isVpn || f.isProxy || f.isTorNode {
    print("Anonymized traffic — consider a CAPTCHA or block")
}
if f.isDatacenter {
    print("Cloud/datacenter IP — likely automated")
}
if f.isThreat {
    print("Active threat signal — block this request")
}

let risky = f.isVpn || f.isProxy || f.isTorNode || f.isThreat

Need to check many IPs at once? lookupBatch(ips:) takes up to 100 addresses in a single request:

let batch = try await client.lookupBatch(ips: ["8.8.8.8", "1.1.1.1", "9.9.9.9"])

print(batch.totalProcessed, batch.successfulLookups)

for (ip, info) in batch.results {
    print(ip, info.suspiciousFactors.isVpn)
}
Tip: want a single block / review / allow decision instead of raw flags? Use try await client.riskScore(ip: ip) for a combined fraud risk score from 0 to 100.
5

Production-ready error handling

Methods throw IpApiError. switch over the cases — the client never retries on its own, so on a rate limit the .rateLimit case's reset tells you exactly when to try again.

do {
    let info = try await client.lookup(ip: "8.8.8.8")
    print(info.ip)
} catch let error as IpApiError {
    switch error {
    case .rateLimit(let limit, let remaining, let reset):
        print("Rate limited. \(remaining)/\(limit). Resets at \(reset)")
    case .authentication:
        print("Invalid or missing API key — get one free at https://ip-api.io")
    case .invalidRequest(let message):
        print("Bad request: \(message)")
    case .server(let status):
        print("ip-api.io is having issues (status \(status)) — try again later")
    case .transport(let underlying):
        print("transport / decode error: \(underlying)")
    }
}
No automatic retries: wrap calls in your own retry/backoff logic and use the .rateLimit case's reset (a Unix timestamp) to schedule the next attempt. Transport failures (DNS, connect, timeout, decode) surface as .transport rather than an API status error.

In a Vapor route, run the lookup server-side and pass the real client IP from the X-Forwarded-For header:

app.get("check") { req async -> [String: String?] in
    let clientIP = req.headers.first(name: "X-Forwarded-For")?
        .split(separator: ",").first
        .map { $0.trimmingCharacters(in: .whitespaces) }
        ?? req.remoteAddress?.ipAddress
        ?? "0.0.0.0"

    do {
        let info = try await client.lookup(ip: clientIP)
        return [
            "ip": info.ip,
            "country": info.location?.country,
            "isVpn": String(info.suspiciousFactors.isVpn)
        ]
    } catch {
        // Treat ip-api.io as a non-critical dependency
        return ["ip": clientIP, "country": nil]
    }
}
Reference

Response & method reference

lookup(ip:) resolves to a typed IpInfo with three parts: the top-level ip/isp/asn, a suspiciousFactors struct, and an optional location struct. Nullable fields are Swift optionals — nil for private ranges or unrecognized addresses.

try await client .lookup(ip: "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)

PropertyDescription
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

PropertyTypeDescription
countryString?Full country name in English (ISO 3166-1)
countryCodeString?ISO 3166-1 alpha-2 code (e.g. "DE", "US")
cityString?City or municipality name (85–95% accuracy)
latitudeDouble?Decimal degrees, WGS84 — ~50 km median accuracy radius
longitudeDouble?Decimal degrees, WGS84 — ~50 km median accuracy radius
zipString?Postal or ZIP code in country-specific format
timezoneString?IANA timezone identifier (e.g. "America/Los_Angeles")
localTimeString?Current local time in ISO 8601 with UTC offset
localTimeUnixInt?Unix timestamp (seconds) in local timezone
isDaylightSavingsBool?True if currently observing DST; nil if not applicable

Client methods

MethodReturnsWhat it does
lookup() / lookup(ip:)IpInfoGeolocate the caller's IP, or a specific IP
lookupBatch(ips:)BatchIpLookupGeolocate up to 100 IPs in one call
riskScore() / riskScore(ip:)RiskScoreFraud risk score (0–100) for an IP
torCheck(ip:)TorDetectionCheck whether an IP is a Tor node
emailInfo(email:)EmailInfoBasic email validation (syntax, MX, disposable)
validateEmail(email:)AdvancedEmailValidationFull validation incl. SMTP deliverability
asn(ip:)AsnLookupAutonomous System Number lookup
whois(domain:)WhoisWHOIS domain registration info
domainAge(domain:)DomainAgeDomain registration date and age in days
rateLimit()RateLimitInfoRemaining quota and plan limits
Full surface: the client also exposes validateEmailBatch(emails:), emailRiskScore(email:), ipReputation(ip:), reverseDns(ip:), forwardDns(hostname:), mxRecords(domain:), domainAgeBatch(domains:), and usageSummary(). See the README and per-feature guides.
FAQ

Frequently asked questions

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

Is the ipapi-swift package official?

Yes. ipapi-swift is the official Swift client for ip-api.io, maintained by the ip-api.io team and distributed via Swift Package Manager from github.com/ip-api-io/ipapi-swift. It is MIT licensed and not affiliated with ip-api.com or ipapi.com.

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

Add the package to your Package.swift dependencies (or via Xcode → Add Package), then import IpApiIo and construct the client:

// .package(url: "https://github.com/ip-api-io/ipapi-swift.git", from: "1.0.0")
import IpApiIo

let client = IpApiClient(apiKey: ProcessInfo.processInfo.environment["IP_API_IO_KEY"]!)

It supports Swift 5.9+ on macOS, iOS, tvOS, watchOS and Linux, with zero dependencies — built on URLSession and Codable.

Is the client async?

Yes. Every method is async and throws, so you try await each call from an async context or a Task. IpApiClient is a value type backed by a shared URLSession — construct one and reuse it.

How are nullable fields represented?

Responses are Codable structs. Nullable fields are Swift optionals — unwrap with if let or the nil-coalescing operator, e.g. info.location?.country ?? "Unknown". Boolean flags such as info.suspiciousFactors.isVpn are plain Bool.

How do I handle rate limits?

Methods throw IpApiError. catch the .rateLimit case, which carries limit, remaining, and reset 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-swift package is open source. Open an issue or pull request on GitHub and we'll take a look.

Open on GitHub