Technical Guide

How to Integrate Address APIs in C#: Verification, Autocomplete & Geocoding

A comprehensive C# and .NET guide covering address verification, autocomplete, parsing, and geocoding with HttpClient, dependency injection, async patterns, and ASP.NET Core integration.

Sthan.io Team
Sthan.io Team
March 7, 2026 · 15 min read

Prerequisites

Before you begin, make sure you have:

  • .NET 6+ (LTS recommended -- .NET 6, .NET 8, or .NET 9) installed
  • Visual Studio 2022+ or VS Code with the C# extension
  • A Sthan.io account (free tier -- no credit card required)
  • Basic familiarity with C#, async/await, and HTTP concepts

System.Text.Json is built into .NET 6+, so no additional NuGet packages are required for basic usage. For a console app, create a new project:

dotnet new console -n SthanAddressDemo
cd SthanAddressDemo
2-Step Authentication:

Sthan.io uses a 2-step authentication flow. First, you obtain a JWT token by calling /Auth/Token with your profileName and profilePassword headers. Then, you use that token as a Bearer token in the Authorization header for all subsequent API calls.

Your credentials are available in your Sthan.io dashboard after signing in.

Authentication & Reusable Client

Let's start by building a reusable SthanApiClient class that handles authentication and token caching. This class implements IDisposable to properly clean up the underlying HttpClient.

Step 1: Define Response Models

All Sthan.io API responses are wrapped in a standard envelope. Define record types to deserialize them:

using System.Text.Json.Serialization;

// Standard API response envelope
public record SthanApiResponse<T>
{
    [JsonPropertyName("id")]
    public string Id { get; init; } = "";

    [JsonPropertyName("result")]
    public T? Result { get; init; }

    [JsonPropertyName("statusCode")]
    public int StatusCode { get; init; }

    [JsonPropertyName("isError")]
    public bool IsError { get; init; }

    [JsonPropertyName("errors")]
    public List<string> Errors { get; init; } = new();
}

// Auth token response
public record AuthTokenResult
{
    [JsonPropertyName("access_token")]
    public string AccessToken { get; init; } = "";

    [JsonPropertyName("expiration")]
    public DateTime Expiration { get; init; }
}

Step 2: Build the API Client

This client handles token acquisition, caching, and provides typed methods for each endpoint:

using System.Net.Http.Headers;
using System.Text.Json;

public class SthanApiClient : IDisposable
{
    private readonly HttpClient _httpClient;
    private readonly string _profileName;
    private readonly string _profilePassword;
    private string? _accessToken;
    private DateTime _tokenExpiration;

    private static readonly JsonSerializerOptions JsonOptions = new()
    {
        PropertyNameCaseInsensitive = true
    };

    public SthanApiClient(string profileName, string profilePassword)
    {
        _profileName = profileName;
        _profilePassword = profilePassword;
        _httpClient = new HttpClient
        {
            BaseAddress = new Uri("https://api.sthan.io")
        };
    }

    public async Task<string> GetTokenAsync()
    {
        // Return cached token if still valid (with 30s buffer)
        if (_accessToken is not null
            && DateTime.UtcNow < _tokenExpiration.AddSeconds(-30))
        {
            return _accessToken;
        }

        var request = new HttpRequestMessage(HttpMethod.Get, "/Auth/Token");
        request.Headers.Add("profileName", _profileName);
        request.Headers.Add("profilePassword", _profilePassword);

        var response = await _httpClient.SendAsync(request);
        response.EnsureSuccessStatusCode();

        var json = await response.Content.ReadAsStringAsync();
        var result = JsonSerializer.Deserialize<SthanApiResponse<AuthTokenResult>>(
            json, JsonOptions);

        if (result?.IsError == true || result?.Result is null)
        {
            throw new InvalidOperationException(
                $"Auth failed: {string.Join(", ", result?.Errors ?? new())}");
        }

        _accessToken = result.Result.AccessToken;
        _tokenExpiration = result.Result.Expiration;
        return _accessToken;
    }

