# .NET SDK

> Official IPScope server-side client for .NET 8.0+ (C# 12) (IpScope.Sdk).

> **Status: stable.** Published as `IpScope.Sdk` for .NET 8.0+ (C# 12). Strictly scoped to IP intelligence operations with built-in bounded LRU caching and automatic retry backoff.

## Install

```bash Install
dotnet add package IpScope.Sdk
```

## Quickstart: single, batch & prefix lookups

Pass your API key explicitly or set `IPSCOPE_KEY` in your environment. Every lookup returns the canonical [response schema](https://ipscope.io/docs/getting-started/response-format) with `geo`, `network`, `connection`, `security` and `security_extensions`.

```csharp .NET
using IpScope.Sdk;

using var client = new IpScopeClient(new IpScopeClientOptions
{
    ApiKey = Environment.GetEnvironmentVariable("IPSCOPE_KEY"),
    MaxCacheEntries = 2048,
});

// 1. Single IPv4 or IPv6 lookup
var info = await client.LookupIpAsync("198.51.100.7");
Console.WriteLine($"{info.Ip} {info.Geo.CountryCode} AS{info.Network.Asn} VPN={info.Security.IsVpn}");

// 2. Batch lookup (up to 100 IPs; deduplicated & cached)
var batch = await client.LookupBatchAsync(new[] { "198.51.100.7", "1.1.1.1" });
Console.WriteLine($"Total resolved: {batch.Count}");

// 3. Memory-bounded IAsyncEnumerable streaming for 10,000+ IPs
await foreach (var chunk in client.LookupBatchStreamAsync(largeIpList))
{
    foreach (var record in chunk.Results)
    {
        // Constant O(chunk) memory overhead
    }
}

// 4. Network CIDR prefix lookup
var prefix = await client.LookupNetworkAsync("198.51.100.0/24");
Console.WriteLine($"{prefix.QueriedCidr} -> AS{prefix.Record?.Asn} ({prefix.Record?.Organization})");
```

### 200 `IpLookupResponse`: 1. Single IPv4/IPv6 lookup response (GET /v1/ip/{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"
}
```

### 200 `BatchLookupResponse`: 2 & 3. Batch & streaming lookup response (POST /v1/ip/batch)

- `dataset_version` (string, required): Snapshot version that served all lookups in this batch.
- `count` (integer, required): Total number of resolved IP records returned in `results`.
- `results` (IpLookupResponse[], required): Ordered array of `IpLookupResponse` objects projected to your API key’s `licensedFamilies`.
  - `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 BatchLookupResponse
{
  "dataset_version": "2026-10-27T00:00:00Z.crawl-1793088000000",
  "count": 3,
  "results": [
    {
      "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"
    },
    {
      "ip": "203.0.113.42",
      "ip_version": 4,
      "matched_network": "203.0.113.0/24",
      "geo": {
        "country_code": "DE",
        "declared_country_code": "DE",
        "physical_country_code": "DE",
        "is_virtual_location": false,
        "location_type": "physical",
        "confidence": 0.92,
        "subdivision": "Hesse",
        "city": "Frankfurt am Main",
        "accuracy_radius_km": 10,
        "timezone": "Europe/Berlin"
      },
      "network": {
        "asn": 24940,
        "organization": "Hetzner Online GmbH"
      },
      "connection": {
        "connection_type": "hosting",
        "is_residential": false,
        "is_mobile": false,
        "is_business": false,
        "is_hosting": true
      },
      "security": {
        "is_vpn": false,
        "is_hosting": true,
        "is_tor_exit": false,
        "is_proxy": false,
        "anonymizer_type": "none",
        "confidence": 0.91,
        "reason_codes": [
          "datacenter_asn_range"
        ]
      },
      "dataset_version": "2026-10-27T00:00:00Z.crawl-1793088000000",
      "updated_at": "2026-10-27T00:00:00Z"
    }
  ]
}
```

### 200 `NetworkPrefixLookupResponse`: 4. Network CIDR prefix lookup response (GET /v1/networks/{prefix}/{length})

- `queried_cidr` (string, required): Normalized canonical CIDR block.
- `family` (integer, required): IP address family (`4` or `6`).
- `dataset_version` (string, required): Active dataset snapshot version.
- `record` (NetworkPrefixRecord, required): Matched prefix record in the routing snapshot.
  - `prefix` (string, required): Matched CIDR prefix in the routing table.
  - `family` (integer, required): Address family (`4` or `6`).
  - `asn` (integer, optional, nullable): Origin Autonomous System Number.
  - `organization` (string, optional, nullable): Registered autonomous system organization name.
  - `countryCode` (string, optional, nullable): ISO 3166-1 alpha-2 country code.
  - `city` (string, optional, nullable): Resolved city name.
  - `locationType` (string, required): Location resolution classification.
  - `isVpn` (boolean, required): Whether the prefix is classified as a VPN exit range.
  - `isHosting` (boolean, required): Whether the prefix belongs to a hosting/cloud ASN.
  - `isTorExit` (boolean, required): Whether the prefix contains active Tor exit nodes.
  - `isProxy` (boolean, required): Whether the prefix contains active proxy relays.
  - `anonymizerType` (string, required): Primary anonymizer classification (`"vpn"`, `"none"`, etc.).

```json 200 NetworkPrefixLookupResponse
{
  "queried_cidr": "198.51.100.0/24",
  "family": 4,
  "dataset_version": "2026-10-27T00:00:00Z.crawl-1793088000000",
  "record": {
    "prefix": "198.51.100.0/24",
    "family": 4,
    "asn": 64501,
    "organization": "Example Cloud LLC",
    "countryCode": "US",
    "city": "Ashburn",
    "locationType": "physical",
    "isVpn": true,
    "isHosting": true,
    "isTorExit": false,
    "isProxy": false,
    "anonymizerType": "vpn"
  }
}
```

## Architecture & runtime guarantees

- Zero external NuGet dependencies: uses `System.Text.Json` source-friendly serialization and `SocketsHttpHandler` connection pooling.
- Native `IAsyncEnumerable<BatchLookupResponse>` via `LookupBatchStreamAsync` for backpressure-aware streaming.
- Thread-safe O(1) LRU cache and `IHttpClientFactory` dependency injection compatibility.

## Reading local MMDB files

If you subscribe to a [downloadable database](https://ipscope.io/docs/databases), our `.mmdb` snapshots open directly in standard MaxMind DB readers with sub-microsecond in-memory lookups:

```csharp .NET (MMDB)
using MaxMind.Db;
using System.Net;

using var reader = new Reader("/var/lib/ipscope/ipscope-complete.mmdb");
var record = reader.Find<Dictionary<string, object>>(IPAddress.Parse("198.51.100.7"));
```

## Plain HTTP fallback

Need to call the [REST API](https://ipscope.io/docs/api-reference) without installing a package? Use .NET’s standard HTTP client directly:

```csharp .NET (HTTP)
using var http = new HttpClient();
http.DefaultRequestHeaders.Add("Authorization", $"Bearer {Environment.GetEnvironmentVariable("IPSCOPE_KEY")}");
var json = await http.GetStringAsync("https://api.ipscope.io/v1/ip/198.51.100.7");
```
