Lotus.Namsor.NET
0.1.1
dotnet add package Lotus.Namsor.NET --version 0.1.1
NuGet\Install-Package Lotus.Namsor.NET -Version 0.1.1
<PackageReference Include="Lotus.Namsor.NET" Version="0.1.1" />
<PackageVersion Include="Lotus.Namsor.NET" Version="0.1.1" />
<PackageReference Include="Lotus.Namsor.NET" />
paket add Lotus.Namsor.NET --version 0.1.1
#r "nuget: Lotus.Namsor.NET, 0.1.1"
#:package Lotus.Namsor.NET@0.1.1
#addin nuget:?package=Lotus.Namsor.NET&version=0.1.1
#tool nuget:?package=Lotus.Namsor.NET&version=0.1.1
Namsor.NET
A .NET 10 client for the NamSor v2 API: gender, origin, ethnicity, country of residence, US race ethnicity, and full name splitting.
Install
dotnet add package Lotus.Namsor.NET
Register once, inject once
services.AddNamsor(o =>
{
o.ApiKey = apiKey;
o.MaxCreditsPerCall = 500;
});
INamsorClient is the only thing registered and the only thing you inject. It is registered transient so IHttpClientFactory can rotate handlers; injecting it into a singleton captures one HttpClient for the life of the app and defeats that.
Every service hangs off the client:
public sealed class NameEnrichmentService(INamsorClient namsor, ILogger<NameEnrichmentService> logger)
{
public async Task EnrichAsync(string fullName, CancellationToken ct)
{
var split = await namsor.NameParser.ParseAsync(fullName, ct);
var first = split!.FirstLastName!.FirstName!;
var last = split.FirstLastName.LastName!;
var ethnicity = await namsor.Diaspora.ClassifyAsync(first, last, "US", ct);
var gender = await namsor.Gender.ClassifyAsync(first, last, ct);
var usage = await namsor.Admin.GetUsageAsync(ct);
logger.LogInformation("{Remaining} credits left", usage?.RemainingCredits);
}
}
| Property | Service | Credits per name |
|---|---|---|
namsor.Gender |
male / female inference | 1 |
namsor.Origin |
where the name comes from | 10 |
namsor.Diaspora |
ethnicity | 20 |
namsor.Country |
where the bearer probably lives now | 10 |
namsor.UsRace |
US race ethnicity | 10 |
namsor.NameParser |
split a full name | 1 |
namsor.Admin |
quota, status, service prices | free |
Batches are correlated by id, not by position
Nothing in the NamSor docs promises the response array comes back in request order, and every response echoes an id. Matching by array index would attach one person's ethnicity to another and never throw, so the client stamps an id on anything you leave blank and rebuilds results by dictionary lookup.
var results = await namsor.Diaspora.ClassifyBatchAsync(names, ct);
foreach (var r in results)
{
if (!r.Matched)
{
// NamSor returned nothing for this name; r.Value is null rather than a neighbour's answer
continue;
}
Save(r.Input.LastName, r.Value!.Ethnicity);
}
Batches over BatchSize (default 100) are chunked automatically, and results still come back in your order.
Geo variants are named, not overloaded (ClassifyGeoBatchAsync, ParseGeoBatchAsync). A collection expression has no type of its own, so overloading on element type would make ClassifyBatchAsync([]) ambiguous and impossible to compile.
Credits
The free plan is 2,500 credits a month, which is 2,500 genderized names but only 125 ethnicities. Two guards:
Splitting a full name and then classifying its ethnicity costs 21 per name, not 20, so the free plan covers 119 such names rather than 125.
var estimate = namsor.Estimate(names.Count, NamsorService.Diaspora);
if (estimate.Credits > budgetLeft) return;
and MaxCreditsPerCall, which throws NamsorBudgetExceededException before the request is sent, so a refused call costs nothing.
Errors
All derive from NamsorException.
| Situation | Exception |
|---|---|
| 401 | NamsorAuthenticationException |
| 403, quota or disabled key | NamsorQuotaExceededException |
| 404, 5xx, unparseable body | NamsorApiException |
| refused locally on budget | NamsorBudgetExceededException |
NamSor returns 403 for both an exhausted quota and a disabled key. Admin routes are free, so Admin.GetUsageAsync() tells you which.
Exceptions carry the endpoint and the response body, never the request headers, so the API key cannot reach a log through an error path.
Live smoke test
$env:NAMSOR_API_KEY = "..."
dotnet run --project samples/Namsor.Smoke # free admin routes only
dotnet run --project samples/Namsor.Smoke -- --billed # also spends 1 credit
It prints your quota, every service's real price, and any drift against the built-in cost table.
Scope
Covered: gender (4 variants), origin, diaspora, country of residence, US race ethnicity (including ZIP), name splitting, and all 5 admin routes.
Other api functionalities are not needed, but I'm open to implementing them.
Not covered: Indian names (caste, religion, subclassification), name type recognition, phone number format, and names corridor.
X-OPTION-EXPLANABILITY is also left out: it costs 50 credits per name on top of the classification, which is the entire monthly free quota on 50 names, and NamSor has to enable it per-account.
docs/api/ holds the captured API documentation, since NamSor publishes no fetchable OpenAPI spec.
AI
I used AI to build this API Wrapper.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 is compatible. 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. |
-
net10.0
- Microsoft.Extensions.Http (>= 10.0.0)
- Microsoft.Extensions.Logging.Abstractions (>= 10.0.0)
- Microsoft.Extensions.Options (>= 10.0.0)
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 |
|---|---|---|
| 0.1.1 | 122 | 8/16/2026 |