    public async Task<T?> CallApiAsync<T>(string endpoint)
    {
        var token = await GetTokenAsync();

        var request = new HttpRequestMessage(HttpMethod.Get, endpoint);
        request.Headers.Authorization =
            new AuthenticationHeaderValue("Bearer", token);

        var response = await _httpClient.SendAsync(request);
        response.EnsureSuccessStatusCode();

        var json = await response.Content.ReadAsStringAsync();
        var result = JsonSerializer.Deserialize<SthanApiResponse<T>>(
            json, JsonOptions);

        if (result?.IsError == true)
        {
            throw new InvalidOperationException(
                $"API error: {string.Join(", ", result.Errors)}");
        }

        return result!.Result;
    }

    public void Dispose() => _httpClient.Dispose();
}

Step 3: Quick Test

Verify authentication works with a simple console app:

using var client = new SthanApiClient(
    "YOUR_PROFILE_NAME",
    "YOUR_PROFILE_PASSWORD"
);

var token = await client.GetTokenAsync();
Console.WriteLine($"Token acquired, expires: {token[..20]}...");
Security Tip: Never hardcode credentials in source code. Use .NET User Secrets for local development and environment variables or a vault service for production.

Address Autocomplete

The Address Autocomplete API returns matching US addresses as the user types. Use Uri.EscapeDataString() to safely encode the input.

Endpoint

GET /AutoComplete/USA/Address/{text}

C# Implementation

using var client = new SthanApiClient(
    "YOUR_PROFILE_NAME",
    "YOUR_PROFILE_PASSWORD"
);

var query = "123 Main";
var encoded = Uri.EscapeDataString(query);
var results = await client.CallApiAsync<List<string>>(
    $"/AutoComplete/USA/Address/{encoded}");

if (results is not null)
{
    Console.WriteLine($"Found {results.Count} suggestions:");
    foreach (var address in results)
    {
        Console.WriteLine($"  {address}");
    }
}

Example Response

{
    "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "result": [
        "123 Main St, Springfield, IL 62701",
        "123 Main St, Anytown, CA 90210",
        "123 Main St Apt 4, Boston, MA 02101"
    ],
    "statusCode": 200,
    "isError": false,
    "errors": []
}
Performance: The autocomplete API returns results in sub-100ms, making it ideal for real-time typeahead scenarios. Pair it with debouncing (200-300ms) on the client side for optimal UX.

Address Verification

The Address Verification API validates, corrects, and standardizes US addresses to USPS format. It returns detailed component data including deliverability status.

Endpoint

GET /AddressVerification/Usa/Single/{address}

Response Model

public record VerificationResult
{
    [JsonPropertyName("inputAddress")]
    public string InputAddress { get; init; } = "";

    [JsonPropertyName("streetAddress")]
    public string StreetAddress { get; init; } = "";

    [JsonPropertyName("city")]
    public string City { get; init; } = "";

    [JsonPropertyName("state")]
    public string State { get; init; } = "";

    [JsonPropertyName("zipCode")]
    public string ZipCode { get; init; } = "";

    [JsonPropertyName("zip4")]
    public string Zip4 { get; init; } = "";

    [JsonPropertyName("county")]
    public string County { get; init; } = "";

    [JsonPropertyName("dpvConfirmation")]
    public string DpvConfirmation { get; init; } = "";

    [JsonPropertyName("isValid")]
    public bool IsValid { get; init; }
}

C# Implementation

var address = "1600 pennsylvania ave nw washington dc";
var encoded = Uri.EscapeDataString(address);
var result = await client.CallApiAsync<VerificationResult>(
    $"/AddressVerification/Usa/Single/{encoded}");

if (result is not null)
{
    Console.WriteLine($"Valid: {result.IsValid}");
    Console.WriteLine($"Street: {result.StreetAddress}");
    Console.WriteLine($"City: {result.City}");
    Console.WriteLine($"State: {result.State}");
    Console.WriteLine($"ZIP: {result.ZipCode}-{result.Zip4}");
    Console.WriteLine($"County: {result.County}");
    Console.WriteLine($"DPV: {result.DpvConfirmation}");
}

