Skip to content
RateStack
Capability · Eligibility

Two-stage eligibility, the way pricing should have always worked.

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

What it is, in one paragraph

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.

  • Reasons, not silence

    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.

  • Cheap pre-flight

    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.

  • Stage 2 only on survivors

    Pricing latency scales with the survivor count, not the total investor count. Adding investors does not slow the warm path.

  • Same predicates, two callsites

    The same predicate set runs in Stage 1 (filter) and as the engine's first-cut in Stage 2 (sanity). They cannot disagree.

  • AMI integration

    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.

  • Two-stage events on the wire

    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

The pipeline, end to end.

Numbered steps from input to output. Each step maps to a specific subsystem you can inspect via OpenTelemetry.

  1. 1

    Programs declare predicates

    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.

  2. 2

    Stage 1 evaluates

    On every eligibility request, the engine fetches active programs, evaluates predicates, and partitions into eligible / ineligible.

  3. 3

    Reasons attach to ineligible

    Each failed predicate writes a human-readable reason. The UI surfaces them in the QuoteIneligibilityPanel; downstream BI consumes them as structured fields.

  4. 4

    Pass to Stage 2 if requested

    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.

  5. 5

    Use the same set in the UI

    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

Stage 1 eligibility-only request

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

The pain it removes.

Fewer calls to compliance

When a program is dropped with reasons, the LO knows why. The compliance line stays quiet.

UX: instant feedback

Stage 1 is fast enough to run on every form change in the workspace. The LO sees the eligible set update as they type.

Lock-day surprises

Two-stage means the borderline cases get discovered at first quote, not after the lock. Your secondary desk thanks you.

Frequently asked

Direct answers, no marketing spin.

What happens when a predicate is missing for a custom field?

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.

Can I have program-level overlays?

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.

Does Stage 1 honor my custom investor allowlist?

Yes. Investor allowlists / blocklists apply before Stage 1 runs. Disabling an investor or program is a config change, not a deploy.

Is the eligibility result idempotent for the same input?

Yes. Eligibility is a pure function of (loan, programs, AMI snapshot). Same input → same output. Idempotency keys protect against duplicate emit.

Ready to see it on your data?

Wire two-stage eligibility, the way pricing should have always worked. up to your real workflow.

We'll spin you a sandbox, load your actual ratesheets, and walk you through this capability against your top scenarios.

Eligibility — two-stage program eligibility pre-flight | RateStack