# Caller IP lookup

> Look up the connecting visitor’s public IPv4 or IPv6 address without an API key, with proxy-aware Cloudflare edge header resolution.

```http
GET https://api.ipscope.io/v1/ip/me
```

Authentication: none. Plan: Public · 120 req/min

```http
GET https://api.ipscope.io/v1/public/ip
```

Authentication: none. Plan: Alias · 120 req/min

Returns the full `IpLookupResponse` for the client IP address that initiated the HTTP request. Both `GET /v1/ip/me` and `GET /v1/public/ip` are public, keyless endpoints designed for CLI diagnostics, health scripts, and interactive browser/edge tools.

## Code examples

```bash curl
curl -sS "https://api.ipscope.io/v1/ip/me"
```

```python Python
import asyncio, os
from ipscope import AsyncIpScopeClient

async def main() -> None:
    async with AsyncIpScopeClient(api_key=os.environ["IPSCOPE_KEY"]) as client:
        record = await client.lookup_caller_ip()
        print(record["ip"], record["geo"]["country_code"], record["security"]["is_vpn"])

asyncio.run(main())
```

```ts Node.js
import { IpScopeClient } from '@ipscope/sdk-node';

const client = new IpScopeClient({ apiKey: process.env.IPSCOPE_KEY });
const record = await client.lookupCallerIp();
console.log(record.ip, record.geo?.country_code, record.security?.is_vpn);
```

```go Go
client := ipscope.NewClient(ipscope.Config{APIKey: os.Getenv("IPSCOPE_KEY")})
defer client.Close()

record, err := client.LookupCallerIP(ctx)
if err != nil {
	return err
}
fmt.Println(record.IP, record.Security.IsVPN)
```

```rust Rust
use ipscope::{AsyncIpScopeClient, ClientConfig};

let client = AsyncIpScopeClient::new(ClientConfig::new(std::env::var("IPSCOPE_KEY")?))?;
let record = client.lookup_caller_ip().await?;
println!("{} VPN={}", record.ip, record.security.is_vpn);
```

```java Java
import io.ipscope.sdk.IpScopeClient;
import io.ipscope.sdk.IpScopeConfig;

IpScopeConfig config = IpScopeConfig.builder().apiKey(System.getenv("IPSCOPE_KEY")).build();
try (IpScopeClient client = new IpScopeClient(config)) {
    var record = client.lookupCallerIp();
    System.out.println(record.ip() + " " + record.geo().countryCode());
}
```

```csharp .NET
using IpScope.Sdk;

using var client = new IpScopeClient(new IpScopeClientOptions {
    ApiKey = Environment.GetEnvironmentVariable("IPSCOPE_KEY")
});
var record = await client.LookupCallerIpAsync();
Console.WriteLine($"{record.Ip} {record.Geo?.CountryCode} VPN={record.Security?.IsVpn}");
```

```ruby Ruby
require "ipscope"

client = IpScope::Client.new(api_key: ENV.fetch("IPSCOPE_KEY"))
record = client.lookup_caller_ip
puts "#{record.ip} #{record.geo.country_code} VPN=#{record.security.is_vpn}"
```

```php PHP
<?php
use IpScope\Sdk\IpScopeClient;

$client = new IpScopeClient(apiKey: getenv('IPSCOPE_KEY') ?: '');
$record = $client->lookupCallerIp();
echo $record->ip . " " . $record->geo->countryCode . PHP_EOL;
```

## Responses

### 200 `IpLookupResponse`: Full IPv4/IPv6 intelligence record for the connecting visitor IP.

- `ip` (string, required): Canonical normalized representation of the queried IPv4 or IPv6 address.
- `ip_version` (integer, required): Protocol family (`4` for IPv4, `6` for IPv6).
- `matched_network` (string, optional, nullable): Longest-matching routed CIDR prefix in the active Radix/LPM snapshot.
- `geo` (GeoLocation, optional): Country and city geolocation record (`geo_country` and `geo_city` families).
  - `country_code` (string, optional, nullable): Effective ISO 3166-1 alpha-2 country code (`"US"`, `"DE"`, `"TR"`).
  - `declared_country_code` (string, optional, nullable): Country code registered in RIR WHOIS/RDAP allocation records or RFC 8805 geofeed.
  - `physical_country_code` (string, optional, nullable): Country code where active BGP routing and latency triangulation place the infrastructure.
  - `is_virtual_location` (boolean, required): `true` when `declared_country_code` differs from `physical_country_code` (cross-border routed or geofeed-relocated prefix).
  - `location_type` (string, required): How the effective location was resolved by the lookup engine.
  - `confidence` (number, optional, nullable): Calibrated geolocation confidence score.
  - `subdivision` (string, optional, nullable): First-level administrative region or state. Included when `geo_city` is licensed.
  - `city` (string, optional, nullable): Resolved city name. Included when `geo_city` is licensed.
  - `accuracy_radius_km` (integer, optional, nullable): Estimated accuracy radius in kilometres around the city centroid. Included when `geo_city` is licensed.
  - `timezone` (string, optional, nullable): IANA time zone identifier. Included when `geo_city` is licensed.
- `network` (NetworkInfo, optional): BGP origin autonomous system metadata (`network` family).
  - `asn` (integer, optional, nullable): Origin Autonomous System Number without the `AS` prefix.
  - `organization` (string, optional, nullable): Canonical autonomous system or operator organization name.
