isa-sdk
v1.0.8
Published
Unified TypeScript SDK for the ISA platform APIs (zyins, rapidsign, proxy).
Readme
isa-sdk
Unified TypeScript SDK for the Best Plan Pro API — powered by the ZyINS engine.
Install
npm install isa-sdkHello world
import { Isa } from 'isa-sdk';
const isa = Isa.withBearer(); // reads ISA_TOKEN
const { data } = await isa.zyins.prequalify(req); // v3 by default
console.log(data.plans[0].carrier.name);Three bootstrap factories cover all auth contexts:
| Factory | Audience | Env defaults |
|---|---|---|
| Isa.withBearer() | Server-to-server | ISA_TOKEN |
| Isa.withLicense({ deviceId, orderId }) | BPP agent tools | ISA_LICENSE_KEYCODE, ISA_LICENSE_EMAIL |
| Isa.withSession() | Embedded forms | ISA_SESSION_ID, ISA_SESSION_SECRET |
First call in <15 lines
import {
Isa,
Sex,
State,
Height,
Weight,
NicotineDuration,
Coverage,
ProductSelection,
Products,
} from 'isa-sdk';
const isa = await Isa.withKeycode({
keycode: 'ABC-123-XYZ',
email: '[email protected]',
});
// The bare `isa.zyins.prequalify` facade routes to v3 by default and
// resolves to an envelope; destructure `data` to read the flat plans[].
const { data } = await isa.zyins.prequalify({
applicant: {
dob: '1962-04-18',
sex: Sex.Male,
state: State.NorthCarolina,
height: Height.fromFeetInches(5, 10),
weight: Weight.fromPounds(195),
nicotineUse: { lastUsed: NicotineDuration.Never },
},
coverage: Coverage.faceValue(25_000),
products: ProductSelection.of([Products.Term.FidelityLifeInstabrainTerm]),
});
for (const offer of data.plans) {
const headline = offer.pricing.find(row => row.primary);
console.log(`${offer.carrier.name} ${offer.product.name}: ${headline?.premium?.amount.display}`);
}Per-surface API versions
The ISA API is a federation of independently versioned surfaces. There is no
single "current" version; the SDK exposes no global shorthand. Every release
ships a frozen BundledApiVersions table recording which /vN each surface
targets:
import { BundledApiVersions } from 'isa-sdk';
console.log(BundledApiVersions);
// {
// prequalify: 'v3',
// quote: 'v3',
// datasets: 'v3',
// reference: 'v3',
// sessions: 'v1',
// branding: 'v1',
// cases: 'v1',
// }To pin a single surface without disturbing the rest, pass a per-surface
apiVersion override map. There is no default key and no string
shorthand — resolution is apiVersion[surface] ?? BundledApiVersions[surface]:
import { Isa } from 'isa-sdk';
const isa = await Isa.withKeycode(
{
keycode: 'ABC-123-XYZ',
email: '[email protected]',
},
undefined, // env reader — defaults to process.env
{ apiVersion: { quote: 'v2' } }, // pin only quote; everything else bundled
);prequalify / quote / datasets / reference default to v3 — the
flat plans[] shape, the Money primitive, and the reference adapters all
work out of the box. Pin apiVersion: { prequalify: 'v2' } only when a
consumer still needs the legacy v2 shape. See SDK syntax proposal §2.7.
Reference adapters
Three SDK adapters consume the /v3/datasets catalog: Autocorrect
(typo correction), Match (text → single canonical concept), and
Autocomplete (text → ranked suggestions). Each one ships a default
implementation and accepts a wholesale replacement at SDK construction.
Each one has a canonical guide on
docs.isaapi.com; the README only shows the
30-second tour.
| Adapter | Default impl | Guide |
|---|---|---|
| Autocorrector | DefaultAutocorrector (typo-map driven) | Autocorrect |
| MatchAlgorithm | DefaultMatchAlgorithm (_makeKey + exact lookup) | Match |
| AutocompleteAlgorithm | DefaultAutocompleteAlgorithm (6-bucket priority) | Autocomplete |
Wire-shape reference for the catalog the adapters consume: Reference catalog shape. Standalone-licensable terms: Licensing the datasets.
Autocorrect
isa.zyins.autocorrector is pre-wired against the ZyINS spelling
corrections. The mode option chooses between mid-typing guards
(keyup) and anti-duplication guards (submit).
import { Isa } from 'isa-sdk';
const isa = await Isa.withKeycode({
keycode: 'ABC-123-XYZ',
email: '[email protected]',
});
const corrected = isa.zyins.autocorrector.correct('hyprtension and losartn', {
mode: 'submit',
});
// → 'HYPERTENSION and LOSARTAN'For a custom typo corpus, use the generic kernel:
const myCorrector = isa.autocorrector.create({
typoMap: new Map([['ASMA', 'ASTHMA'], ['DIABEETUS', 'DIABETES']]),
});
myCorrector.correct('asma flare', { mode: 'submit' });
// → 'ASTHMA flare'Match
isa.zyins.{conditions,medications,concepts}.match() resolves free
text to a single canonical Concept. Unknown text never rejects — it
returns an UnknownConcept with inputText preserved, safe to send
straight to /v3/prequalify.
const hbp = isa.zyins.conditions.match('High Blood Pressure');
console.log(hbp.id, hbp.name, hbp.isKnown);
// cond_01KSR2XVXS8F3PQRJGFG91W51G High Blood Pressure true
const novel = isa.zyins.medications.match('NewExperimental XR 2026');
console.log(novel.isKnown, novel.inputText);
// false 'NewExperimental XR 2026'Autocomplete
isa.zyins.{conditions,medications,concepts}.autocomplete(text, opts)
returns ranked Suggestion[] using the 6-bucket priority algorithm
with within-bucket frequency boost. frequencies and kinds come
from the cached datasets bundle automatically.
const suggestions = await isa.zyins.conditions.autocomplete('high b', {
limit: 5,
});
suggestions.forEach(s => console.log(s.bucket, s.concept.name, s.score));
// 1 High Blood Pressure 12480
// 1 High Blood Sugar 3200Extending the default
Each Default* implementation is immutable and exposes a clone(overrides)
method plus a versionTag getter. Use clone() to swap one field
without restating the others:
import { DefaultAutocorrector } from 'isa-sdk';
// Start from the SDK-bundled corrector, override the typo map, keep
// the rest of the wiring (mode, onApplied sink, etc.) intact:
const tenant = isa.zyins.autocorrector.clone({
typoMap: new Map([['HBP', 'HIGH BLOOD PRESSURE']]),
versionTag: '2026.05.29-acme',
onApplied: (event) => analytics.track('autocorrect', event),
});
tenant.versionTag; // '2026.05.29-acme' — surfaces stale-corpus detectionDefaultAutocorrector.onApplied fires once per correction with the input
text, the corrected text, and the mode. Use it to surface telemetry
without subclassing.
DefaultMatchAlgorithm and DefaultAutocompleteAlgorithm follow the
same clone(overrides) + versionTag pattern.
Wholesale replacement
When the bundled algorithm is the wrong shape (e.g. fuzzy edit-distance,
hosted-LLM lookup), implement the interface directly and pass it on the
constructor. The minimal MatchAlgorithm is two lines:
import type { MatchAlgorithm, Concept } from 'isa-sdk';
// Exact-string match against `concept.name`, no key normalization.
class ExactNameMatch implements MatchAlgorithm {
match(text: string, candidates: readonly Concept[]): Concept | null {
return candidates.find((c) => c.name === text) ?? null;
}
}
const isa = await Isa.withKeycode({
keycode: 'ABC-123-XYZ',
email: '[email protected]',
autocorrector: new MyAutocorrector(), // Autocorrector
matchAlgorithm: new ExactNameMatch(), // MatchAlgorithm
autocompleteAlgorithm: new MyAutocompleteAlgorithm(), // AutocompleteAlgorithm
});The injected implementation replaces the default everywhere — pre-wired
facades (isa.zyins.conditions.match, isa.zyins.medications.autocomplete)
route through it. Same wholesale + decorator + composition pattern as
CaseStorage.
BundledApiVersions and the adapter wire
The default adapters consume the reference catalog. The /datasets
surface is pinned in BundledApiVersions:
import { BundledApiVersions } from 'isa-sdk';
BundledApiVersions.datasets; // 'v3' as of this SDK release
BundledApiVersions.prequalify; // 'v3' as of this SDK releaseOverride per-surface via the apiVersion map on Isa.withKeycode. The
adapters always read whichever /vN/datasets you pinned — no separate
adapter version. See api-version-pinning.
Case storage — bring your own
isa.zyins.cases.* routes through a CaseStorage adapter. The default is
the zero-knowledge store — ISA's servers only hold ciphertext and an opaque
ID. To plug a carrier-controlled store, pass your adapter at construction:
// `CarrierCaseStorage` is a placeholder for a consumer-supplied
// implementation of the `CaseStorage` interface — see the cases guide.
import { Isa, LicenseAuth, type CaseStorage } from 'isa-sdk';
declare const CarrierCaseStorage: new () => CaseStorage;
const isa = await Isa.create({
auth: LicenseAuth.fromEnv(),
caseStorage: new CarrierCaseStorage(), // optional; default = ZeroKnowledgeCaseStorage
});See cases guide for the full bring-your-own pattern.
Sub-namespaces
| Namespace | Access | Status |
|---|---|---|
| isa.zyins.* | isa.zyins.prequalify(req) | Live (license mode) |
| isa.rapidsign.* | isa.rapidsign.webhooks.verify(...) | Verifier stub (issue #38) |
| isa.proxy.* | internal | Phase 3 |
| isa.webhooks.* | top-level alias | Verifier stub |
Raw response variant
Every product method has a .withRawResponse sibling returning
{ data, response } with status, headers, and url:
// Continued from "First call in <15 lines" — `isa` and `req` are
// the ones defined there.
const { data, response } = await isa.zyins.prequalifyRaw(req);
console.log(response.headers['x-isa-request-id']);Debug logging
Set ISA_LOG=debug to stream request/response pairs to stderr. Sensitive
headers (Authorization, X-Device-Signature, X-Session-Signature) and
body fields (license, licenseKey, keycode, password, secret,
token) are redacted.
Concurrency safety
A single Isa instance carries no shared mutable state. Multiple
concurrent in-flight calls on one instance are safe — there's no need
to construct one client per request.
Tree-shaking
The package is annotated sideEffects: false. Modern bundlers
(esbuild, Rollup, Webpack) will drop unused product namespaces from
your bundle when you import only the names you use.
Migration from per-product packages
@isa-sdk/core, @isa-sdk/zyins, @isa-sdk/rapidsign, and
@isa-sdk/proxy (all 0.0.0) are retired in favor of this unified
package. See MIGRATION.md for the codemod and a
mapping table.
License lifecycle
License activation, check, and deactivation hang off isa.zyins.license
(singular — a device carries exactly one license). The facade fills
keycode/email/deviceId from the credential state used to
construct isa, so the common call is argument-free:
import { Isa } from 'isa-sdk';
const isa = await Isa.withKeycode({
keycode: 'ABC-123-XYZ',
email: '[email protected]',
});
const status = await isa.zyins.license.check();
// status.status: 'valid' | 'invalid' | 'inactive'