Quickstart: TypeScript

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

TypeScript Quickstart

Get a prequalify decision from a fresh Node project in under five minutes. No HTTP libraries, no auth plumbing, no boilerplate.

1. Install

npm install isa-sdk

The SDK targets Node 20+ and ships TypeScript declarations. ESM and CommonJS both work.

2. Hello world

Set the bearer token and run prequalify:

export ISA_TOKEN="<paste your isa_test_… key here>"
import {
  Isa, Sex, Height, Weight, NicotineDuration, Coverage, ProductSelection, Products,
} from 'isa-sdk';

async function main() {
  const isa = await Isa.withBearer();

  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]),
  });

  // data.plans is a flat array — one entry per product.
  for (const offer of data.plans) {
    const headline = offer.pricing.find(row => row.primary);
    console.log(
      offer.carrier.name,
      offer.product.name,
      headline?.rateClass,
      headline?.premium?.amount.display,
    );
  }
}

main().catch(console.error);

Isa.withBearer() is static async — it returns a Promise<Isa>, so await it inside an async function. It reads ISA_TOKEN from the environment. No transport setup, no request plumbing.

Top-level await works in Node 20+ ESM; wrapping in async function main() is portable across both ESM and CommonJS.

The response structure is Envelope<PrequalifyResult>. The offers live under .data.plans. The requestId, idempotencyKey, and livemode sit alongside for your records.

The inputs use typed builders, not raw JSON. Coverage.faceValue(25_000) takes integer dollars (not cents). Height.fromFeetInches(5, 10) and Weight.fromPounds(195) construct demographics. ProductSelection.of([…]) picks products from the typed Products catalog.

Each offer's deathBenefit and pricing[].premium.amount carry both cents (for arithmetic) and display (verbatim carrier string, e.g., "$87.42").

3. A real example

A full prequalify sequence for a real applicant. John Doe, born 1962-04-18, lives in North Carolina, 5'10", 195 lb, no medications, looking at Aetna Accendo final expense at $25,000 face value.

import {
  Isa, Sex, Height, Weight, NicotineDuration, Coverage, ProductSelection, Products,
} from 'isa-sdk';

async function main() {
  const isa = await Isa.withBearer();

  // Single face amount — $25,000 coverage in NC
  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,
      Products.Term.FidelityLifeInstabrainTerm,
    ]),
  });

  // data.plans is a flat array — one entry per product.
  // Each offer carries a deathBenefit Money value and a flat pricing[] table.
  for (const offer of data.plans) {
    console.log(offer.deathBenefit?.amount.display);  // "$25,000.00"

    const headline = offer.pricing.find(row => row.primary);
    console.log(
      offer.carrier.name,
      headline?.rateClass,
      headline?.premium?.amount.display,  // verbatim carrier string, e.g. "$87.42"
      headline?.premium?.amount.cents,    // integer cents for arithmetic
    );

    // Alternate qualifying tiers (e.g. graded when best is immediate) are sibling rows:
    for (const row of offer.pricing) {
      if (row.primary || !row.eligibility.eligible) continue;
      console.log('  also:', row.rateClass, row.premium?.amount.display, row.eligibility.category);
    }
  }
}

main().catch(console.error);

Every method resolves to an Envelope<T>. The offers are at .data.plans; requestId, idempotencyKey, and livemode sit alongside for your records.

4. Multi-amount face-value quote

Request several coverage amounts in one call with Coverage.faceValues([…]). The same product appears once per requested amount in the flat data.plans[] response.

Group client-side with the byAmount helper (or by deathBenefit.amount.cents) for a side-by-side comparison table.

import {
  Isa, Sex, Height, Weight, NicotineDuration, Coverage, ProductSelection, Products,
} from 'isa-sdk';
import { byAmount } from 'isa-sdk/zyins';

