Pricing
Reading premium and death benefit values off prequalify and quote responses. Every monetary value ships as integer cents plus a verbatim display string.
Pricing
Every monetary value the API returns ships as integer cents plus a verbatim display string. Use cents for math and comparisons; use display for rendering. You never parse "$1,084.20" yourself.
v3 introduces the Money primitive. Death benefits and budgets are
Money— anamount({cents, display}) paired with aperiod(nullfor lump-sum,"monthly"for requested budget). Premium is{amount, default_mode, modes}:amountis the headline default-mode value,default_modenames whichmodeskey it equals, andmodesis the carrier grid. Premium carries noperiod; the mode token carries the cadence. See v3 uniform pricing below. Existing v2 integrators keep working — see v2 legacy pricing at the end.
v3 uniform pricing
On /v3/prequalify and /v3/quote, every offer carries:
death_benefit—Moneywithperiod: null(a one-time lump sum) for life products (fex,term,preneed), andnullfor premium-only products (medsup), whose coverage value lives entirely inpricing[].premium. The key is always present on the wire —nullrather than omitted — so you can null-check it directly. Null-check it before reading.amount.budget—Moneywithperiod: "monthly". Present only on monthly-budget quotes; the requested budget, distinct from the solved premium.pricing[]— uniform table, one row per rate class. Each row haspremium(when eligible) as{amount, default_mode, modes}:amount({cents, display}) is the headline default-mode value,default_modenames whichmodeskey it equals, andmodesis the full carrier grid.
The Money primitive
death_benefit: # Money
amount: # AmountResponse
cents: integer # minor units (US cents)
display: string # carrier-formatted, e.g. "$25,000" or "$91.22"
period: null | "monthly" | "quarterly" | "semiannual" | "annual"
period: null— a one-time or lump-sum amount (death benefit).period: "monthly"— a recurring amount with a billing cycle (currently onlybudgetuses it; the other values are reserved).
premium.amount and every entry in premium.modes is an AmountResponse ({cents, display}). premium.default_mode is the modes key whose value equals amount. Premium carries no period — the mode token carries the cadence.
Example offer (face-amount request)
{
"object": "plan_offer",
"carrier": { "name": "Aetna Accendo" },
"product": { "id": "prod_d7b57156-3e83-506b-8936-0692c1193dc7", "name": "Aetna Accendo Final Expense", "class": "fex" },
"death_benefit": {
"amount": { "cents": 2500000, "display": "$25,000" },
"period": null
},
"pricing": [
{
"rate_class": "Preferred Plus",
"primary": true,
"rank": 1,
"eligibility": { "eligible": true, "category": "immediate", "reasons": [] },
"premium": {
"amount": { "cents": 4287, "display": "$42.87" },
"default_mode": "monthly",
"modes": {
"monthly": { "cents": 4287, "display": "$42.87" },
"annual": { "cents": 50116, "display": "$501.16" },
"quarterly": { "cents": 12614, "display": "$126.14" },
"semi-annual": { "cents": 24752, "display": "$247.52" }
}
}
},
{
"rate_class": "Standard",
"primary": false,
"rank": 4,
"eligibility": { "eligible": true, "category": "immediate", "reasons": [] },
"premium": {
"amount": { "cents": 5921, "display": "$59.21" },
"default_mode": "monthly",
"modes": { "monthly": { "cents": 5921, "display": "$59.21" } }
}
},
{
"rate_class": "Graded",
"primary": false,
"rank": 18,
"eligibility": { "eligible": true, "category": "graded", "reasons": [] },
"premium": {
"amount": { "cents": 8113, "display": "$81.13" },
"default_mode": "monthly",
"modes": { "monthly": { "cents": 8113, "display": "$81.13" } }
}
}
]
}
Example offer (monthly-budget request)
A monthly-budget offer carries a budget Money (period: "monthly") alongside the death_benefit the budget buys (period: null). Group multi-budget responses client-side by budget.amount.cents.
{
"object": "plan_offer",
"carrier": { "name": "Foresters" },
"product": { "id": "prod_e5f6a7b8-c9d0-4e1f-a2b3-c4d5e6f7a8b9", "name": "Foresters PlanRight", "class": "fex" },
"death_benefit": {
"amount": { "cents": 4372100, "display": "$43,721" },
"period": null
},
"budget": {
"amount": { "cents": 7500, "display": "$75.00" },
"period": "monthly"
},
"pricing": [
{
"rate_class": "basic",
"primary": true,
"rank": 3,
"eligibility": { "eligible": true, "category": "immediate", "reasons": [] },
"premium": {
"amount": { "cents": 7500, "display": "$75.00" },
"default_mode": "MONTHLY-EFT",
"modes": {
"MONTHLY-EFT": { "cents": 7500, "display": "$75.00" },
"ANNUAL": { "cents": 82500, "display": "$825.00" }
}
}
}
]
}
Reading pricing[]
pricing[]| Field | Read it for |
|---|---|
row.rate_class | Carrier-authored class label (Preferred Plus, Standard, Graded, …). Render verbatim. |
row.primary | Exactly one row per offer has primary: true. Render it as the headline. |
row.rank | Server-authoritative global rank across the response. Lower is better. Sort by it. |
row.eligibility.category | Closed enum: immediate | graded | rop | other. |
row.eligibility.eligible | true for offered rows. Ineligible rows surface by default with eligibility.eligible: false, a populated rate_class, and generic reasons; pass include_ineligible: false to omit them. |
row.eligibility.reasons | Generic, carrier-confidential strings. Safe to surface to agents. |
row.premium.amount.cents | Headline premium for this row (the default mode), integer cents. |
row.premium.amount.display | Verbatim carrier-formatted string for the headline. Render as-is. |
row.premium.default_mode | The modes key whose value equals amount — the carrier's canonical billing mode for this product. |
row.premium.modes | Open carrier-string map. Keys are carrier-supplied (monthly, MONTHLY-EFT, annual, …). Values are {cents, display}. |
row.premium.amount is the apples-to-apples comparison value — it's always the default mode's amount, so comparing row.premium.amount.cents across rows never mixes modes. row.premium.default_mode names which mode that is, so you can label the comparison without inspecting the full grid.
Code examples
// TypeScript — render the headline, build the comparison table.
import {
Isa, Sex, Height, Weight, NicotineDuration, Coverage, ProductSelection, Products,
} from 'isa-sdk';
const isa = await Isa.withBearer(undefined, undefined, { apiVersion: { prequalify: 'v3' } });
const { data } = await isa.zyins.prequalify({
applicant: {
dob: '1962-04-18', sex: Sex.Male,
height: Height.fromFeetInches(5, 10), weight: Weight.fromPounds(195),
state: 'NC', nicotineUse: { lastUsed: NicotineDuration.Never },
},
coverage: Coverage.faceValue(25_000),
products: ProductSelection.of([Products.Fex.AetnaAccendo]),
});
for (const offer of data.plans) {
const headline = offer.pricing.find(row => row.primary);
console.log(offer.carrier.name, headline?.rateClass, headline?.premium?.amount.display);
const others = offer.pricing.filter(row => !row.primary && row.eligibility.eligible);
for (const row of others) {
console.log(` ${row.rateClass}: ${row.premium?.amount.display} (${row.eligibility.category})`);
}
}
# Python — same shape. `envelope` is the prequalify result; offers live
# under envelope.data.plans.
for offer in envelope.data.plans:
headline = next(row for row in offer.pricing if row.primary)
print(offer.carrier.name, headline.rate_class, headline.premium.amount.display)
for row in offer.pricing:
if row.primary or not row.eligibility.eligible:
continue
print(f" {row.rate_class}: {row.premium.amount.display} ({row.eligibility.category})")
// Go — server picks the headline; never compute it client-side.
for _, offer := range result.Plans {
for _, row := range offer.Pricing {
if row.Primary {
fmt.Println(offer.Carrier.Name, row.RateClass, row.Premium.Amount.Display)
}
}
}
// C# — LINQ query for headline.
foreach (var offer in result.Plans) {
var headline = offer.Pricing.First(r => r.Primary);
Console.WriteLine($"{offer.Carrier.Name} {headline.RateClass} {headline.Premium.Amount.Display}");
}
// PHP — filter for headline.
foreach ($result->plans as $offer) {
$headline = array_values(array_filter($offer->pricing, fn($r) => $r->primary))[0];
echo "{$offer->carrier->name} {$headline->rate_class} {$headline->premium->amount->display}\n";
}
All money on /v3/prequalify and /v3/quote is integer cents + display string, including every entry in row.premium.modes.
Multi-amount requests: client-side grouping
The response is always a flat data.plans[] — single amount or many. On a multi-amount probe (Coverage.faceValues([…]) / Coverage.monthlyBudgets([…])) the same product appears once per requested amount. Group client-side by the requested dimension: face-amount offers key off deathBenefit.amount.cents, monthly-budget offers off budget.amount.cents. The byAmount helper picks the right dimension for you.
// TypeScript — group the flat plans[] by the requested amount.
import {
Isa, Sex, Height, Weight, NicotineDuration, Coverage, ProductSelection, Products,
} from 'isa-sdk';
import { byAmount } from 'isa-sdk/zyins';
const isa = await Isa.withBearer(undefined, undefined, { apiVersion: { prequalify: 'v3' } });
const { data } = await isa.zyins.prequalify({
applicant: {
dob: '1962-04-18', sex: Sex.Male,
height: Height.fromFeetInches(5, 10), weight: Weight.fromPounds(195),
state: 'NC', nicotineUse: { lastUsed: NicotineDuration.Never },
},
coverage: Coverage.faceValues([10_000, 25_000, 50_000]),
products: ProductSelection.of([Products.Term.FidelityLifeInstabrainTerm]),
});
for (const [, offers] of byAmount(data.plans)) {
console.log(`Amount ${offers[0].deathBenefit?.amount.display}`);
for (const offer of offers) {
const headline = offer.pricing.find(r => r.primary);
console.log(` ${offer.carrier.name}: ${headline?.premium?.amount.display}`);
}
}
# Python — group the flat plans[] by death_benefit.amount.cents.
# `envelope` is the prequalify result for a multi-amount probe.
from collections import defaultdict
by_dim = defaultdict(list)
for offer in envelope.data.plans:
# death_benefit is None for premium-only products (medsup) — they have no
# face-amount dimension to group on, so skip them here.
if offer.death_benefit is None:
continue
by_dim[offer.death_benefit.amount.cents].append(offer)
for cents, offers in by_dim.items():
head_db = offers[0].death_benefit
assert head_db is not None # narrowed: every offer in this bucket has one
print(f"Amount: {head_db.amount.display}")
for offer in offers:
headline = next(r for r in offer.pricing if r.primary)
if headline.premium is not None:
print(f" {offer.carrier.name}: {headline.premium.amount.display}")
// Go — group the flat plans[] by death_benefit.amount.cents.
byAmount := make(map[int64][]zyins.V3Offer)
for _, offer := range result.Plans {
// DeathBenefit is nil for premium-only products (medsup) — they have no
// face-amount dimension to group on, so skip them here.
if offer.DeathBenefit == nil {
continue
}
key := offer.DeathBenefit.Amount.Cents
byAmount[key] = append(byAmount[key], offer)
}
for _, offers := range byAmount {
fmt.Printf("Amount: %s\n", offers[0].DeathBenefit.Amount.Display)
for _, offer := range offers {
for _, row := range offer.Pricing {
if row.Primary && row.Premium != nil {
fmt.Printf(" %s: %s\n", offer.Carrier.Name, row.Premium.Amount.Display)
}
}
}
}
For monthly-budget requests, group by budget.amount.cents instead. The byAmount helper detects budget responses and switches the key automatically.
death_benefit is null for premium-only products
death_benefit is null for premium-only productsLife products (fex, term, preneed) pay out a coverage amount, so their death_benefit is a Money value. Premium-only products — medsup (Medicare Supplement) — have no payout; their value is the monthly premium in pricing[].premium. On medsup offers, death_benefit is null.
The key is always present on the wire, so null-check it — you don't have to test for a missing key:
// fex / term / preneed offer
"death_benefit": { "amount": { "cents": 2500000, "display": "$25,000" }, "period": null }
// medsup offer — premium-only, no payout coverage
"death_benefit": null
Null-check before reading .amount, and branch on product.type when you render:
import type { V3Offer } from 'isa-sdk';
declare const offer: V3Offer;
declare const ui: { coverageLabel: { textContent: string } };
// good — null-check, then read the coverage; medsup leads with premium instead
const headline = offer.pricing.find(r => r.primary);
if (offer.deathBenefit) {
ui.coverageLabel.textContent = offer.deathBenefit.amount.display; // "$25,000"
} else {
// medsup: no payout coverage — show the monthly premium as the headline
ui.coverageLabel.textContent = headline?.premium?.amount.display ?? '—';
}
// bad — assumes deathBenefit is always populated; throws on medsup
ui.coverageLabel.textContent = offer.deathBenefit!.amount.display;
from isa_sdk import Isa
from isa_sdk.zyins import PrequalifyRequest
isa = Isa.with_bearer()
request: PrequalifyRequest
envelope = isa.zyins.prequalify(request)
for offer in envelope.data.plans:
if offer.death_benefit is not None:
coverage = offer.death_benefit.amount.display # life products
else:
headline = next((r for r in offer.pricing if r.primary), None)
coverage = headline.premium.amount.display if headline and headline.premium else "—" # medsup
cents is for math, display is for humans
cents is for math, display is for humansNever parse display for computation. Never format cents for rendering.
import type { V3Premium } from 'isa-sdk';
declare const a: { premium: V3Premium };
declare const b: { premium: V3Premium };
declare const row: { premium: V3Premium };
declare const ui: { priceLabel: { textContent: string } };
// good — compare on cents, render display verbatim
const cheaper = a.premium.amount.cents < b.premium.amount.cents;
ui.priceLabel.textContent = row.premium.amount.display;
// bad — parsing display for math, formatting cents by hand
const wrong = parseFloat(a.premium.amount.display) < parseFloat(b.premium.amount.display);
ui.priceLabel.textContent = `$${(row.premium.amount.cents / 100).toFixed(2)}`;
Carrier display strings carry formatting (commas, decimal places, conventions) that cents loses. Render exactly what the carrier intends.
rate_class and mode keys are carrier-specific
rate_class and mode keys are carrier-specificBoth fields are verbatim from the carrier — never lowercased, aliased, or remapped.
Examples: a standard policy might be STANDARD or default; a preferred rate PREFERRED; a graded policy MODIFIED or GRADED depending on carrier convention.
Mode keys vary too: monthly vs MONTHLY-EFT vs PAC-EFT-MONTHLY. Treat both as opaque strings — surface them verbatim to agents.
v2 legacy pricing
The rest of this guide documents the legacy v2 wire shape: a top-level premium plus an other_offers[] array. v2 keeps working; new integrations should target v3.
Where prices appear
Two fields on every plan_offer carry a price-shaped value:
premium— what the applicant pays for the policydeath_benefit— what the policy pays out
Both follow the same {cents, display} shape. premium carries additional fields because policies are quoted across multiple payment frequencies and rate classes.
The premium shape
premium shape"premium": {
"cents": 10159,
"display": "$101.59",
"mode": "MONTHLY",
"rate_class": "PREFERRED",
"modes": {
"MONTHLY": { "cents": 10159, "display": "$101.59" },
"QUARTERLY": { "cents": 29914, "display": "$299.14" },
"SEMI-ANNUAL": { "cents": 58698, "display": "$586.98" },
"ANNUAL": { "cents": 112880, "display": "$1,128.80" }
}
}
| Field | Read it for |
|---|---|
cents | Arithmetic, persistence, sorting. Integer USD cents, rounded. |
display | UI rendering. Verbatim carrier-formatted string. |
mode | The active payment frequency for cents and display (e.g. MONTHLY). |
rate_class | The applicant's resolved rate class for this offer (e.g. PREFERRED, STANDARD). |
modes | All payment frequencies for this offer. Each value has its own cents and display. |
Use cents and display when you want the headline price. Use modes when you want to show alternate payment frequencies.
When eligibility.eligible is false, premium is null. Guard accordingly.
Multiple eligibility tiers in one response
A single product may quote at multiple eligibility tiers (immediate, graded, ROP). The "best" tier appears as the top-level premium. The others appear under other_offers[], each with its own {eligibility, premium, rank}:
{
"rank": 2,
"eligibility": { "category": "immediate", "coverage_tier": "PREFERRED", "eligible": true },
"premium": { "cents": 10159, "display": "$101.59", "mode": "MONTHLY", "rate_class": "PREFERRED", "modes": {...} },
"other_offers": [
{
"eligibility": { "category": "immediate", "coverage_tier": "STANDARD", "eligible": true },
"premium": { "cents": 12397, "display": "$123.97", ... },
"rank": 3
},
{
"eligibility": { "category": "rop", "coverage_tier": "MODIFIED", "eligible": true },
"premium": { "cents": 18243, "display": "$182.43", ... },
"rank": 10
},
{
"eligibility": { "eligible": false, "reasons": ["Applicant did not qualify at this tier"] },
"premium": null,
"rank": null
}
]
}
Use the top-level premium for the headline price. Iterate other_offers[] only when you need to show side-by-side tier comparisons.
Death benefit
death_benefit is also {cents, display}. It applies to the whole plan_offer and is not repeated per other_offer (the death benefit is a property of the product and the requested coverage amount, not the eligibility tier).
"death_benefit": { "cents": 2000000, "display": "$20,000.00" }
Reading premiums from a response
curl -s 'https://zyins.isaapi.com/v2/prequalify' \
-H "Authorization: Bearer $ISA_TOKEN" \
-H "Idempotency-Key: $(uuidgen)" \
-H 'Content-Type: application/json' \
-d @prequalify.json |
jq '.data.plans[] | select(.eligibility.eligible) | {carrier: .carrier.name, product: .product.name, premium: .premium.display}'
import { Isa } from 'isa-sdk';
import type { PrequalifyRequest } from 'isa-sdk';
const isa = await Isa.withBearer();
declare const req: PrequalifyRequest;
const envelope = await isa.zyins.prequalify(req);
for (const plan of envelope.data.plans) {
const headline = plan.pricing.find((row) => row.primary && row.eligibility.eligible);
if (!headline?.premium) continue;
console.log(plan.carrier.name, plan.product.name, headline.premium.amount.display);
console.log(" paid", headline.premium.defaultMode, "—", headline.premium.amount.cents, "¢");
}
from isa_sdk import Isa
from isa_sdk.zyins import PrequalifyRequest
isa = Isa.with_bearer()
req: PrequalifyRequest
envelope = isa.zyins.prequalify(req)
for plan in envelope.data.plans:
headline = next((row for row in plan.pricing if row.primary and row.eligibility.eligible), None)
if headline is None or headline.premium is None:
continue
print(plan.carrier.name, plan.product.display_name, headline.premium.amount.display)
print(f" {headline.premium.amount.cents}¢ ({headline.premium.default_mode})")
envelope, err := isa.Zyins.Prequalify(ctx, req)
if err != nil {
return err
}
for _, plan := range envelope.Data.Plans {
if !plan.Eligibility.Eligible || plan.Premium == nil {
continue
}
fmt.Println(plan.Carrier.Name, plan.Product.Name, plan.Premium.Display)
}
See also
- Prequalify — the main endpoint that returns priced offers.
POST /v3/quote— comparison-table quote at one or more coverage amounts.- Glossary —
rate_class,coverage_tier,mode, and other carrier-specific terms.
Updated about 8 hours ago