Fewer calls to compliance
When a program is dropped with reasons, the LO knows why. The compliance line stays quiet.
Stage 1 runs cheap program eligibility against the loan profile and discards programs the borrower can't take. Stage 2 runs the full pricing ladder against survivors only. Latency drops, cost drops, lock-day surprises drop to near zero.
Overview
Programs declare eligibility predicates: occupancy, loan type, amort type, FICO range, LTV range, DTI ceiling, doc type, state, AMI percent, and any custom field your investors send. Stage 1 evaluates every predicate against the loan profile, discards failures with explicit reasons, and emits the survivor set. Stage 2 only fires the rule engine against survivors, so an LO with 30 active investors might run the full ladder against 12 — with the other 18 explained, not silently dropped.
When a program is dropped, Stage 1 returns every predicate that failed, the program's threshold, and the loan's value. No 'we didn't quote you Investor X' silence.
Stage 1 is order-of-magnitude faster than Stage 2 because it's a predicate check, not a rule engine run. Use it as a UX gate — show eligibility before the LO even sees prices.
Pricing latency scales with the survivor count, not the total investor count. Adding investors does not slow the warm path.
The same predicate set runs in Stage 1 (filter) and as the engine's first-cut in Stage 2 (sanity). They cannot disagree.
Programs that restrict to ≤80% AMI use the AMI service's percent-of-AMI; the trace shows the AMI table version, the area, and the computed percent.
Stage 1 emits pricing.eligibility.computed; Stage 2 emits pricing.computed. Both carry the same correlationId so downstream consumers can join them.
How it works
Numbered steps from input to output. Each step maps to a specific subsystem you can inspect via OpenTelemetry.
Each program carries an eligibility block (FICO range, LTV range, DTI ceiling, doc type, state list, AMI cap, etc.). All data-driven; configured per investor in MySQL.
On every eligibility request, the engine fetches active programs, evaluates predicates, and partitions into eligible / ineligible.
Each failed predicate writes a human-readable reason. The UI surfaces them in the QuoteIneligibilityPanel; downstream BI consumes them as structured fields.
Calling /v1/pricing or /v1/pricing/mode runs Stage 1 internally and feeds survivors to the rule engine. The full ladder fires only against survivors.
The pricing workspace shows survivors above the fold and ineligibles in a collapsed panel — so LOs can answer 'why didn't Investor X quote' without leaving the screen.
Hands on
Live cURL sample — copy, paste, ship.
# Stage 1 only — fast, cheap, no full ladder
curl -X POST https://api.ratestack.com/v1/pricing/eligibility \
-H "X-API-Key: $RATESTACK_KEY" \
-H "Content-Type: application/json" \
-d '{
"borrower": { "fico": 685 },
"loan": { "loanAmount": 525000, "loanType": "CONFORMING", "amortizationType": "FIXED" },
"property": { "attachmentType": "DETACHED", "constructionMethod": "SITE_BUILT", "projectLegalStructureType": "NONE", "projectClassificationIdentifier": "NA", "pudIndicator": false, "financedNumberOfUnits": 1, "propertyEstateType": "FEE_SIMPLE", "occupancy": "PRIMARY", "state": "CA" },
"transaction":{ "type": "PURCHASE" },
"execution": { "lockPeriodDays": 30 }
}'
# Returns surviving programs, with reasons for the rejected ones
{
"eligible": [ { "programCode": "INV_42:CONF30", ... }, ... ],
"ineligible": [ {
"programCode": "INV_99:CONF30_HIB",
"reasons": [ "FICO 685 below program min 700",
"LTV 95.0 above program max 90.0" ]
} ]
}Why this matters
When a program is dropped with reasons, the LO knows why. The compliance line stays quiet.
Stage 1 is fast enough to run on every form change in the workspace. The LO sees the eligible set update as they type.
Two-stage means the borderline cases get discovered at first quote, not after the lock. Your secondary desk thanks you.
Frequently asked
The engine logs a warning and treats the predicate as unsatisfied (fail-closed). You see this in the trace and can fix it by enriching the loan input.
Yes. Investor-level overlays apply to all programs from that investor; program-level overlays apply to one program. Both sit in the same eligibility table.
Yes. Investor allowlists / blocklists apply before Stage 1 runs. Disabling an investor or program is a config change, not a deploy.
Yes. Eligibility is a pure function of (loan, programs, AMI snapshot). Same input → same output. Idempotency keys protect against duplicate emit.
Related capabilities
Ready to see it on your data?
We'll spin you a sandbox, load your actual ratesheets, and walk you through this capability against your top scenarios.