Example Response

{
    "id": "b2c3d4e5-f6a7-8901-bcde-f12345678901",
    "result": {
        "inputAddress": "1600 pennsylvania ave nw washington dc",
        "streetAddress": "1600 PENNSYLVANIA AVE NW",
        "city": "WASHINGTON",
        "state": "DC",
        "zipCode": "20500",
        "zip4": "0005",
        "county": "DISTRICT OF COLUMBIA",
        "dpvConfirmation": "Y",
        "isValid": true
    },
    "statusCode": 200,
    "isError": false,
    "errors": []
}

Address Parsing

The Address Parser API breaks a free-form US address string into structured components (street number, street name, city, state, ZIP, etc.) without validating deliverability.

Endpoint

GET /AddressParser/USA/Single/{address}

Response Model

public record ParsedAddress
{
    [JsonPropertyName("streetNumber")]
    public string StreetNumber { get; init; } = "";

    [JsonPropertyName("preDirectional")]
    public string PreDirectional { get; init; } = "";

    [JsonPropertyName("streetName")]
    public string StreetName { get; init; } = "";

    [JsonPropertyName("streetSuffix")]
    public string StreetSuffix { get; init; } = "";

    [JsonPropertyName("postDirectional")]
    public string PostDirectional { get; init; } = "";

    [JsonPropertyName("unitDesignator")]
    public string UnitDesignator { get; init; } = "";

    [JsonPropertyName("unitNumber")]
    public string UnitNumber { get; init; } = "";

    [JsonPropertyName("city")]
    public string City { get; init; } = "";

    [JsonPropertyName("state")]
    public string State { get; init; } = "";

    [JsonPropertyName("zipCode")]
    public string ZipCode { get; init; } = "";
}

C# Implementation

var address = "456 N Oak Street Apt 12 Chicago IL 60611";
var encoded = Uri.EscapeDataString(address);
var parsed = await client.CallApiAsync<ParsedAddress>(
    $"/AddressParser/USA/Single/{encoded}");

if (parsed is not null)
{
    Console.WriteLine($"Number: {parsed.StreetNumber}");
    Console.WriteLine($"Pre-Dir: {parsed.PreDirectional}");
    Console.WriteLine($"Street: {parsed.StreetName}");
    Console.WriteLine($"Suffix: {parsed.StreetSuffix}");
    Console.WriteLine($"Unit: {parsed.UnitDesignator} {parsed.UnitNumber}");
    Console.WriteLine($"City: {parsed.City}");
    Console.WriteLine($"State: {parsed.State}");
    Console.WriteLine($"ZIP: {parsed.ZipCode}");
}

Example Response

{
    "id": "c3d4e5f6-a7b8-9012-cdef-123456789012",
    "result": {
        "streetNumber": "456",
        "preDirectional": "N",
        "streetName": "OAK",
        "streetSuffix": "ST",
        "postDirectional": "",
        "unitDesignator": "APT",
        "unitNumber": "12",
        "city": "CHICAGO",
        "state": "IL",
        "zipCode": "60611"
    },
    "statusCode": 200,
    "isError": false,
    "errors": []
}
Parsing vs. Verification: Use parsing when you need to break an address into components (e.g., populating separate form fields or normalizing data). Use verification when you need to confirm an address is deliverable and standardized to USPS format. You can combine both: parse first, then verify.

Forward Geocoding

The Forward Geocoding API converts a US street address into geographic coordinates (latitude and longitude).

Endpoint

GET /Geocoding/USA/Forward/{address}

Response Model

public record GeocodingResult
{
    [JsonPropertyName("latitude")]
    public double Latitude { get; init; }

    [JsonPropertyName("longitude")]
    public double Longitude { get; init; }

    [JsonPropertyName("formattedAddress")]
    public string FormattedAddress { get; init; } = "";

