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

PHP SDK

A dependency-free PHP client for the IPFly API — caching, retries, rate limiting and real parallel batch lookups via cURL, built in.

Get started → API Reference

Overview

The IPFly PHP SDK wraps the IPFly API in a single dependency-free file. It uses the curl extension when available for real parallel batch lookups, and falls back to PHP's built-in streams otherwise — so your integration code can stay focused on what to do with the response instead of the transport layer.

TTL + LRU caching

In-process memory, a shared JSON file, or APCu — shared across PHP-FPM workers with no disk I/O.

Automatic retries

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

Real parallel batching

batchLookup() uses curl_multi for genuine concurrency — not a sequential loop dressed up as a batch.

Client-side rate limiting

A token-bucket limiter smooths bursts in long-running scripts and batch jobs.

Typed exceptions

A single IPFlyException with ->getStatus() and ->getErrorCode() instead of parsing messages.

Zero dependencies

One file, PHP 7.4+. No Composer package required. MIT licensed.

Where This SDK Belongs

This client is meant to run server-side — in a web backend, a CLI script, a queue worker, a cron job. It is not meant to be shipped to a browser or a distributed client app where your API token could be extracted.

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

Installation

No Composer package is published — copy ipfly_sdk.php into your project.

bash
# Drop ipfly_sdk.php directly into your source tree, e.g.:
cp ipfly_sdk.php src/IPFly/ipfly_sdk.php
bash
curl -O https://ipfly.world/libs/php-sdk/ipfly_sdk
# or
wget https://ipfly.world/libs/php-sdk/ipfly_sdk
php
<?php
require_once __DIR__ . '/ipfly_sdk.php';

use IPFly\IPFlyClient;
use IPFly\IPFlyException;

$client = new IPFlyClient('YOUR_TOKEN');

Requirements

PHP 7.4 or later. The curl extension is optional but recommended — it enables real parallel batch lookups and more precise timeouts; without it the SDK falls back to PHP's stream wrappers automatically. The apcu extension is only needed if you choose 'storage' => 'apcu' for the cache.

Quick Start

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

php
<?php
require_once 'ipfly_sdk.php';

use IPFly\IPFlyClient;
use IPFly\IPFlyException;

$client = new IPFlyClient('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');
echo $data['city'], ' ', $data['country_name'], "\n";

// Look up the caller's own IP (omit the ip argument)
$me = $client->lookupSelf();
echo "You appear to be in {$me['city']}\n";

// Handle errors explicitly
try {
    $client->lookup('not-an-ip');
} catch (IPFlyException $err) {
    echo $err->getErrorCode(), ': ', $err->getMessage(), "\n";
}

Client Options

The second constructor argument is an options array: new IPFlyClient($token, [...]).

OptionTypeDefaultDescription
base_urlstringipfly.world/apiOverride to point at your own backend proxy.
includestringnullDefault 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 parallel cURL handles for batchLookup().
cachearraysee belowResponse cache settings — see Cache Options.
debugboolfalseWrite internal lifecycle events to STDERR (tokens are redacted).
on_requestcallablenullfunction(?string $ip, string $url): void — fired right before a request is sent.
on_responsecallablenullfunction(?string $ip, array $data, bool $fromCache): void — fired after a successful lookup.
on_errorcallablenullfunction(?string $ip, IPFlyException $error): void — fired when a lookup ultimately fails.
on_cache_hitcallablenullfunction(?string $ip, array $data): void — fired when a cached response is served instead of a network call.

Cache Options