async function main() {
  const isa = await Isa.withBearer();

  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]),
  });

  // Flat plans[], grouped client-side by death_benefit.amount.cents.
  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}`);
    }
  }
}

main().catch(console.error);

5. Monthly-budget prequalify

Solve for coverage given a monthly budget. Use Coverage.monthlyBudget(75) (integer dollars per month).

Each offer returns both the requested budget and the deathBenefit the budget buys. The budget has period: "monthly", while deathBenefit has period: null.

import {
  Isa, Sex, Height, Weight, NicotineDuration, Coverage, ProductSelection, Products,
} from 'isa-sdk';

async function main() {
  const isa = await Isa.withBearer();

  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.monthlyBudget(75),  // $75/mo budget
    products: ProductSelection.of([Products.Fex.AetnaAccendo]),
  });

  for (const offer of data.plans) {
    const headline = offer.pricing.find(r => r.primary);
    console.log(
      `Budget: ${offer.budget?.amount.display} → Coverage: ${offer.deathBenefit?.amount.display}`,
    );
    console.log(`  Premium: ${headline?.premium?.amount.display} (${headline?.rateClass})`);
  }
}

main().catch(console.error);

6. Errors

Every SDK exception extends IsaError. Match on the specific subclass — don't switch on error.message:

import {
  Isa, IsaApiError, IsaIdempotencyConflictError, IsaConfigError,
  Sex, Height, Weight, NicotineDuration, Coverage, ProductSelection, Products,
} from 'isa-sdk';

async function run() {
  const isa = await Isa.withBearer();
  return 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]),
  });
}

try {
  await run();
} catch (err) {
  if (err instanceof IsaIdempotencyConflictError) {
    // Replayed key with a different body. Almost always a bug — log and bail.
    console.error('idempotency conflict:', err.key, 'first seen', err.firstSeenAt);
    throw err;
  }
  if (err instanceof IsaApiError) {
    // 4xx / 5xx from the API. Stable `code`, machine-readable, human-safe.
    console.error(err.code, err.message, 'request', err.requestId);
    throw err;
  }
  if (err instanceof IsaConfigError) {
    // Thrown synchronously when ISA_TOKEN is missing or malformed.
    console.error('configuration error:', err.message);
    throw err;
  }
  throw err;
}

The four you see first:

ErrorWhen it firesWhat to do
IsaConfigErrorIsa.withBearer() called with no token and no ISA_TOKEN in the environmentSet the env var or pass { token: '...' } explicitly
IsaApiErrorAny 4xx/5xx from the APIMatch on err.code (stable enum), log err.requestId
IsaIdempotencyConflictErrorSame Idempotency-Key, different bodyTreat as a bug; the SDK never produces this on retry
IsaErrorBase classCatch-all for typed SDK errors

7. Configuration

The defaults match what most consumers need. You should not have to override anything in the quickstart:

DefaultValueWhen to override
Base URLProductionSet ISA_BASE_URL for staging or self-hosted
Timeout30s per requestPass { timeout: ms } to a specific call
Retries3 attempts, exponential backoff, 5xx + 429 only{ retries: 0 } to disable per-call
Idempotency-KeyUUID v4 minted per callPass your own for cross-process replays
Version headerThe SDK's pinned API versionOverride per-call for staged rollouts
TransportBuilt-in fetchInject your own for proxies, mocking, telemetry

If you find yourself overriding more than one of these in normal usage, file an issue — the default is probably wrong.

8. What's next

  • Prequalify guide — full envelope, eligibility categories, premium mode grid, and field contract
  • Authentication guide — when to use bearer vs. license vs. session, lifecycle, rotation
  • Idempotency guide — how the SDK mints keys, when to bring your own
  • Errors reference — every code value, status mapping, remediation
  • Webhooks guide — verifying signed webhook deliveries with constant-time comparison
  • API Reference — every endpoint, every parameter, every response field

Need raw HTTP details (status, headers, request ID)? Every method has a [methodName]Raw sibling:

import {
  Isa, Sex, Height, Weight, NicotineDuration, Coverage, ProductSelection, Products,
} from 'isa-sdk';

const isa = await Isa.withBearer();
const { data, response } = await isa.zyins.prequalifyRaw({
  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]),
});
console.log(response.status, data.requestId);