    [JsonPropertyName("accuracy")]
    public string Accuracy { get; init; } = "";
}

C# Implementation

var address = "1600 Amphitheatre Pkwy Mountain View CA 94043";
var encoded = Uri.EscapeDataString(address);
var geo = await client.CallApiAsync<GeocodingResult>(
    $"/Geocoding/USA/Forward/{encoded}");

if (geo is not null)
{
    Console.WriteLine($"Address: {geo.FormattedAddress}");
    Console.WriteLine($"Lat: {geo.Latitude}");
    Console.WriteLine($"Lon: {geo.Longitude}");
    Console.WriteLine($"Accuracy: {geo.Accuracy}");
}

Example Response

{
    "id": "d4e5f6a7-b8c9-0123-defa-234567890123",
    "result": {
        "latitude": 37.4224764,
        "longitude": -122.0842499,
        "formattedAddress": "1600 AMPHITHEATRE PKWY, MOUNTAIN VIEW, CA 94043",
        "accuracy": "Rooftop"
    },
    "statusCode": 200,
    "isError": false,
    "errors": []
}

ASP.NET Core Integration

For production ASP.NET Core applications, use IHttpClientFactory with dependency injection instead of creating HttpClient instances directly. This avoids socket exhaustion and integrates with the framework's service lifetime management.

Step 1: Configuration

Add your Sthan.io credentials to appsettings.json:

{
    "SthanApi": {
        "BaseUrl": "https://api.sthan.io",
        "ProfileName": "YOUR_PROFILE_NAME",
        "ProfilePassword": "YOUR_PROFILE_PASSWORD"
    }
}

Create a strongly-typed settings class:

public class SthanApiSettings
{
    public string BaseUrl { get; set; } = "https://api.sthan.io";
    public string ProfileName { get; set; } = "";
    public string ProfilePassword { get; set; } = "";
}

Step 2: Create the Service

using System.Net.Http.Headers;
using System.Text.Json;
using Microsoft.Extensions.Options;

public class SthanAddressService
{
    private readonly HttpClient _httpClient;
    private readonly SthanApiSettings _settings;
    private string? _accessToken;
    private DateTime _tokenExpiration;
    private readonly SemaphoreSlim _tokenLock = new(1, 1);

    private static readonly JsonSerializerOptions JsonOptions = new()
    {
        PropertyNameCaseInsensitive = true
    };

    public SthanAddressService(
        HttpClient httpClient,
        IOptions<SthanApiSettings> settings)
    {
        _httpClient = httpClient;
        _settings = settings.Value;
    }

    private async Task EnsureAuthenticatedAsync(
        CancellationToken ct = default)
    {
        if (_accessToken is not null
            && DateTime.UtcNow < _tokenExpiration.AddSeconds(-30))
            return;

        await _tokenLock.WaitAsync(ct);
        try
        {
            // Double-check after acquiring lock
            if (_accessToken is not null
                && DateTime.UtcNow < _tokenExpiration.AddSeconds(-30))
                return;

            var request = new HttpRequestMessage(
                HttpMethod.Get, "/Auth/Token");
            request.Headers.Add("profileName", _settings.ProfileName);
            request.Headers.Add(
                "profilePassword", _settings.ProfilePassword);

            var response = await _httpClient.SendAsync(request, ct);
            response.EnsureSuccessStatusCode();

            var json = await response.Content.ReadAsStringAsync(ct);
            var result = JsonSerializer
                .Deserialize<SthanApiResponse<AuthTokenResult>>(
                    json, JsonOptions);

            if (result?.IsError == true || result?.Result is null)
                throw new InvalidOperationException("Auth failed");

            _accessToken = result.Result.AccessToken;
            _tokenExpiration = result.Result.Expiration;
        }
        finally
        {
            _tokenLock.Release();
        }
    }

    public async Task<List<string>?> AutocompleteAsync(
        string query, CancellationToken ct = default)
    {
        await EnsureAuthenticatedAsync(ct);
        var encoded = Uri.EscapeDataString(query);
        return await GetAsync<List<string>>(
            $"/AutoComplete/USA/Address/{encoded}", ct);
    }

