IPWhois 1.2.0
dotnet add package IPWhois --version 1.2.0
NuGet\Install-Package IPWhois -Version 1.2.0
<PackageReference Include="IPWhois" Version="1.2.0" />
<PackageVersion Include="IPWhois" Version="1.2.0" />
<PackageReference Include="IPWhois" />
paket add IPWhois --version 1.2.0
#r "nuget: IPWhois, 1.2.0"
#:package IPWhois@1.2.0
#addin nuget:?package=IPWhois&version=1.2.0
#tool nuget:?package=IPWhois&version=1.2.0
IPWhois — official .NET client
Official, lightweight .NET client for the ipwhois.io IP Geolocation API. Zero dependencies on modern .NET (uses System.Text.Json on netstandard2.0).
- ✅ Single and bulk IP lookups (IPv4 and IPv6)
- ✅ Works with both the Free and Paid plans
- ✅ HTTPS by default
- ✅ Localisation, field selection, threat detection, rate info
- ✅ Robust error handling —
LookupAsync/BulkLookupAsyncreturnSuccess = falseinstead of throwing on API, HTTP, network, timeout, and validation failures - ✅ Async/await,
CancellationTokensupport - ✅ Plays nicely with
IHttpClientFactory - ✅ Multi-targets
netstandard2.0,net6.0,net8.0
Installation
dotnet add package IPWhois
or, from the Package Manager Console:
Install-Package IPWhois
Free vs Paid plan
The same IPWhoisClient class is used for both plans. The only difference
is whether you pass an API key:
- Free plan — create the client without arguments. No API key, no signup required. Suitable for low-traffic and non-commercial use.
- Paid plan — create the client with your API key from https://ipwhois.io. Higher limits, plus access to bulk lookups and threat-detection data.
using var free = new IPWhoisClient(); // Free plan — no API key
using var paid = new IPWhoisClient("YOUR_API_KEY"); // Paid plan — with API key
Everything else (LookupAsync, options, error handling) is identical.
Quick start — Free plan (no API key)
using IPWhois;
using var client = new IPWhoisClient(); // no API key
var info = await client.LookupAsync("8.8.8.8");
Console.WriteLine($"{info.Country} {info.Flag?.Emoji}");
// → United States 🇺🇸
Console.WriteLine($"{info.City}, {info.Region}");
// → Mountain View, California
Quick start — Paid plan (with API key)
Get an API key at https://ipwhois.io and pass it to the constructor:
using IPWhois;
using var client = new IPWhoisClient("YOUR_API_KEY"); // with API key
var info = await client.LookupAsync("8.8.8.8");
Console.WriteLine($"{info.Country} {info.Flag?.Emoji}");
// → United States 🇺🇸
Console.WriteLine($"{info.City}, {info.Region}");
// → Mountain View, California
ℹ️ Pass nothing (or
null) to look up your own public IP:await client.LookupAsync();— works on both plans.
Lookup options
Every option below can be passed per call via LookupOptions, or set once
on the client as a default.
| Option | C# Type | Plans needed | Description |
|---|---|---|---|
Language |
string |
Free + Paid | One of: "en", "ru", "de", "es", "pt-BR", "fr", "zh-CN", "ja" |
Fields |
IEnumerable<string> |
Free + Paid | Restrict the response to specific fields (e.g. new[] { "country", "city" }) |
Rate |
bool |
Basic and above | Include the Rate block (Limit, Remaining) |
Security |
bool |
Business and above | Include the Security block (proxy/vpn/tor/hosting) |
Setting defaults once
Every option can be passed two ways: per call (as the second argument to
LookupAsync / BulkLookupAsync) or once as a default on the client.
Per-call options always override the defaults.
Defaults are set with fluent setters — SetLanguage, SetFields,
SetSecurity, SetRate, SetTimeout, SetUserAgent, SetSsl — and can
be chained:
using IPWhois;
// Free plan
using var client = new IPWhoisClient()
.SetLanguage("en")
.SetFields(new[] { "success", "country", "city", "flag.emoji" })
.SetTimeout(8);
using IPWhois;
// Paid plan
using var client = new IPWhoisClient("YOUR_API_KEY")
.SetLanguage("en")
.SetFields(new[] { "success", "country", "city", "flag.emoji" })
.SetTimeout(8);
…or via the IPWhoisClientOptions object at construction time:
using var client = new IPWhoisClient(
apiKey: "YOUR_API_KEY",
options: new IPWhoisClientOptions
{
Language = "en",
Fields = new[] { "success", "country", "city", "flag.emoji" },
Timeout = TimeSpan.FromSeconds(8),
});
Per-call options always win over the defaults:
await client.LookupAsync("8.8.8.8"); // uses lang=en, field whitelist, timeout=8
await client.LookupAsync("1.1.1.1", new LookupOptions { Language = "de" }); // overrides lang for this single call only
⚠️ When you restrict fields with
SetFields()(or the per-callFieldsoption), the API only returns the fields you ask for. Always include"success"in the list if you rely oninfo.Successfor error checking — otherwise the field will be missing on responses.
ℹ️
SetSecurity(true)requires Business+ andSetRate(true)requires Basic+. See the table above for what's available where.
HTTPS Encryption
By default, all requests are sent over HTTPS. If you need to disable it
(for example, in environments without an up-to-date CA bundle), set Ssl
on the constructor options or call SetSsl(false):
using IPWhois;
// Free plan
using var client = new IPWhoisClient(apiKey: null,
options: new IPWhoisClientOptions { Ssl = false });
using IPWhois;
// Paid plan
using var client = new IPWhoisClient("YOUR_API_KEY",
new IPWhoisClientOptions { Ssl = false });
ℹ️ HTTPS is strongly recommended for production traffic — your API key is sent in the query string and would otherwise travel in clear text.
Bulk lookup (Paid plan only)
The bulk endpoint sends up to 100 IPs in a single GET request. Each address counts as one credit. Available on the Business and Unlimited plans.
using IPWhois;
using var client = new IPWhoisClient("YOUR_API_KEY");
var bulk = await client.BulkLookupAsync(new[]
{
"8.8.8.8",
"1.1.1.1",
"208.67.222.222",
"2c0f:fb50:4003::", // IPv6 is fine — mix freely
});
if (!bulk.Success)
{
// Whole-batch failure (network down, bad API key, rate limit, …).
Console.Error.WriteLine($"Bulk failed: {bulk.Message} (HTTP {bulk.HttpStatus})");
return;
}
foreach (var row in bulk.Results)
{
if (!row.Success)
{
// Per-IP errors (e.g. "Invalid IP address") are returned inline —
// the rest of the batch is still usable.
Console.WriteLine($"skip {row.Ip}: {row.Message}");
continue;
}
Console.WriteLine($"{row.Ip} → {row.Country}");
}
ℹ️ Bulk requires an API key. Calling
BulkLookupAsyncwithout one will fail at the API level.
Error handling
LookupAsync and BulkLookupAsync do not throw for any of:
- API errors (invalid IP, bad key, rate limit, "Reserved range", …)
- HTTP errors (4xx / 5xx)
- Network failures (DNS, connection refused, connection reset, …)
HttpClient.Timeouttimeouts- Malformed JSON
- Validation errors (e.g. unsupported
Language, too many IPs in a bulk)
Every one of those comes back on the response object with Success = false,
a populated Message, and an ErrorType you can branch on. Just check
info.Success after every call:
var info = await client.LookupAsync("8.8.8.8");
if (!info.Success)
{
Console.Error.WriteLine($"Lookup failed: {info.Message}");
return;
}
Console.WriteLine(info.Country);
This means an outage of the ipwhois.io API (or of your server's DNS, connection, etc.) will never surface as an unhandled exception in your application — you decide how to react.
There are two cases where the methods do throw, both of them deliberate:
CancellationTokencancellation. Cancelling the supplied token — whether directly (cts.Cancel()), viaCancelAfter, or through aCancellationTokenSourceconstructed with a timeout — propagates asOperationCanceledException, which is the standard .NET convention for cooperative cancellation. If you'd rather have a deadline that doesn't throw, setHttpClient.Timeout(orSetTimeout()) instead: that one is translated into aSuccess = falseerror response.- Programmer errors. The constructor and the fluent
Set*setters throwArgumentException/ArgumentNullExceptionfor clearly invalid inputs (e.g.SetFields(null),SetUserAgent("")). These represent misuse of the API, not runtime conditions, and aren't caught.
Error response fields
Every error response sets Success = false, populates Message, and tags
ErrorType with the category of the failure. Some errors include extra
fields you can branch on:
| Field | When it's present |
|---|---|
Success |
Always — false for error responses (true for successful responses) |
Message |
Always — human-readable description of what went wrong |
ErrorType |
Always — one of "api", "network", "environment", or "invalid_argument" |
HttpStatus |
On HTTP 4xx / 5xx responses |
RetryAfter |
On HTTP 429 — free plan only (the paid endpoint does not send a Retry-After header) |
var info = await client.LookupAsync("8.8.8.8");
if (!info.Success)
{
if (info.HttpStatus == 429)
{
await Task.Delay(TimeSpan.FromSeconds(info.RetryAfter ?? 60));
// …retry
}
if (info.ErrorType == "network")
{
// DNS failure, connection refused, timeout, …
}
Console.Error.WriteLine($"Error: {info.Message}");
return;
}
Response shape
A successful response includes (depending on your plan and selected options):
info.Ip // "8.8.4.4"
info.Success // true
info.Type // "IPv4"
info.Country // "United States"
info.CountryCode // "US"
info.Region // "California"
info.City // "Mountain View"
info.Latitude // 37.3860517
info.Longitude // -122.0838511
info.Flag?.Emoji // "🇺🇸"
info.Connection?.Isp // "Google LLC"
info.Timezone?.Id // "America/Los_Angeles"
info.Currency?.Code // "USD"
info.Security?.Vpn // false (Business+)
info.Rate?.Remaining // 50155 (Basic+)
If you need a field that isn't modelled by IPWhoisResponse, or if you've
restricted the response with a custom Fields whitelist, info.Raw gives
you the underlying JsonElement:
var asnDescription = info.Raw
.GetProperty("connection")
.GetProperty("org")
.GetString();
For the full field reference, see the official API documentation.
Using with IHttpClientFactory
For ASP.NET Core (or any host that uses IHttpClientFactory), inject a
shared HttpClient so DNS rotation works properly:
services.AddHttpClient("ipwhois");
services.AddSingleton<IPWhoisClient>(sp =>
{
var http = sp.GetRequiredService<IHttpClientFactory>().CreateClient("ipwhois");
return new IPWhoisClient(
apiKey: builder.Configuration["IPWhois:ApiKey"],
options: null,
httpClient: http);
});
When you supply your own HttpClient, the library will not dispose of
it — that's the factory's job.
Cancellation
All async methods accept an optional CancellationToken:
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(2));
var info = await client.LookupAsync("8.8.8.8", cancellationToken: cts.Token);
Cancellation through the supplied CancellationToken — including
CancelAfter and constructors that take a TimeSpan — propagates as
OperationCanceledException. That's the standard .NET convention and
intentional: callers usually want a thrown exception to unwind the
call stack on cancellation.
If you'd rather have a deadline that comes back as Success = false
instead of throwing, configure HttpClient.Timeout (via the constructor
or SetTimeout()):
using var client = new IPWhoisClient("YOUR_API_KEY").SetTimeout(2);
var info = await client.LookupAsync("8.8.8.8");
// info.Success == false, info.ErrorType == "network" on timeout
Requirements
- .NET 6 / .NET 8 / .NET Standard 2.0 (covers .NET Framework 4.6.1+ and every modern .NET runtime)
System.Text.Json(built into .NET 6+; pulled in automatically on netstandard2.0)
Contributing
Issues and pull requests are welcome on GitHub.
License
MIT © ipwhois.io
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net5.0 was computed. net5.0-windows was computed. net6.0 is compatible. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 was computed. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 was computed. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
| .NET Core | netcoreapp2.0 was computed. netcoreapp2.1 was computed. netcoreapp2.2 was computed. netcoreapp3.0 was computed. netcoreapp3.1 was computed. |
| .NET Standard | netstandard2.0 is compatible. netstandard2.1 was computed. |
| .NET Framework | net461 was computed. net462 was computed. net463 was computed. net47 was computed. net471 was computed. net472 was computed. net48 was computed. net481 was computed. |
| MonoAndroid | monoandroid was computed. |
| MonoMac | monomac was computed. |
| MonoTouch | monotouch was computed. |
| Tizen | tizen40 was computed. tizen60 was computed. |
| Xamarin.iOS | xamarinios was computed. |
| Xamarin.Mac | xamarinmac was computed. |
| Xamarin.TVOS | xamarintvos was computed. |
| Xamarin.WatchOS | xamarinwatchos was computed. |
-
.NETStandard 2.0
- System.Text.Json (>= 8.0.5)
-
net6.0
- No dependencies.
-
net8.0
- No dependencies.
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 1.2.0 | 37 | 5/12/2026 |
See CHANGELOG.md for the full list of changes.