Quickstart: Go

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

Go Quickstart

Get a prequalify decision from a fresh Go program in under five minutes. The SDK uses idiomatic context.Context, typed errors via errors.As, no global state, and returns results in a single flat result.Plans slice.

1. Install

go get github.com/isa-sdk/sdk

The SDK targets Go 1.22+.

The unified entrypoint lives in the root package as sdk. Product surfaces live in subpackages: sdk/zyins for prequalify and quote, and sdk/catalog for typed product and state constants.

2. Hello world

Set the bearer token and run prequalify:

export ISA_TOKEN=<YOUR_ISA_TOKEN>
package main

import (
	"context"
	"fmt"

	sdk "github.com/isa-sdk/sdk"
	"github.com/isa-sdk/sdk/catalog"
	"github.com/isa-sdk/sdk/zyins"
)

func main() {
	isa, err := sdk.WithBearer("")
	if err != nil {
		panic(err)
	}

	height, err := zyins.NewHeight(5, 10) // 5 ft 10 in
	if err != nil {
		panic(err)
	}
	weight, err := zyins.NewWeight(195) // pounds
	if err != nil {
		panic(err)
	}
	coverage, err := zyins.NewFaceValueCoverage(25_000) // dollars — $25,000 face value
	if err != nil {
		panic(err)
	}
	// Products.Fex.AetnaAccendo carries the stable prod_<uuid> used on the wire.
	products, err := zyins.NewProductSelectionOf(catalog.Products.Fex.AetnaAccendo())
	if err != nil {
		panic(err)
	}

	result, err := isa.Zyins.Prequalify.Run(context.Background(), &zyins.PrequalifyRequest{
		Applicant: zyins.Applicant{
			DOB:         "1962-04-18",
			Sex:         zyins.SexMale,
			Height:      height,
			Weight:      weight,
			State:       catalog.StateNorthCarolina,
			NicotineUse: zyins.NicotineUsageInput{LastUsed: zyins.NicotineNever},
		},
		Coverage: coverage,
		Products: products,
	})
	if err != nil {
		panic(err)
	}

	// result.Plans is a flat slice — one entry per product.
	for _, offer := range result.Plans {
		for _, row := range offer.Pricing {
			if row.Primary && row.Premium != nil {
				fmt.Println(offer.Carrier.Name, offer.Product.Name, row.Premium.Amount.Display)
			}
		}
	}
}

Money inputs are integer dollars. zyins.NewFaceValueCoverage(25_000) means $25,000 of death benefit, not 25,000 cents. Outputs always carry both Cents (integer minor units) and Display (the verbatim carrier string), so you never have to format on the way out.

sdk.WithBearer("") reads ISA_TOKEN from the environment when Token is empty. If the token is missing, it returns a typed *zyins.ConfigError.

Run the example. The expected output is one line per qualifying offer:

Aetna Accendo Aetna Accendo Final Expense $87.42

3. A real example

A full prequalify + quote sequence for the canonical applicant: John Doe, born 1962-04-18, North Carolina, 5'10", 195 lb, no medications, against Aetna Accendo final expense at $25,000 of face value.

package main

import (
	"context"
	"fmt"
	"log/slog"
	"time"

	sdk "github.com/isa-sdk/sdk"
	"github.com/isa-sdk/sdk/catalog"
	"github.com/isa-sdk/sdk/zyins"
)

func main() {
	ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
	defer cancel()

	isa, err := sdk.WithBearer("")
	if err != nil {
		slog.Error("sdk init", "err", err)
		return
	}

	height, err := zyins.NewHeight(5, 10)
	if err != nil {
		slog.Error("height", "err", err)
		return
	}
	weight, err := zyins.NewWeight(195)
	if err != nil {
		slog.Error("weight", "err", err)
		return
	}
	coverage, err := zyins.NewFaceValueCoverage(25_000) // $25,000 face value (dollars, not cents)
	if err != nil {
		slog.Error("coverage", "err", err)
		return
	}

	john := zyins.Applicant{
		DOB:         "1962-04-18",
		Sex:         zyins.SexMale,
		Height:      height,
		Weight:      weight,
		State:       catalog.StateNorthCarolina,
		NicotineUse: zyins.NicotineUsageInput{LastUsed: zyins.NicotineNever},
	}

	products, err := zyins.NewProductSelectionOf(catalog.Products.Fex.AetnaAccendo())
	if err != nil {
		slog.Error("product selection", "err", err)
		return
	}

	// 1. Prequalify — best offer per product, with the uniform pricing[] table.
	pq, err := isa.Zyins.Prequalify.Run(ctx, &zyins.PrequalifyRequest{
		Applicant: john,
		Coverage:  coverage,
		Products:  products,
	})
	if err != nil {
		slog.Error("prequalify", "err", err) // see §5 for typed error unwrapping
		return
	}
	for _, offer := range pq.Plans {
		// DeathBenefit is nil for premium-only products (medsup); nil-check it.
		if offer.DeathBenefit != nil {
			fmt.Println(offer.DeathBenefit.Amount.Display) // "$25,000.00"
		}

		// Eligibility and premium live on each pricing[] row, not the offer.
		for _, row := range offer.Pricing {
			if row.Primary && row.Premium != nil {
				fmt.Println(
					offer.Carrier.Name,
					offer.Product.Name,
					row.RateClass,               // "graded", "immediate", ...
					row.Premium.Amount.Display,  // verbatim carrier string
					row.Premium.Amount.Cents,    // integer cents
				)
			}
		}
		// Alternate qualifying tiers are sibling rows in offer.Pricing.
		for _, row := range offer.Pricing {
			if row.Primary || !row.Eligibility.Eligible || row.Premium == nil {
				continue
			}
			fmt.Println("  also:", row.RateClass, row.Premium.Amount.Display)
		}
	}

	// 2. Quote — same flat plans[] shape, broader product set.
	if len(pq.Plans) == 0 {
		fmt.Println("no qualifying plans returned")
		return
	}
	q, err := isa.Zyins.Quote.Run(ctx, &zyins.QuoteRequest{
		Applicant: john,
		Coverage:  coverage,
		Products:  products,
	})
	if err != nil {
		slog.Error("quote", "err", err) // see §5 for typed error unwrapping
		return
	}
	fmt.Printf("quote returned %d plan(s)\n", len(q.Plans))
}