    public async Task<VerificationResult?> VerifyAddressAsync(
        string address, CancellationToken ct = default)
    {
        await EnsureAuthenticatedAsync(ct);
        var encoded = Uri.EscapeDataString(address);
        return await GetAsync<VerificationResult>(
            $"/AddressVerification/Usa/Single/{encoded}", ct);
    }

    public async Task<ParsedAddress?> ParseAddressAsync(
        string address, CancellationToken ct = default)
    {
        await EnsureAuthenticatedAsync(ct);
        var encoded = Uri.EscapeDataString(address);
        return await GetAsync<ParsedAddress>(
            $"/AddressParser/USA/Single/{encoded}", ct);
    }

    public async Task<GeocodingResult?> GeocodeAsync(
        string address, CancellationToken ct = default)
    {
        await EnsureAuthenticatedAsync(ct);
        var encoded = Uri.EscapeDataString(address);
        return await GetAsync<GeocodingResult>(
            $"/Geocoding/USA/Forward/{encoded}", ct);
    }

    private async Task<T?> GetAsync<T>(
        string endpoint, CancellationToken ct)
    {
        var request = new HttpRequestMessage(HttpMethod.Get, endpoint);
        request.Headers.Authorization =
            new AuthenticationHeaderValue("Bearer", _accessToken);

        var response = await _httpClient.SendAsync(request, ct);
        response.EnsureSuccessStatusCode();

        var json = await response.Content.ReadAsStringAsync(ct);
        var result = JsonSerializer
            .Deserialize<SthanApiResponse<T>>(json, JsonOptions);

        if (result?.IsError == true)
            throw new InvalidOperationException(
                $"API error: {string.Join(", ", result.Errors)}");

        return result!.Result;
    }
}

Step 3: Register in Program.cs

var builder = WebApplication.CreateBuilder(args);

// Bind configuration
builder.Services.Configure<SthanApiSettings>(
    builder.Configuration.GetSection("SthanApi"));

// Register typed HttpClient with IHttpClientFactory
builder.Services.AddHttpClient<SthanAddressService>((sp, client) =>
{
    var settings = builder.Configuration
        .GetSection("SthanApi")
        .Get<SthanApiSettings>()!;
    client.BaseAddress = new Uri(settings.BaseUrl);
    client.Timeout = TimeSpan.FromSeconds(30);
});

var app = builder.Build();

// Map endpoints (minimal API example)
app.MapGet("/api/address/autocomplete", async (
    string query,
    SthanAddressService addressService,
    CancellationToken ct) =>
{
    var suggestions = await addressService.AutocompleteAsync(query, ct);
    return Results.Ok(suggestions);
});

app.MapGet("/api/address/verify", async (
    string address,
    SthanAddressService addressService,
    CancellationToken ct) =>
{
    var result = await addressService.VerifyAddressAsync(address, ct);
    return Results.Ok(result);
});

app.MapGet("/api/address/geocode", async (
    string address,
    SthanAddressService addressService,
    CancellationToken ct) =>
{
    var result = await addressService.GeocodeAsync(address, ct);
    return Results.Ok(result);
});

app.Run();

Alternative: Controller-Based Approach

If you prefer MVC controllers over minimal APIs:

[ApiController]
[Route("api/[controller]")]
public class AddressController : ControllerBase
{
    private readonly SthanAddressService _addressService;

    public AddressController(SthanAddressService addressService)
    {
        _addressService = addressService;
    }

    [HttpGet("autocomplete")]
    public async Task<IActionResult> Autocomplete(
        [FromQuery] string query,
        CancellationToken ct)
    {
        var suggestions = await _addressService
            .AutocompleteAsync(query, ct);
        return Ok(suggestions);
    }

