Pricing Docs Contact Status Blog
Log in Get Free API Key →

Python SDK

A dependency-free Python client for the IPFly API — caching, retries, rate limiting, thread-safe de-duplication and batch lookups, built in.

Get started → API Reference

Overview

The IPFly Python SDK wraps the IPFly API in a single dependency-free module. It relies only on the standard library (urllib, threading, ipaddress, json) and handles the operational details — caching, retries, rate limiting, and thread-safe request de-duplication — so your integration code can stay focused on what to do with the response.

TTL + LRU caching

In-memory by default, or persisted to a JSON file across process runs for CLI scripts and cron jobs.

Automatic retries

Exponential backoff with jitter on network errors and 5xx responses. 4xx fails fast, no wasted retries.

Thread-safe de-duplication

Two threads looking up the same IP at the same moment share a single HTTP request.

Batch lookups

A bounded ThreadPoolExecutor looks up many IPs at once; a bad IP never fails the whole batch.

Client-side rate limiting

A token-bucket limiter smooths bursts so you don't blow through your plan's requests/second.

Zero dependencies

One file, standard library only. Python 3.7+. MIT licensed.

Where This SDK Belongs

This client is meant to run server-side — in a script, a web backend, a worker, a notebook. It is not meant to be shipped inside a distributed application (a desktop app, a compiled mobile app, anything an end user's machine executes) where your API token could be extracted from the binary.

If you need geolocation results in a browser-facing app, put this SDK behind your own backend endpoint and call that endpoint from the client (see the Flask proxy example below), or use the IPFly JavaScript SDK with a domain-restricted token for prototyping.

Installation

No PyPI package is published — copy ipfly_sdk.py into your project, or host it internally and pull it in your build step.

bash
# Drop ipfly_sdk.py directly into your source tree, e.g.:
cp ipfly_sdk.py my_project/vendor/ipfly_sdk.py
bash
curl -O https://ipfly.world/libs/python-sdk/ipfly_sdk.py
# or
wget https://ipfly.world/libs/python-sdk/ipfly_sdk.py
python
from ipfly_sdk import IPFlyClient, IPFlyError, is_valid_ip

client = IPFlyClient(token="YOUR_TOKEN")

Quick Start

Create a client once, then call lookup() wherever you need geolocation data.

python
from ipfly_sdk import IPFlyClient, IPFlyError

client = IPFlyClient(
    token="YOUR_TOKEN",
    include="security",  # optional: request the security/ASN field set on every call
)

# Look up a specific IP
data = client.lookup("8.8.8.8")
print(data["city"], data["country_name"], data["security"]["is_vpn"])

# Look up the caller's own IP (omit the ip argument)
me = client.lookup_self()
print("You appear to be in", me["city"])

# Handle errors explicitly
try:
    client.lookup("not-an-ip")
except IPFlyError as err:
    print(err.code, err.message)

# Use as a context manager to clean up cache/state automatically
with IPFlyClient(token="YOUR_TOKEN") as c:
    print(c.lookup("1.1.1.1")["country_name"])

Client Parameters

All parameters are passed to IPFlyClient(...).

ParameterTypeDefaultDescription
tokenstrrequiredYour IPFly API token.
base_urlstripfly.world/apiOverride to point at your own backend proxy.
includestrNoneDefault include value sent with every request (e.g. "security"). Overridable per call.
timeoutfloat8.0Per-request timeout in seconds.
retriesint2Retry attempts for network errors and 5xx. 4xx is never retried.
retry_delayfloat0.3Base delay (seconds) for exponential backoff; jitter is added automatically.
rate_limitfloat0Max requests/second this client will issue. 0 = unlimited.
concurrencyint6Default max worker threads for batch_lookup.
cachedictsee belowResponse cache settings — see Cache Options.
openerOpenerDirectorNoneCustom urllib.request opener — for corporate proxies, custom SSL contexts, etc.
debugboolFalsePrint internal lifecycle events to stdout (tokens are redacted).
on_requestcallableNone(ip, url) -> None — fired right before a network request is sent.
on_responsecallableNone(ip, data, from_cache) -> None — fired after a successful lookup.
on_errorcallableNone(ip, error) -> None — fired when a lookup ultimately fails, after retries.
on_cache_hitcallableNone(ip, data) -> None — fired when a cached response is served instead of a network call.

Cache Options

Passed as the cache dict, e.g. IPFlyClient(token=..., cache={"storage": "file", "ttl": 3600}).

KeyTypeDefaultDescription
enabledboolTrueSet False to disable caching entirely.
ttlfloat300How long a cached response stays valid, in seconds.
max_sizeint500Max cached entries before least-recently-used entries are evicted.
storagestr'memory''memory' or 'file'. 'file' persists the cache to a JSON file across process runs — good for CLI scripts and cron jobs, not built for high write concurrency.
pathstr.ipfly_cache.jsonFile path used when storage='file'.

.lookup() & .lookup_self()

client.lookup(ip=None, include=None, skip_cache=False, timeout=None) looks up a single IP. Omit ip (or pass None) to geolocate the caller — or use the client.lookup_self(**kwargs) shorthand.

python
client.lookup(
    "1.1.1.1",
    include="security",  # overrides the client-level `include` for this call only
    skip_cache=True,       # force a fresh network request, bypassing the cache
    timeout=3.0,            # override the client's default timeout for this call
)

Returns the parsed JSON response as a dict (see Response Shape). Raises IPFlyError on failure (see Error Handling).

.batch_lookup()

client.batch_lookup(ips, concurrency=None, **kwargs) looks up a list of IPs using a bounded ThreadPoolExecutor (concurrency overrides the client default). This call never raises for an individual IP failure — it returns a list of BatchResult in the same order as ips.

python
from ipfly_sdk import BatchResult

results = client.batch_lookup(["8.8.8.8", "1.1.1.1", "not-an-ip"])
for r in results:
    if r.ok:
        print(r.ip, "->", r.data["city"])
    else:
        print(r.ip, "failed:", r.error.code)

BatchResult is a small dataclass: ip: str, ok: bool, data: Optional[dict], error: Optional[IPFlyError].

Cache Controls

MethodReturnsDescription
client.clear_cache()NoneEmpties the response cache immediately.
client.cache_stats()dictReturns {"enabled", "size", "ttl", "max_size"} — useful for logging or a health-check endpoint.
client.close()NoneClears the cache and any in-flight request bookkeeping. Called automatically when used as a context manager.

is_valid_ip()

A module-level helper that validates an IPv4 or IPv6 string without making a request.

python
from ipfly_sdk import is_valid_ip

is_valid_ip("8.8.8.8")               # True
is_valid_ip("2001:4860:4860::8888")  # True
is_valid_ip("not-an-ip")             # False

Response Shape

Successful lookups return the same JSON structure the IPFly API returns, unchanged, as a Python dict — see the full field reference in the API docs. Shape depends on your plan and the include value requested.

python
{
    "ip": "8.8.8.8",
    "hostname": "dns.google",
    "country_name": "United States",
    "city": "Mountain View",
    "latitude": "37.4056",
    "longitude": "-122.0775",
    "time_zone": {"name": "America/New_York", "...": "..."},
    "asn": {"asn": "AS15169", "name": "Google LLC", "...": "..."},
    "security": {"is_vpn": False, "is_tor": False, "...": "..."},
}

Error Handling

All failures raise IPFlyError, a plain Exception subclass carrying message, status, code, and cause attributes.

CodeMeaning
MISSING_TOKENNo token was supplied when creating the client.
INVALID_IPThe IP string failed local validation before any request was sent.
TIMEOUTThe request exceeded timeout.
NETWORK_ERRORThe request failed before a response was received (offline, DNS, connection refused, etc).
HTTP_ERRORThe API responded with a non-2xx status — check status (401 = bad token, 403 = plan/permission issue, 404 = private/bogon IP, 429/5xx = retry-worthy).
BAD_RESPONSEThe server responded but the body wasn't valid JSON.
python
from ipfly_sdk import IPFlyClient, IPFlyError

client = IPFlyClient(token="YOUR_TOKEN")

try:
    client.lookup("8.8.8.8")
except IPFlyError as err:
    if err.code == "TIMEOUT":
        ...  # retry later, log, fall back to a default
    elif err.status == 401:
        ...  # token is invalid — surface a config error, don't retry
    print(err.message)

Performance Notes

Caching

Avoids re-querying the same IP within the TTL window — handy for a web app looking up the same visitor repeatedly, or a batch job re-processing overlapping log files.

De-duplication

If two request-handling threads call lookup("1.2.3.4") at the same moment, only one HTTP request goes out; both get the same result.

Rate limiting

A courtesy limiter on the client side that smooths bursts (e.g. a bulk-enrichment script) so you don't blow through your plan's requests/second in a tight loop.

Batching

Uses a bounded ThreadPoolExecutor, not raw asyncio — good for I/O-bound HTTP calls with no async dependency. Already on asyncio? Wrap calls with loop.run_in_executor(None, client.lookup, ip).

Examples

Two common integration patterns: enriching a CSV of visitor IPs in bulk, and proxying instead of embedding your token in a client-facing app.

python
import csv
from ipfly_sdk import IPFlyClient

client = IPFlyClient(token="YOUR_TOKEN", cache={"storage": "file", "path": "geo_cache.json"})

with open("visitors.csv") as infile:
    reader = csv.DictReader(infile)
    ips = [row["ip"] for row in reader]

for result in client.batch_lookup(ips, concurrency=8):
    if result.ok:
        print(result.ip, "->", result.data["country_name"])
    else:
        print(result.ip, "-> error:", result.error.code)
python — Flask
from flask import Flask, request, jsonify
from ipfly_sdk import IPFlyClient, IPFlyError
import os

app = Flask(__name__)
client = IPFlyClient(token=os.environ["IPFLY_TOKEN"])

@app.get("/api/geo")
def geo():
    ip = request.args.get("ip") or request.remote_addr
    try:
        return jsonify(client.lookup(ip, include="security"))
    except IPFlyError as err:
        return jsonify({"error": err.code, "message": err.message}), err.status or 502

In the Flask example, your frontend (browser JS, mobile app, etc.) calls /api/geo on your own server — the IPFly token stays in this process and is never sent to the client.

Command-Line Usage

The module ships a tiny CLI for quick checks.

bash
export IPFLY_TOKEN=your_token_here
python -m ipfly_sdk 8.8.8.8
python -m ipfly_sdk                     # looks up your own IP
python -m ipfly_sdk 8.8.8.8 --include security

Python Version Support

Requires Python 3.7+ (uses dataclasses and from __future__ import annotations). No third-party packages — works anywhere the standard library is available, including minimal containers and locked-down environments.

License

MIT — use it, fork it, ship it.

Build on reliable IP Intelligence

Start with free requests per day. No credit card. No commitments. Scale when you’re ready.