Passed as the cache array, e.g. new IPFlyClient($token, ['cache' => ['storage' => 'apcu', '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.
storagestring'memory''memory', 'file', or 'apcu' — see the comparison below.
pathstringsystem temp dirFile path used when storage => 'file'.
prefixstring'ipfly:'Key prefix used when storage => 'apcu'.

Which storage should I pick?

'memory' lives only for the current request/process — fine for CLI scripts, mostly useless for a typical PHP-FPM app where each request is a fresh process. 'file' persists a shared JSON file across requests; simple and portable, best at low/medium traffic. 'apcu' is shared in-memory across PHP-FPM workers on the same machine with no disk I/O — the best choice for a typical web app, if the extension is installed.

lookup() & lookupSelf()

lookup(?string $ip = null, array $options = []): array looks up a single IP. Pass null (or omit) to geolocate the caller — or use the lookupSelf(array $options = []) shorthand.

php
$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 an associative array (see Response Shape). Throws IPFlyException on failure (see Error Handling).

batchLookup()

batchLookup(array $ips, ?int $concurrency = null, array $options = []): BatchResult[] looks up an array of IPs. When the curl extension is loaded this runs genuinely in parallel via curl_multi, bounded by $concurrency; otherwise it falls back to a sequential loop automatically. This call never throws for an individual IP failure — it returns a list of BatchResult in the same order as $ips.

php
use IPFly\BatchResult;

$results = $client->batchLookup(['8.8.8.8', '1.1.1.1', 'not-an-ip']);
foreach ($results as $r) {
    if ($r->ok) {
        echo "{$r->ip} -> {$r->data['city']}\n";
    } else {
        echo "{$r->ip} failed: {$r->error->getErrorCode()}\n";
    }
}

BatchResult is a small value object: public string $ip, public bool $ok, public ?array $data, public ?IPFlyException $error.

Cache Controls

MethodReturnsDescription
clearCache()voidEmpties the response cache immediately.
cacheStats()arrayReturns ['enabled', 'size', 'ttl', 'max_size'] — useful for logging or a health-check endpoint.

is_valid_ip()

A namespaced function that validates an IPv4 or IPv6 string without making a request.

php
use function IPFly\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 PHP associative array — see the full field reference in the API docs. Shape depends on your plan and the include value requested.

php
[
    '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 throw IPFly\IPFlyException, a RuntimeException subclass with getMessage(), getStatus(), getErrorCode(), and getCause().

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 getStatus() (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, or the response was empty.
php
use IPFly\IPFlyClient;
use IPFly\IPFlyException;

$client = new IPFlyClient('YOUR_TOKEN');

try {
    $client->lookup('8.8.8.8');
} catch (IPFlyException $err) {
    if ($err->getErrorCode() === 'TIMEOUT') {
        // retry later, log, fall back to a default
    } elseif ($err->getStatus() === 401) {
        // token is invalid — surface a config error, don't retry
    }
    error_log($err->getMessage());
}

Performance Notes

Caching

Use 'apcu' storage for a typical PHP-FPM web app so the cache is actually shared across requests, rather than reset every time like 'memory' would be.

Parallel batching

batchLookup() uses curl_multi so N IPs don't cost N × round-trip-time — they're bounded by concurrency concurrent connections instead.

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.

Retries

Only apply to transient failures (network errors, timeouts, 5xx), including inside a batch. A 401/403/404 fails fast since retrying won't fix a bad token or a private 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.

php
<?php
require_once 'ipfly_sdk.php';

use IPFly\IPFlyClient;

$client = new IPFlyClient(getenv('IPFLY_TOKEN'), [
    'cache' => ['storage' => 'file', 'path' => __DIR__ . '/geo_cache.json'],
]);

$ips = [];
if (($handle = fopen('visitors.csv', 'r')) !== false) {
    $header = fgetcsv($handle);
    $ipCol = array_search('ip', $header, true);
    while (($row = fgetcsv($handle)) !== false) {
        $ips[] = $row[$ipCol];
    }
    fclose($handle);
}

foreach ($client->batchLookup($ips, 8) as $result) {
    if ($result->ok) {
        echo "{$result->ip} -> {$result->data['country_name']}\n";
    } else {
        echo "{$result->ip} -> error: {$result->error->getErrorCode()}\n";
    }
}
php — public/api/geo.php
<?php
require_once __DIR__ . '/../../ipfly_sdk.php';

use IPFly\IPFlyClient;
use IPFly\IPFlyException;

$client = new IPFlyClient(getenv('IPFLY_TOKEN'));
$ip = $_GET['ip'] ?? $_SERVER['REMOTE_ADDR'];

header('Content-Type: application/json');

try {
    echo json_encode($client->lookup($ip, ['include' => 'security']));
} catch (IPFlyException $err) {
    http_response_code($err->getStatus() ?: 502);
    echo json_encode(['error' => $err->getErrorCode(), 'message' => $err->getMessage()]);
}
php — Laravel controller
<?php

namespace App\Http\Controllers;

use IPFly\IPFlyClient;
use IPFly\IPFlyException;
use Illuminate\Http\Request;

class GeoController extends Controller
{
    public function __invoke(Request $request)
    {
        static $client = null;
        $client ??= new IPFlyClient(config('services.ipfly.token'), [
            'cache' => ['storage' => 'apcu'],
        ]);

        try {
            return response()->json(
                $client->lookup($request->query('ip') ?? $request->ip(), ['include' => 'security'])
            );
        } catch (IPFlyException $err) {
            return response()->json(
                ['error' => $err->getErrorCode(), 'message' => $err->getMessage()],
                $err->getStatus() ?: 502
            );
        }
    }
}

In both proxy examples, your frontend calls your own endpoint — the IPFly token stays in this process and is never sent to the client.

Requirements

PHP 7.4 or later (uses typed properties and arrow functions; no PHP 8-only syntax required). The curl extension is optional but recommended — without it, the SDK automatically falls back to PHP's built-in stream wrappers for single lookups and runs batchLookup() sequentially. The apcu extension is only needed for 'storage' => 'apcu' caching.

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.