Quickstart: C#

Get a prequalify decision from a fresh .NET project in under five minutes.

C# Quickstart

Get a prequalify decision from a fresh .NET project in under five minutes. The SDK uses async/await throughout, typed exceptions on IsaException, immutable record value objects, and CancellationToken on every call.

1. Install

dotnet add package IsaSdk

The NuGet package is IsaSdk and the C# namespace is Isa.Sdk. The SDK targets both netstandard2.0 and net8.0 with nullable reference annotations enabled.

2. Hello world

Set the bearer token and run prequalify:

export ISA_TOKEN=<YOUR_ISA_TOKEN>
using Isa.Sdk;
using Isa.Sdk.Catalog;
using Isa.Sdk.Zyins;

var isa = IsaClient.WithBearer();

var result = await isa.Zyins.Prequalify.RunAsync(new PrequalifyRequest(
    Applicant: new Applicant
    {
        Dob          = "1962-04-18",
        Sex          = Sex.Male,
        HeightInches = 70,
        WeightPounds = 195,
        State        = "NC",
        NicotineUse  = new NicotineUsageInput { LastUsed = NicotineDuration.Never },
    },
    Coverage: Coverage.ByFaceValue(25_000),
    Products: new[] { Products.Fex.AetnaAccendo }));

// result.Plans is a flat list — one entry per product.
foreach (var offer in result.Plans)
{
    var headline = offer.Pricing.FirstOrDefault(r => r.Primary);
    if (headline?.Premium is not null)
        Console.WriteLine($"{offer.Carrier.Name} {offer.Product.Name} {headline.Premium.Amount.Display}");
}

IsaClient.WithBearer() reads ISA_TOKEN from the environment. No options, no config, no transport setup. Run it. The expected output is one line per qualifying offer:

Aetna Accendo Aetna Accendo Final Expense $87.42

Money inputs are integer dollars. Coverage.ByFaceValue(25_000) means $25,000 of death benefit, not 25,000 cents. Outputs always carry both Cents (integer minor units) and Display (the verbatim carrier string), so you never have to decide formatting on the way out.

3. A real example

A full prequalify + quote sequence for the canonical applicant: John Doe, born 1962-04-18, North Carolina, 5'10", 195 lb, no medications, against Aetna Accendo final expense at $25,000 of face value.

using Isa.Sdk;
using Isa.Sdk.Catalog;
using Isa.Sdk.Core;
using Isa.Sdk.Zyins;

using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(30));
var ct = cts.Token;

var isa = IsaClient.WithBearer();

var john = new Applicant
{
    Dob          = "1962-04-18",
    Sex          = Sex.Male,
    HeightInches = 70,
    WeightPounds = 195,
    State        = "NC",
    NicotineUse  = new NicotineUsageInput { LastUsed = NicotineDuration.Never },
};

var products = new[] { Products.Fex.AetnaAccendo };

// 1. Prequalify — best offer per product, with the uniform Pricing[] table.
var prequalify = await isa.Zyins.Prequalify.RunAsync(new PrequalifyRequest(
    Applicant: john,
    Coverage: Coverage.ByFaceValue(25_000),
    Products: products), ct);

foreach (var offer in prequalify.Plans)
{
    // DeathBenefit is null for premium-only products (medsup); null-check it.
    if (offer.DeathBenefit is not null)
        Console.WriteLine(offer.DeathBenefit.Amount.Display);  // "$25,000.00"

    // Eligibility and premium live on each Pricing[] row, not the offer.
    var headline = offer.Pricing.FirstOrDefault(r => r.Primary);
    if (headline?.Premium is not null)
    {
        Console.WriteLine(
            $"{offer.Carrier.Name} {headline.RateClass} " +   // "graded", "immediate", ...
            $"{headline.Premium.Amount.Display} " +           // verbatim carrier string
            $"{headline.Premium.Amount.Cents}");              // integer cents
    }
    // Alternate qualifying tiers are sibling rows in offer.Pricing.
    foreach (var row in offer.Pricing)
    {
        if (row.Primary || !row.Eligibility.Eligible || row.Premium is null) continue;
        Console.WriteLine($"  also: {row.RateClass} {row.Premium.Amount.Display}");
    }
}

// 2. Quote — same flat Plans[] shape, broader product set.
var quote = await isa.Zyins.Quote.RunAsync(new QuoteRequest(
    Applicant: john,
    Coverage: Coverage.ByFaceValue(25_000),
    Products: products), ct);

Console.WriteLine($"quote returned {quote.Plans.Count} plan(s)");

Both PrequalifyResult and QuoteResult expose a flat result.Plans list along with RequestId, IdempotencyKey, and Livemode. Save the RequestId and IdempotencyKey with your business records — they link your logs to the server trace.

The IsaClient is thread-safe. Construct one per process and reuse it:

