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 IsaSdkThe 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.42Money inputs are integer dollars.
Coverage.ByFaceValue(25_000)means $25,000 of death benefit, not 25,000 cents. Outputs always carry bothCents(integer minor units) andDisplay(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:
| Exception | When it fires | What to do |
|---|---|---|
IsaConfigException | IsaClient.WithBearer() called with no token and no ISA_TOKEN | Set the env var or pass the token explicitly |
IsaValidationException | 400 from the API | Inspect Param; fix the input |
IsaException | Any other 4xx/5xx | Match on CodeEnum (stable typed enum) |
IsaIdempotencyConflictException | Same Idempotency-Key, different body | Treat as a bug |
IsaRateLimitException | 429 | Task.Delay(RetryAfter), retry |
6. Configuration
The defaults match what most consumers need:
| Default | Value | When to override |
|---|---|---|
| Base URL | Production | Set ISA_BASE_URL for staging or self-hosted |
| Timeout | 30s per request (or the CancellationToken deadline) | Pass a CancellationToken with a tighter deadline |
| Retries | 3 attempts, exponential backoff, 5xx + 429 only | Controlled by IsaClientOptions |
| Transport | HttpClient long-lived instance | Inject 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
- Prequalify guide — full envelope, eligibility categories, premium mode grid, and field contract
- Custom sorting and grouping — override the default order, group by your own dimension
- Authentication guide — bearer token rotation, test vs. live
- Idempotency guide — how the SDK mints keys, when to bring your own
- Error catalog — every
codevalue, status mapping, remediation - Webhooks guide — verifying signed webhook deliveries with
FixedTimeEquals - API Reference — every endpoint, every parameter, every response field
Need raw HTTP details? Every result includes RequestId and IdempotencyKey — the same values from the response headers.
Updated about 10 hours ago