Prerequisites
- The .NET 6 SDK or later
- A C# project (
dotnet new consoleworks for trying it out) - An ip-api.io API key — get one free
Add the package
Add the official IpApiIo package from NuGet. It has zero package
dependencies — it's built on the framework's own HttpClient and
System.Text.Json.
dotnet add package IpApiIo
# ...or add to your .csproj:
# <PackageReference Include="IpApiIo" Version="1.*" /> Construct the client
Bring the namespace into scope and build one IpApiClient with your API key.
Read the key from an environment variable — never hardcode it. The API rejects keyless
requests with 401.
using IpApiIo;
var apiKey = Environment.GetEnvironmentVariable("IP_API_IO_KEY")!;
var client = new IpApiClient(apiKey);
// ... use the client ...
IpApiClient wraps a single HttpClient and is thread-safe —
register it as a singleton (or reuse one instance) instead of creating one per request.
In ASP.NET Core, add it once in Program.cs:
builder.Services.AddSingleton(
new IpApiClient(builder.Configuration["IpApiIo:Key"]!)); Look up an IP address
Call LookupAsync(ip) for any IPv4 or IPv6 address, or
LookupAsync() to resolve the caller's own IP. Responses are strongly-typed
objects; nullable fields are string? / double? — null-check
before use.
var info = await client.LookupAsync("8.8.8.8");
Console.WriteLine(info.Ip); // "8.8.8.8"
Console.WriteLine(info.Isp); // "Google LLC"
Console.WriteLine(info.Location.Country); // "United States"
Console.WriteLine(info.SuspiciousFactors.IsDatacenter); // True
// Resolve the caller's own IP:
var me = await client.LookupAsync();
Console.WriteLine(me.Ip); Country
and City are string? because they're absent for private ranges
or unrecognized addresses — use the null-coalescing operator
(info.Location.Country ?? "Unknown"). Boolean flags such as
info.SuspiciousFactors.IsVpn are plain bool values.
Detect VPN, proxy, Tor & batch lookups
The SuspiciousFactors object carries seven boolean fields on every
lookup — this is what sets ip-api.io apart from plain geolocation APIs. You get
security intelligence in the same call, at no extra cost.
var info = await client.LookupAsync("185.220.101.45"); // example Tor exit node
var f = info.SuspiciousFactors;
if (f.IsVpn || f.IsProxy || f.IsTorNode)
Console.WriteLine("Anonymized traffic — consider a CAPTCHA or block");
if (f.IsDatacenter)
Console.WriteLine("Cloud/datacenter IP — likely automated");
if (f.IsThreat)
Console.WriteLine("Active threat signal — block this request");
var risky = f.IsVpn || f.IsProxy || f.IsTorNode || f.IsThreat;
Need to check many IPs at once? LookupBatchAsync takes up to 100 addresses
in a single request:
var batch = await client.LookupBatchAsync(
new[] { "8.8.8.8", "1.1.1.1", "9.9.9.9" });
Console.WriteLine($"{batch.TotalProcessed} {batch.SuccessfulLookups}");
foreach (var (ip, result) in batch.Results)
Console.WriteLine($"{ip} {result.SuspiciousFactors.IsVpn}"); await client.RiskScoreAsync(ip) for a combined
fraud risk score from
0 to 100.
Production-ready error handling
The client throws typed exceptions that all derive from IpApiException and
never retries on its own, so on a rate limit
RateLimitException.Reset tells you exactly when to try again.
using IpApiIo;
using IpApiIo.Exceptions;
try
{
var info = await client.LookupAsync("8.8.8.8");
Console.WriteLine(info.Ip);
}
catch (RateLimitException ex)
{
Console.WriteLine($"Rate limited. Remaining: {ex.Remaining}. Resets at {ex.Reset}");
}
catch (AuthenticationException)
{
Console.WriteLine("Invalid or missing API key — get one free at https://ip-api.io");
}
catch (IpApiException ex)
{
// Invalid request, server error, or transport failure
Console.WriteLine($"ip-api.io error: {ex.Message}");
} RateLimitException.Reset (a Unix timestamp) to schedule the
next attempt. Every method also accepts a CancellationToken so you can wire
in request timeouts.
In an ASP.NET Core minimal API, run the lookup server-side and pass the real client IP
from the X-Forwarded-For header:
app.MapGet("/check", async (HttpContext ctx, IpApiClient client) =>
{
var clientIp = ctx.Request.Headers["X-Forwarded-For"]
.FirstOrDefault()?.Split(',')[0].Trim()
?? ctx.Connection.RemoteIpAddress?.ToString()
?? "0.0.0.0";
try
{
var info = await client.LookupAsync(clientIp);
return Results.Ok(new
{
ip = info.Ip,
country = info.Location.Country,
isVpn = info.SuspiciousFactors.IsVpn,
isThreat = info.SuspiciousFactors.IsThreat
});
}
catch (IpApiException)
{
// Treat ip-api.io as a non-critical dependency
return Results.Ok(new { ip = clientIp, country = (string?)null });
}
});