Skip to content
Finowl

C# / .NET · Email breach checks

Check email breaches in C# with HttpClient

The C# example posts an anonymous object to /v1/breaches/email with PostAsJsonAsync and deserializes the result into immutable records. Finowl returns breach fields in PascalCase, so a record declared as Breach(string Title, DateOnly BreachDate, string[] DataClasses, bool IsVerified) binds without attributes, and DateOnly gives you a real date instead of a string. Iterating result.Breaches then prints each breach and the categories of data it exposed.

A switch on response.StatusCode covers the cases that matter: 400 for an address that fails validation, 401 for a missing or wrong key, and 429, where the program reads the error code to tell a temporary rate limit from an exhausted credit balance. Rate limits trigger an await on the RetryAfter delta and another attempt. Anything else stops without retrying, because a 5xx lookup may already have run.

Steps

  1. 1

    Create a console project with dotnet new console and replace Program.cs with the example.

  2. 2

    Set FINOWL_API_KEY in your shell, or in launchSettings.json for local debugging only.

  3. 3

    Run dotnet run -- someone@example.com to look up an address.

  4. 4

    Read DataClasses on each Breach record to decide how strongly to warn the user.

  5. 5

    Move the call into an injected FinowlClient when you wire it into ASP.NET Core.

Install
dotnet new console -n FinowlQuickstart
cd FinowlQuickstart
Check email breaches in C# with HttpClient
using System.Net;
using System.Net.Http.Headers;
using System.Net.Http.Json;

var apiKey = Environment.GetEnvironmentVariable("FINOWL_API_KEY")
    ?? throw new InvalidOperationException("Set FINOWL_API_KEY first.");

using var http = new HttpClient
{
    BaseAddress = new Uri("https://api.finowl.co"),
    Timeout = TimeSpan.FromSeconds(15),
};
http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", apiKey);

var email = args.Length > 0 ? args[0] : "alex@example.com";

for (var attempt = 1; attempt <= 3; attempt++)
{
    using var response = await http.PostAsJsonAsync("/v1/breaches/email", new { email });

    switch (response.StatusCode)
    {
        case HttpStatusCode.OK:
            var result = await response.Content.ReadFromJsonAsync<EmailBreachResult>();
            if (result is null || !result.Breached)
            {
                Console.WriteLine($"No known breaches for {email}.");
                return;
            }
            foreach (var breach in result.Breaches)
            {
                var flag = breach.IsVerified ? "" : " (unverified)";
                Console.WriteLine($"{breach.Title}, {breach.BreachDate:yyyy-MM-dd}{flag}");
                Console.WriteLine($"  exposed: {string.Join(", ", breach.DataClasses)}");
            }
            return;
        case HttpStatusCode.BadRequest:
            Console.Error.WriteLine("Finowl rejected the address as invalid_input.");
            return;
        case HttpStatusCode.Unauthorized:
            Console.Error.WriteLine("FINOWL_API_KEY is missing or invalid.");
            return;
        case HttpStatusCode.TooManyRequests:
            var error = await response.Content.ReadFromJsonAsync<ErrorEnvelope>();
            if (error?.Error.Code == "quota_exceeded")
            {
                Console.Error.WriteLine("Out of Finowl credits.");
                return;
            }
            var wait = response.Headers.RetryAfter?.Delta ?? TimeSpan.FromSeconds(5);
            await Task.Delay(wait);
            break;
        default:
            Console.Error.WriteLine($"Finowl returned {(int)response.StatusCode}; not retrying.");
            return;
    }
}

record Breach(string Title, DateOnly BreachDate, string[] DataClasses, bool IsVerified);
record EmailBreachResult(string Email, bool Breached, Breach[] Breaches);
record ErrorEnvelope(ErrorDetail Error);
record ErrorDetail(string Code);

IN PRODUCTION

Production tips

  • In ASP.NET Core, run breach lookups from a BackgroundService fed by a Channel<T> rather than inside the registration endpoint, so the response to the user never waits on an external call.

  • Keep the API key in user secrets during development and in your host's secret store in production, exposed to the app as the FINOWL_API_KEY environment variable rather than in appsettings.json.

  • Cache results per normalized address with IMemoryCache or a distributed cache. Checking the same inbox on every login wastes credits and rarely changes the answer from one day to the next.

Each lookup costs 1 credit, with 100 free credits every month. See pricing and error codes.

A FEW GOOD QUESTIONS

Common questions

Check email breaches in C# with HttpClient

Grab an API key, paste the example and run it. 100 free credits every month.