- `connection` (ConnectionInfo, optional): Access network classification (`connection` and `hosting` families).
  - `connection_type` (string, required): Primary access network classification.
  - `is_residential` (boolean, required): `true` for consumer FTTH, cable, and DSL broadband ISPs.
  - `is_mobile` (boolean, required): `true` for cellular carrier gateways (4G/5G CGNAT pools).
  - `is_business` (boolean, required): `true` for enterprise leased lines and corporate autonomous systems.
  - `is_hosting` (boolean, required): `true` for cloud providers, colocation facilities, and VPS networks.
- `security` (SecurityInfo, optional): VPN, proxy, Tor, and hosting anonymizer classification (`anonymity` and `hosting` families).
  - `is_vpn` (boolean, required): `true` when the IP is an active commercial or self-hosted VPN exit node.
  - `is_hosting` (boolean, required): `true` when routed by a datacenter, cloud, or hosting ASN.
  - `is_tor_exit` (boolean, required): `true` when listed in the active Tor Project exit consensus.
  - `is_proxy` (boolean, required): `true` for open HTTP/SOCKS proxies and residential proxy relays.
  - `anonymizer_type` (string, required): Primary anonymizer classification.
  - `confidence` (number, optional, nullable): Calibrated anonymity confidence score.
  - `reason_codes` (string[], required): Deterministic evidence codes explaining why the IP was flagged.
- `security_extensions` (SecurityExtensions, optional): Extended threat telemetry and scanner recency (`security_extensions` family).
  - `abuse_velocity_score` (number, optional, nullable): Normalized abuse velocity score from active honeypot and darknet observations.
  - `is_known_scanner` (boolean, optional, nullable): `true` when the IP has been observed performing internet-wide port or vulnerability scanning.
  - `observation_recency_hours` (integer, optional, nullable): Hours elapsed since the most recent active security or anonymizer observation.
- `dataset_version` (string, required): Version identifier of the compiled IPScope dataset snapshot serving the response.
- `updated_at` (string, optional, nullable): ISO 8601 UTC timestamp when the active dataset snapshot was published.

```json 200 IpLookupResponse
{
  "ip": "198.51.100.7",
  "ip_version": 4,
  "matched_network": "198.51.100.0/24",
  "geo": {
    "country_code": "US",
    "declared_country_code": "US",
    "physical_country_code": "US",
    "is_virtual_location": false,
    "location_type": "physical",
    "confidence": 0.94,
    "subdivision": "Virginia",
    "city": "Ashburn",
    "postal_code": "20147",
    "latitude": 39.0438,
    "longitude": -77.4874,
    "accuracy_radius_km": 10,
    "timezone": "America/New_York"
  },
  "network": {
    "asn": 64501,
    "organization": "Example Cloud LLC"
  },
  "connection": {
    "connection_type": "hosting",
    "is_hosting": true,
    "is_residential": false,
    "is_mobile": false,
    "is_business": false
  },
  "security": {
    "is_vpn": true,
    "is_hosting": true,
    "is_tor_exit": false,
    "is_proxy": false,
    "anonymizer_type": "vpn",
    "confidence": 0.96,
    "reason_codes": [
      "vpn_handshake_confirmed",
      "datacenter_asn_range"
    ]
  },
  "security_extensions": {
    "abuse_velocity_score": 18,
    "is_known_scanner": false,
    "observation_recency_hours": 12,
    "anonymizer_reason": "vpn_handshake_confirmed",
    "threat_level": "medium",
    "abuse_velocity": "low",
    "confidence": 0.96
  },
  "dataset_version": "2026-10-27T00:00:00Z.crawl-1793088000000",
  "updated_at": "2026-10-27T00:00:00Z"
}
```

### 429 `ApiError`: Public caller IP rate limit exceeded (120 req/min per visitor IP).

- `statusCode` (integer, required): HTTP status code returned by the API gateway.
- `message` (string, required): Human-readable diagnostic message describing the error condition.
- `error` (string, required): Canonical HTTP error classification.

```json 429 ApiError
{
  "statusCode": 429,
  "message": "Public IP lookup rate limit exceeded (120 req/min). Retry after 12s.",
  "error": "Too Many Requests"
}
```

### 503 `ApiError`: No active dataset snapshot loaded on the API node.

- `statusCode` (integer, required): HTTP status code returned by the API gateway.
- `message` (string, required): Human-readable diagnostic message describing the error condition.
- `error` (string, required): Canonical HTTP error classification.

```json 503 ApiError
{
  "statusCode": 503,
  "message": "No active dataset snapshot loaded",
  "error": "Service Unavailable"
}
```

## Proxy-aware IP extraction & anti-spoofing

To prevent `X-Forwarded-For` header spoofing while supporting reverse-proxy and Cloudflare deployments, `LookupController` inspects the `TRUST_PROXY` environment configuration:

- **When `TRUST_PROXY=true`**: The service extracts the visitor’s real public IP in priority order from `cf-connecting-ip`, the first hop of `x-forwarded-for`, or `x-real-ip`, falling back to the socket peer `req.ip`.
- **When `TRUST_PROXY=false` (default direct mode)**: Forwarded headers are ignored and only the verified TCP/QUIC socket address (`req.ip`) is used.
- **Cloudflare edge hint fallback**: When behind Cloudflare (`TRUST_PROXY=true`), the service also reads `cf-ipcountry`, `cf-ipcity`, `cf-region`, and `cf-ray` as fallback hints if a newly allocated prefix has no city record in the local snapshot.

> **Note:** Calling `/v1/ip/me` from your own backend microservice resolves your server’s egress IP. To enrich a customer connecting to your backend, read their IP from your load balancer and call [`GET /v1/ip/{ip}`](https://ipscope.io/docs/api-reference/endpoints/ip).