    [HttpGet("verify")]
    public async Task<IActionResult> Verify(
        [FromQuery] string address,
        CancellationToken ct)
    {
        var result = await _addressService
            .VerifyAddressAsync(address, ct);
        return Ok(result);
    }

    [HttpGet("parse")]
    public async Task<IActionResult> Parse(
        [FromQuery] string address,
        CancellationToken ct)
    {
        var result = await _addressService
            .ParseAddressAsync(address, ct);
        return Ok(result);
    }

    [HttpGet("geocode")]
    public async Task<IActionResult> Geocode(
        [FromQuery] string address,
        CancellationToken ct)
    {
        var result = await _addressService
            .GeocodeAsync(address, ct);
        return Ok(result);
    }
}

Error Handling & Best Practices

1. Never new Up HttpClient Directly

In ASP.NET Core, always use IHttpClientFactory. Creating HttpClient instances directly can lead to socket exhaustion under load because each instance manages its own connection pool. The factory manages HttpMessageHandler lifetimes and reuses connections efficiently.

// BAD - socket exhaustion risk
using var client = new HttpClient();

// GOOD - uses IHttpClientFactory (ASP.NET Core)
builder.Services.AddHttpClient<SthanAddressService>(client =>
{
    client.BaseAddress = new Uri("https://api.sthan.io");
});

// ACCEPTABLE - single static instance (console apps)
private static readonly HttpClient SharedClient = new()
{
    BaseAddress = new Uri("https://api.sthan.io")
};

2. URL-Encode All Address Inputs

