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/sdkThe 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 bothCents(integer minor units) andDisplay(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.423. 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:
| Error | When it fires | What to do |
|---|---|---|
*zyins.ConfigError | ISA_TOKEN missing | Set the env var |
*zyins.ValidationError | 400 from the API | Inspect Errors; fix the input |
*zyins.APIError | Any other 4xx/5xx | Match on Code (stable enum) |
*zyins.IdempotencyConflictError | Same Idempotency-Key, different body | Treat as a bug |
*zyins.RateLimitError | 429 | Sleep RetryAfter, retry |
6. Configuration
The defaults match what most consumers need:
| Default | Value | When to override |
|---|---|---|
| Base URL | Production | Set ISA_BASE_URL for staging or self-hosted |
| Timeout | 30s per request (or the ctx deadline, whichever is sooner) | Use context.WithTimeout |
| Retries | 3 attempts, exponential backoff, 5xx + 429 only | Per-call zyins.WithRetries(0) |
Idempotency-Key | UUID v4 minted per call | zyins.WithIdempotencyKey(...) for cross-process replays |
| Transport | net/http default | Inject 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
- Prequalify guide — full envelope, eligibility categories, premium mode grid, and field contract
- Custom sorting and grouping — override the default order, group by your own dimension
- Authentication guide — bearer token rotation, test vs. live
- Idempotency guide — how the SDK mints keys, when to bring your own
- Error catalog — every
codevalue, status mapping, remediation - Webhooks guide — verifying signed webhook deliveries with
subtle.ConstantTimeCompare - API Reference — every endpoint, every parameter, every response field
Need raw HTTP details (status, headers, request ID)? Every result carries RequestID and IdempotencyKey directly — the same provenance the response headers do.
Updated about 10 hours ago