Both PrequalifyResult and QuoteResult are flat: result.Plans, result.RequestID, result.IdempotencyKey, and result.Livemode. Persist RequestID and IdempotencyKey alongside your business records.

Context propagation works as you'd expect — cancelling the context cancels the in-flight HTTP call.

4. Multi-amount face-value quote

Request several coverage amounts in one call with zyins.NewFaceValuesCoverage(...).

The response is a flat result.Plans — the same product appears once per requested amount. Group client-side with zyins.ByAmount, keyed by DeathBenefit.Amount.Cents.

package main

import (
	"context"
	"fmt"

	sdk "github.com/isa-sdk/sdk"
	"github.com/isa-sdk/sdk/catalog"
	"github.com/isa-sdk/sdk/zyins"
)

func main() {
	isa, err := sdk.WithBearer("")
	if err != nil {
		panic(err)
	}

	height, err := zyins.NewHeight(5, 10)
	if err != nil {
		panic(err)
	}
	weight, err := zyins.NewWeight(195)
	if err != nil {
		panic(err)
	}
	coverage, err := zyins.NewFaceValuesCoverage([]int{10_000, 25_000, 50_000})
	if err != nil {
		panic(err)
	}
	products, err := zyins.NewProductSelectionOf(catalog.Products.Term.FidelityLifeInstabrainTerm())
	if err != nil {
		panic(err)
	}

	result, err := isa.Zyins.Prequalify.Run(context.Background(), &zyins.PrequalifyRequest{
		Applicant: zyins.Applicant{
			DOB:         "1962-04-18",
			Sex:         zyins.SexMale,
			Height:      height,
			Weight:      weight,
			State:       catalog.StateNorthCarolina,
			NicotineUse: zyins.NicotineUsageInput{LastUsed: zyins.NicotineNever},
		},
		Coverage: coverage,
		Products: products,
	})
	if err != nil {
		panic(err)
	}

	// Flat plans, grouped client-side by DeathBenefit.Amount.Cents.
	for cents, offers := range zyins.ByAmount(result.Plans) {
		fmt.Printf("Amount %d cents: %d offer(s)\n", cents, len(offers))
		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)
				}
			}
		}
	}
}

5. Errors

Every SDK error is a typed value. Use errors.As to unwrap:

func handleError(err error) {
	var conflictErr *zyins.IdempotencyConflictError
	if errors.As(err, &conflictErr) {
		// Replayed key with a different body. Almost always a bug — log and bail.
		slog.Error("idempotency conflict",
			"key", conflictErr.Key,
			"first_seen", conflictErr.FirstSeenAt,
		)
		return
	}

	var rateLimitErr *zyins.RateLimitError
	if errors.As(err, &rateLimitErr) {
		// 429. Honor Retry-After if present.
		delay := rateLimitErr.RetryAfter
		if delay <= 0 {
			delay = time.Second
		}
		time.Sleep(delay)
		return
	}

	var validationErr *zyins.ValidationError
	if errors.As(err, &validationErr) {
		// 400 from the API. validationErr.Errors carries per-field details.
		for _, fe := range validationErr.Errors {
			slog.Error("field error", "field", fe.Field, "message", fe.Message)
		}
		return
	}

	var apiErr *zyins.APIError
	if errors.As(err, &apiErr) {
		// Any other 4xx / 5xx. Match on apiErr.Code (stable enum).
		slog.Error("api error",
			"code", apiErr.Code,
			"request_id", apiErr.RequestID,
		)
		return
	}

	var configErr *zyins.ConfigError
	if errors.As(err, &configErr) {
		// Returned synchronously when ISA_TOKEN is missing.
		slog.Error("config error", "detail", configErr.Detail)
		return
	}

	// Network or transport error.
	slog.Error("unexpected", "err", err)
}

The five you see first:

ErrorWhen it firesWhat to do
*zyins.ConfigErrorISA_TOKEN missingSet the env var
*zyins.ValidationError400 from the APIInspect Errors; fix the input
*zyins.APIErrorAny other 4xx/5xxMatch on Code (stable enum)
*zyins.IdempotencyConflictErrorSame Idempotency-Key, different bodyTreat as a bug
*zyins.RateLimitError429Sleep RetryAfter, retry

6. Configuration

The defaults match what most consumers need:

DefaultValueWhen to override
Base URLProductionSet ISA_BASE_URL for staging or self-hosted
Timeout30s per request (or the ctx deadline, whichever is sooner)Use context.WithTimeout
Retries3 attempts, exponential backoff, 5xx + 429 onlyPer-call zyins.WithRetries(0)
Idempotency-KeyUUID v4 minted per callzyins.WithIdempotencyKey(...) for cross-process replays
Transportnet/http defaultInject your own *http.Client 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.

7. What's next

Need raw HTTP details (status, headers, request ID)? Every result carries RequestID and IdempotencyKey directly — the same provenance the response headers do.