Addresses can contain characters that break URLs (#, &, /, spaces). Always use Uri.EscapeDataString():

// BAD - "123 Main St #4" breaks the URL
var url = $"/AddressParser/USA/Single/{address}";

// GOOD - properly encoded
var url = $"/AddressParser/USA/Single/{Uri.EscapeDataString(address)}";

3. Token Caching with Thread Safety

In a multi-threaded web server, multiple requests might try to refresh the token simultaneously. Use SemaphoreSlim for thread-safe token refresh (as shown in the ASP.NET Core service above):

private readonly SemaphoreSlim _tokenLock = new(1, 1);

private async Task RefreshTokenAsync(CancellationToken ct)
{
    await _tokenLock.WaitAsync(ct);
    try
    {
        // Double-check pattern: another thread may have
        // refreshed while we waited for the lock
        if (IsTokenValid()) return;

        // ... refresh token ...
    }
    finally
    {
        _tokenLock.Release();
    }
}

4. Cancellation Token Support

Always accept and pass CancellationToken through your async call chain. This allows ASP.NET Core to cancel in-flight requests when the client disconnects:

public async Task<VerificationResult?> VerifyAddressAsync(
    string address, CancellationToken ct = default)
{
    // ct is passed to HttpClient.SendAsync, which will throw
    // OperationCanceledException if the request is cancelled
    var response = await _httpClient.SendAsync(request, ct);
    // ...
}

5. Retry Policies with Polly (Optional)

For production resilience, consider adding Polly retry and circuit breaker policies via Microsoft.Extensions.Http.Polly:

// Install: dotnet add package Microsoft.Extensions.Http.Polly

builder.Services.AddHttpClient<SthanAddressService>(client =>
{
    client.BaseAddress = new Uri("https://api.sthan.io");
})
.AddTransientHttpErrorPolicy(p =>
    p.WaitAndRetryAsync(3, attempt =>
        TimeSpan.FromSeconds(Math.Pow(2, attempt))))
.AddTransientHttpErrorPolicy(p =>
    p.CircuitBreakerAsync(5, TimeSpan.FromSeconds(30)));

6. Logging

Add structured logging to track API call performance and errors. Use ILogger<T> injected via DI:

using System.Diagnostics;

public class SthanAddressService
{
    private readonly ILogger<SthanAddressService> _logger;

    public SthanAddressService(
        HttpClient httpClient,
        IOptions<SthanApiSettings> settings,
        ILogger<SthanAddressService> logger)
    {
        _logger = logger;
        // ...
    }

    public async Task<VerificationResult?> VerifyAddressAsync(
        string address, CancellationToken ct = default)
    {
        var sw = Stopwatch.StartNew();
        try
        {
            var result = await GetAsync<VerificationResult>(
                $"/AddressVerification/Usa/Single/{Uri.EscapeDataString(address)}", ct);
            _logger.LogInformation(
                "Address verified in {ElapsedMs}ms: {IsValid}",
                sw.ElapsedMilliseconds, result?.IsValid);
            return result;
        }
        catch (Exception ex)
        {
            _logger.LogError(ex,
                "Address verification failed after {ElapsedMs}ms",
                sw.ElapsedMilliseconds);
            throw;
        }
    }
}

Key Takeaways

  • Use IHttpClientFactory in ASP.NET Core -- never new HttpClient() in web apps
  • Always URL-encode address inputs with Uri.EscapeDataString()
  • Cache JWT tokens and use SemaphoreSlim for thread-safe refresh
  • Pass CancellationToken through your entire async call chain
  • Store credentials in appsettings.json or User Secrets -- never hardcode them
  • Consider Polly for retry and circuit breaker policies in production

Share This Article

Frequently Asked Questions

Always use IHttpClientFactory in ASP.NET Core applications. Creating HttpClient instances directly can lead to socket exhaustion under load because each instance holds its own connection pool. IHttpClientFactory manages the underlying HttpMessageHandler lifetime and pools connections efficiently. For console apps or short-lived processes, a single static HttpClient instance is acceptable.

Sthan.io returns HTTP 429 (Too Many Requests) when you exceed your plan's rate limit. In C#, check the response status code and implement exponential backoff with retry logic. Libraries like Polly (via Microsoft.Extensions.Http.Polly) make this easy by configuring retry policies on your HttpClient. The free tier includes generous rate limits suitable for development and moderate production use. Check pricing plans for higher rate limits.

Yes, Sthan.io APIs integrate seamlessly with ASP.NET Core. Register a typed HttpClient with IHttpClientFactory in Program.cs, inject your service via dependency injection, and call the APIs asynchronously. This guide includes a complete ASP.NET Core integration example with IOptions configuration, service registration, and both minimal API and controller-based approaches.

Any .NET version that supports HttpClient works with Sthan.io APIs, but we recommend .NET 6 or later (LTS versions) for the best experience. .NET 6+ includes System.Text.Json built-in, supports top-level statements and file-scoped namespaces, and provides IHttpClientFactory out of the box. The code examples in this guide use modern C# features available in .NET 6+.

All Sthan.io API responses are wrapped in a standard envelope with id, result, statusCode, isError, and errors fields. Define a generic SthanApiResponse<T> record type that matches this structure, then use JsonSerializer.Deserialize<SthanApiResponse<T>>(json) from System.Text.Json. Use [JsonPropertyName] attributes to map JSON property names to C# property names.

Never hardcode credentials in source code. For local development, use .NET User Secrets (dotnet user-secrets). For production, use environment variables, Azure Key Vault, AWS Secrets Manager, or your cloud provider's secret management service. In ASP.NET Core, bind credentials to an IOptions<SthanApiSettings> configuration class loaded from appsettings.json or environment variables.

Yes. For Blazor Server, inject HttpClient or IHttpClientFactory just like any ASP.NET Core app and call Sthan.io APIs from your server-side code. For Blazor WebAssembly, you should route API calls through your own backend to keep credentials secure, similar to any frontend framework. The service patterns shown in this guide work directly in Blazor Server applications.

Ready to Get Started?

Get 100,000 free API calls per month. Verification, autocomplete, parsing, and geocoding. No credit card required.

Sthan.io Team
Written by Sthan.io Team

The Sthan.io engineering team builds and maintains address verification, parsing, geocoding, and autocomplete APIs processing millions of requests daily. With deep expertise in USPS postal standards, spatial data systems, and high-throughput API infrastructure, we help 2,000+ businesses improve address data quality and reduce failed deliveries.

Learn more about us