Cases
Save and recall case state through a zero-knowledge store, or plug in your own HIPAA-compliant storage with one constructor option.
Cases
A case is a snapshot of an applicant's prequalification state — input, computed results, selected products — that you can persist, recall later, and share via a link. ISA exposes one resource (isa.zyins.cases) backed by a pluggable storage interface.
By default, cases go into ISA's zero-knowledge case store: the server only ever sees ciphertext and an opaque ID. If you already run a HIPAA-compliant or carrier-managed datastore, you can plug it in instead and ISA never sees the data at all.
This guide answers four questions integrators ask:
- What does ISA store?
- Can ISA read my customers' data?
- How do I share a case?
- Can I use my own storage instead?
What does ISA store?
When you use ISA's built-in case store, the server stores only the following per case:
- An opaque case ID (random UUID).
- A ciphertext blob (AES-GCM-256, encrypted client-side by the SDK).
- A non-PII product routing tag (
zyins,eapp, orrapidsign). created_atandexpires_attimestamps.
The server does not store:
- Plaintext applicant data, names, dates of birth, ZIP codes, or any identifier.
- Medical conditions, medications, premiums, or underwriting decisions.
- The encryption key.
- The recall token.
- Anything else that could identify the applicant.
The encryption key is generated client-side by the SDK at save time and returned to your caller as a recallToken. It never crosses the wire to ISA. Cases at rest are encrypted with AES-GCM-256; the database additionally lives behind cloud-provider at-rest encryption as defense in depth, but that is not the boundary — the boundary is the client-held key.
Compliance posture
ISA's built-in case store is designed so the server never holds plaintext PHI or PII — only ciphertext and an opaque ID. If you bring your own HIPAA-compliant system, you provide a CaseStorage implementation that keeps case data in your environment; ISA's SDK never reads or transmits the case body through that path.
Can ISA read my customers' data?
No. With the built-in store, ISA's server holds ciphertext and an opaque ID — no copy of the key, no way to derive one. If the database were compromised, an attacker would get ciphertext blobs and routing tags, not applicant data.
With your own CaseStorage adapter, ISA never sees the data at all. The SDK calls your put / get directly; the bytes never touch ISA infrastructure.
Save a case (zero-knowledge default)
isa.zyins.cases.save(record) returns a caseId and a recallToken. Capture both. To recall the case you must present both.
import { Isa } from 'isa-sdk';
const isa = await Isa.withBearer();
// Your application — durable storage of (id, recallToken).
declare function saveToYourSystem(args: { caseId: string; recallToken?: string }): void;
// `payload` is product-defined JSON — opaque to the SDK. Adapters
// route on `product`; the default zero-knowledge store encrypts
// `payload` client-side before the wire call.
const saved = await isa.zyins.cases.save({
product: 'zyins',
payload: {
applicant: {
firstName: 'John',
lastName: 'Doe',
dateOfBirth: '1962-04-18',
state: 'NC',
heightIn: 70,
weightLb: 195,
},
conditions: [],
medications: [],
},
});
// Persist both. Without the recallToken, the case cannot be recovered.
saveToYourSystem({ caseId: saved.id, recallToken: saved.recallToken });The recallToken is the AES key, base64-encoded. The SDK generated it locally; ISA's server never received it.
Recall a case
import { Isa } from 'isa-sdk';
const isa = await Isa.withBearer();
declare const caseId: string;
declare const recallToken: string;
const restored = await isa.zyins.cases.recall(caseId, recallToken);
// `payload` is opaque (typed `unknown`) — narrow it to your record shape.
interface ZyinsCase { applicant: { firstName: string } }
const payload = restored?.payload as ZyinsCase | undefined;
console.log(payload?.applicant.firstName); // "John"The SDK fetches the ciphertext from ISA's server, decrypts it locally with recallToken, and returns the record. If recallToken is wrong or missing, decryption fails locally — the server cannot fall back to plaintext because it never had it.
A deleted or expired case returns 404. This is intentional: deleted and expired cases are indistinguishable by design.
How do I share a case?
Place the recallToken in the URL fragment (the portion after #). Browsers do not send the fragment in HTTP request lines, the Referer header, or access logs, so the token travels with the share link but does not leak into server logs along the way.
https://your-app.example.com/case/c_a1b2c3d4?id=c_a1b2c3d4#k=<recallToken>
The recipient's browser loads your page; your page reads window.location.hash, hands the token to the SDK, and calls isa.zyins.cases.recall(...). ISA only ever sees the case ID in the path; the token is local to the browser.
A share link without a token is useless — the case cannot be decrypted without it. A token without a case ID is useless — there is nothing to apply it to.
Retention
Cases in ISA's built-in store are retained for 7 days by default, after which the server sweeper removes them. expires_at is returned with every case fetch so you know exactly when it goes away. To force-delete a case before its retention window, call:
import { type CaseStorage } from 'isa-sdk';
// Force-delete is handled adapter-side. `CaseStorage.delete` is optional,
// so guard for it. See the CaseStorage reference for per-adapter semantics.
declare const storage: CaseStorage;
declare const caseId: string;
await storage.delete?.(caseId);Bring-your-own adapters set their own retention; ISA has no role in it.
Can I use my own storage?
Yes. ISA's built-in store and your store implement the same interface, so the rest of your code is identical:
import {
Isa,
BearerAuth,
type CaseRecord,
type CaseStorage,
type CaseStoragePutResult,
} from 'isa-sdk';
class CarrierCaseStorage implements CaseStorage {
async put(record: CaseRecord): Promise<CaseStoragePutResult> {
const r = await fetch('https://cases.carrier.example.com/cases', {
method: 'POST',
body: JSON.stringify(record),
});
const { id } = (await r.json()) as { id: string };
return { id }; // adapter mints no client-side key material
}
async get(id: string): Promise<CaseRecord | null> {
const r = await fetch(`https://cases.carrier.example.com/cases/${id}`);
if (r.status === 404) return null;
return (await r.json()) as CaseRecord;
}
}
// caseStorage is an `Isa.create` option (it's per-instance config,
// not an auth-identity field).
const isa = await Isa.create({
auth: BearerAuth.fromEnv(),
caseStorage: new CarrierCaseStorage(),
});
// Same call as before — now routes to your store.
// ISA's server never sees the record.
await isa.zyins.cases.save({
product: 'zyins',
payload: { applicant: {} },
});When you provide a caseStorage, the SDK skips the built-in zero-knowledge store entirely. Encryption, retention, access control, and audit logging are all in your environment. The SDK still presents the same save / recall / delete surface; only the bytes move differently.
The default implementation is named ZeroKnowledgeCaseStorage — you do not need to reference it directly. Omitting caseStorage selects it.
Summary
| Concern | Built-in store | Bring-your-own store |
|---|---|---|
| Where ciphertext lives | ISA's database | Your store |
| Where plaintext lives | Nowhere on ISA infrastructure | Your store |
| Who holds the key | The integrator (returned as recallToken) | You decide |
| Retention | 7 days, then swept | You decide |
| SDK call site | isa.zyins.cases.save / .recall | isa.zyins.cases.save / .recall |
One interface, two storage backends, zero plaintext on ISA's servers either way.
Updated about 10 hours ago