lookup('8.8.8.8'); * echo !empty($data['threat']['is_vpn']) ? 'yes' : 'no'; */ declare(strict_types=1); class OpenIPApiException extends \RuntimeException { public int $status; public string $errorCode; public function __construct(string $message, int $status = 0, string $errorCode = '') { parent::__construct($message, $status); $this->status = $status; $this->errorCode = $errorCode; } } class OpenIPApiClient { private string $apiKey; private string $baseUrl; public function __construct(string $apiKey, string $baseUrl = 'https://api.openipapi.com') { if ($apiKey === '') { throw new OpenIPApiException('API key is required', 0, "missing_api_key"); } $this->apiKey = $apiKey; $this->baseUrl = rtrim($baseUrl, '/'); } public function lookup(string $ip): array { return $this->request('GET', '/v1/lookup/' . rawurlencode($ip)); } public function me(): array { return $this->request('GET', '/v1/me'); } public function batch(array $ips): array { if (empty($ips)) { throw new OpenIPApiException('batch() expects a non-empty array of IPs', 0, "invalid_input"); } return $this->request('POST', '/v1/lookup/batch', ['ips' => array_values($ips)]); } public function asn($asn): array { $n = ltrim((string)$asn, 'AaSs'); return $this->request('GET', '/v1/asn/' . rawurlencode($n)); } public function validate(string $ip): array { return $this->request('GET', '/v1/validate/' . rawurlencode($ip)); } private function request(string $method, string $path, ?array $body = null): array { $url = $this->baseUrl . $path; $payload = $body !== null ? json_encode($body, JSON_UNESCAPED_SLASHES) : null; $headers = [ 'X-API-Key: ' . $this->apiKey, 'Accept: application/json', 'User-Agent: openipapi-php-sdk/1.0', ]; if ($payload !== null) $headers[] = 'Content-Type: application/json'; if (function_exists('curl_init')) { [$raw, $status] = $this->doCurl($method, $url, $payload, $headers); } else { [$raw, $status] = $this->doStream($method, $url, $payload, $headers); } $data = $raw !== '' ? json_decode($raw, true) : null; if ($status < 200 || $status >= 300) { $code = (is_array($data) && isset($data['error'])) ? (string)$data['error'] : 'http_' . $status; $msg = (is_array($data) && isset($data['message'])) ? (string)$data['message'] : 'HTTP ' . $status; throw new OpenIPApiException($msg, $status, $code); } return is_array($data) ? $data : []; } protected function doCurl(string $method, string $url, ?string $body, array $headers): array { $ch = curl_init($url); curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_TIMEOUT => 30, CURLOPT_CONNECTTIMEOUT => 10, CURLOPT_HTTPHEADER => $headers, CURLOPT_CUSTOMREQUEST => $method, ]); if ($body !== null) curl_setopt($ch, CURLOPT_POSTFIELDS, $body); $raw = curl_exec($ch); $status = (int)curl_getinfo($ch, CURLINFO_HTTP_CODE); $err = curl_error($ch); curl_close($ch); if ($raw === false) { throw new OpenIPApiException('Network error: ' . $err, 0, "network_error"); } return [$raw, $status]; } protected function doStream(string $method, string $url, ?string $body, array $headers): array { $ctx = stream_context_create([ 'http' => [ 'method' => $method, 'header' => implode("\r\n", $headers), 'content' => $body ?? '', 'ignore_errors' => true, 'timeout' => 30, ], ]); $raw = @file_get_contents($url, false, $ctx); if ($raw === false) { throw new OpenIPApiException('Network error fetching ' . $url, 0, "network_error"); } $status = 0; if (isset($http_response_header) && is_array($http_response_header) && count($http_response_header) > 0) { if (preg_match('#HTTP/\S+\s+(\d+)#', $http_response_header[0], $m)) { $status = (int)$m[1]; } } return [$raw, $status ?: 200]; } }