Python: Zero to first 200

A Python script that prequalifies John Doe for Final Expense coverage. Under 60 minutes for a backend developer with no insurance domain knowledge.

Python: Zero to first 200

Target: A Python script that prequalifies John Doe (NC, 64, male, no conditions) for Final Expense coverage and prints every qualifying plan with its monthly premium. Time budget: under 60 minutes from a blank directory.

Language requirement: Python 3.10 or newer with pip.


What you'll build

A single script — prequalify.py — that:

  1. Reads your API bearer token from an environment variable.
  2. Asks the ISA underwriting engine whether John Doe qualifies for Final Expense coverage.
  3. Prints each qualifying plan: carrier, product, eligibility category, and monthly premium.

What "prequalify" means: It's a fast eligibility screen. You send an applicant's demographics and a coverage request; the engine returns every plan the applicant qualifies for with a bucketed monthly premium. It is not a binding quote.


Prerequisites

RequirementWhy
Python 3.10 or newerThe SDK uses match statements and dataclasses(slots=True) available in 3.10+.
pipPackage installation.
An ISA API bearer tokenSee "Getting your test token" below.

Getting your test token

Complete checkout at checkout.isaapi.com and you'll receive an email with two tokens: isa_test_… and isa_live_…. Set the test token as an environment variable:

export ISA_TOKEN="<paste your isa_test_… key here>"

Test vs live. Every call made with a test token runs against the real engine with real carrier rules, but no binding decisions are issued. Switch to your live token when you are ready to ship.


1. Create a project directory

mkdir prequalify && cd prequalify
python3 -m venv .venv
source .venv/bin/activate   # Windows: .venv\Scripts\activate

2. Install the SDK

pip install isa-sdk

3. Hello world

Create prequalify.py:

from isa_sdk import Isa
from isa_sdk.zyins import (
    Applicant, NicotineDuration, NicotineUsageInput, Sex, Coverage, ProductSelection,
)
from isa_sdk.catalog import Products

def main() -> None:
    # Isa.with_bearer() reads ISA_TOKEN from the environment.
    isa = Isa.with_bearer()

    result = isa.zyins.prequalify(
        applicant=Applicant(
            dob="1962-04-18",
            sex=Sex.MALE,
            height_inches=70,
            weight_pounds=195,
            state="NC",
            nicotine_use=NicotineUsageInput(last_used=NicotineDuration.NEVER),
        ),
        coverage=Coverage.face_value(25_000),
        # Products.Fex.AetnaAccendo carries the stable prod_<uuid> used on the wire.
        products=ProductSelection.of(Products.Fex.AetnaAccendo),
    )

    for offer in result.data.plans:
        headline = next((row for row in offer.pricing if row.primary), None)
        if headline is not None and headline.premium is not None:
            print(
                f"{offer.carrier.name:<24} {offer.product.name:<40} "
                f"category={headline.eligibility.category:<10} "
                f"premium={headline.premium.amount.display:<10} ({headline.premium.amount.cents} cents)"
            )

if __name__ == "__main__":
    main()

Run it:

python prequalify.py

You should see a list of qualifying plans. No plans printed means the applicant did not qualify — confirm that ISA_TOKEN is set.


4. Add idempotency

The SDK auto-mints a UUID v4 idempotency key for every call. For a stateless script, that is fine. For production jobs where you want to replay a specific call safely, mint the key at job dispatch and reuse it on retry:

import uuid

key = str(uuid.uuid4())  # mint once; reuse on retry

result = isa.zyins.prequalify(request, idempotency_key=key)

If the server receives the same key with the same body within 24 hours, it returns the cached response. If the body differs, the server returns 409 idempotency_conflict.


5. Retries

The SDK automatically retries 5xx and 429 rate_limit_exceeded responses on exponential backoff with jitter, reusing the same idempotency key. You do not need to write retry logic.

If you are wrapping the SDK in your own job queue, mint the idempotency key at enqueue time, not at execution time. That way if the worker crashes and replays, the key is stable across attempts.


6. Handle errors

Use except to catch typed SDK errors:

import logging
import time
from isa_sdk.zyins import (
    IsaIdempotencyConflictError,
    RateLimitError,
    IsaApiError,
)

log = logging.getLogger(__name__)

try:
    result = isa.zyins.prequalify(request)
except IsaIdempotencyConflictError as e:
    # Key reused with different body — bug in call site.
    log.error("idempotency conflict: key=%s first_seen=%s", e.key, e.first_seen_at)
    raise
except RateLimitError as e:
    # SDK already retried; honor the retry-after window.
    retry_after = e.retry_after_seconds or 1.0
    log.warning("rate limited; retry after %.1fs", retry_after)
    time.sleep(retry_after)
    raise
except IsaApiError as e:
    # Any other API error — log request_id for support.
    log.error("api error: code=%s request_id=%s", e.code, e.request_id)
    raise

Save request_id with every log entry: It is the correlation ID that links your log line to the server-side trace, critical for debugging.


7. Run it

python prequalify.py

Expected output (exact plans depend on carrier availability in NC):

Aetna Accendo            Aetna Accendo Final Expense              category=immediate  premium=$87.42     (8742 cents)

premium.amount.cents is the integer in US cents (always); premium.amount.display is the verbatim carrier-formatted string. Render display, do arithmetic on cents. premium.default_mode names which premium.modes key the headline amount came from.


8. What just happened

When you called isa.zyins.prequalify(request), the SDK:

  1. Read ISA_TOKEN from the environment and set up the HTTP client.
  2. Minted a UUID v4 and sent Idempotency-Key: 550e8400-... on the request.
  3. Received an envelope response with request_id, idempotency_key, livemode: False, and data.plans.
  4. Returned a typed pydantic model — no JSON parsing needed on your side.

result.livemode == False confirms you ran against the test sandbox.


9. What's next