var isa = IsaClient.WithBearer();
var tasks = applicants.Select(a => isa.Zyins.Prequalify.RunAsync(BuildRequest(a), ct));
var results = await Task.WhenAll(tasks);

4. Multi-amount face-value quote

Request several coverage amounts in one call with Coverage.ByFaceValues([…]). The response is always a flat result.Plans — the same product appears once per requested amount. Group client-side with Grouping.ByAmount(...), keyed by DeathBenefit.Amount.Cents.

using Isa.Sdk;
using Isa.Sdk.Catalog;
using Isa.Sdk.Zyins;

var isa = IsaClient.WithBearer();

var result = await isa.Zyins.Prequalify.RunAsync(new PrequalifyRequest(
    Applicant: new Applicant
    {
        Dob          = "1962-04-18",
        Sex          = Sex.Male,
        HeightInches = 70,
        WeightPounds = 195,
        State        = "NC",
        NicotineUse  = new NicotineUsageInput { LastUsed = NicotineDuration.Never },
    },
    Coverage: Coverage.ByFaceValues(new[] { 10_000, 25_000, 50_000 }),
    Products: new[] { Products.Term.FidelityLifeInstabrainTerm }));

// Flat plans, grouped client-side by DeathBenefit.Amount.Cents.
foreach (var (cents, offers) in Grouping.ByAmount(result.Plans))
{
    Console.WriteLine($"Amount {offers[0].DeathBenefit.Amount.Display} ({cents}c):");
    foreach (var offer in offers)
    {
        var headline = offer.Pricing.FirstOrDefault(r => r.Primary);
        if (headline?.Premium is not null)
            Console.WriteLine($"  {offer.Carrier.Name}: {headline.Premium.Amount.Display}");
    }
}

5. Errors

Every SDK exception inherits from IsaException. Match on the specific subclass — don't switch on ex.Message:

using Isa.Sdk;
using Isa.Sdk.Catalog;
using Isa.Sdk.Core;
using Isa.Sdk.Zyins;

var isa = IsaClient.WithBearer();
var ct = CancellationToken.None;
var request = new PrequalifyRequest(
    Applicant: new Applicant
    {
        Dob = "1962-04-18",
        Sex = Sex.Male,
        HeightInches = 70,
        WeightPounds = 195,
        State = "NC",
        NicotineUse = new NicotineUsageInput { LastUsed = NicotineDuration.Never },
    },
    Coverage: Coverage.ByFaceValue(25_000),
    Products: new[] { Products.Fex.AetnaAccendo });

try
{
    var result = await isa.Zyins.Prequalify.RunAsync(request, ct);
}
catch (IsaConfigException ex)
{
    // Thrown synchronously when ISA_TOKEN is missing at construction.
    Console.Error.WriteLine($"configuration error: {ex.Message}");
    throw;
}
catch (IsaIdempotencyConflictException ex)
{
    // Replayed key with a different body. Almost always a bug — log and bail.
    Console.Error.WriteLine($"idempotency conflict: key={ex.Key} firstSeen={ex.FirstSeenAt}");
    throw;
}
catch (IsaValidationException ex)
{
    // 400 from the API. ex.Param names the offending JSON pointer when known.
    Console.Error.WriteLine($"validation error: param={ex.Param} message={ex.Message}");
    throw;
}
catch (IsaRateLimitException ex)
{
    // 429. Honor Retry-After if present.
    await Task.Delay(ex.RetryAfter ?? TimeSpan.FromSeconds(1), ct);
}
catch (IsaException ex)
{
    // Any other 4xx / 5xx. Match on ex.CodeEnum (stable typed enum).
    Console.Error.WriteLine($"api error: code={ex.CodeEnum} requestId={ex.RequestId}");
    throw;
}

The five you see first:

ExceptionWhen it firesWhat to do
IsaConfigExceptionIsaClient.WithBearer() called with no token and no ISA_TOKENSet the env var or pass the token explicitly
IsaValidationException400 from the APIInspect Param; fix the input
IsaExceptionAny other 4xx/5xxMatch on CodeEnum (stable typed enum)
IsaIdempotencyConflictExceptionSame Idempotency-Key, different bodyTreat as a bug
IsaRateLimitException429Task.Delay(RetryAfter), retry

6. Configuration

The defaults match what most consumers need:

DefaultValueWhen to override
Base URLProductionSet ISA_BASE_URL for staging or self-hosted
Timeout30s per request (or the CancellationToken deadline)Pass a CancellationToken with a tighter deadline
Retries3 attempts, exponential backoff, 5xx + 429 onlyControlled by IsaClientOptions
TransportHttpClient long-lived instanceInject your own via IsaClientOptions { Transport = ... } for proxies, mocking, telemetry

If you're overriding more than one default in normal usage, file an issue — we probably got the defaults wrong.

7. What's next

Need raw HTTP details? Every result includes RequestId and IdempotencyKey — the same values from the response headers.