FlexPoint Docs
EngineeringAPI Reference

POST /api/pricer/quote

Evaluate a loan scenario against every applicable Non-QM and FlexPoint program and receive ranked eligible offers plus structured rejection reasons.

Endpoint

POST /api/pricer/quote
Content-Type: application/json
Authorization: Bearer <FLEXRATE_INTERNAL_TOKEN>

A single call evaluates the scenario against every program on the most recent rate sheet of every lender (Non-QM + FlexPoint today), returns priced offers ranked best-price-first across both lenders, and structured rejection reasons for every (lender, program, doc_type) tuple that didn't qualify. Every eligible offer and rejection is tagged with lender_slug so the consumer can group or filter by lender. Omit program_slug and doc_type to evaluate the full catalog.

Request Body

All fields are JSON. Required fields are marked with *. Defaults reflect what the API substitutes when a field is omitted — they are the production defaults, not placeholders.

Program filters

FieldTypeValuesDefaultDescription
program_slugstringSee /api/pricer/programsnullLimit evaluation to one program. Omit to evaluate all.
doc_typestringe.g. full_doc, bank_statements, dscrnullLimit to one doc type within the selected program.
doc_seasoningstring12mo, 24monullDoc seasoning for Full Doc / Jumbo. null preserves the legacy behavior of matching the 12-month row.

Loan product

FieldTypeValuesDefaultDescription
term *integer10, 15, 20, 25, 30, 40Amortisation term in years.
purpose *stringpurchase, rate_term_refi, cash_out_refiLoan purpose.
comp_typestringlender_paid, borrower_paidlender_paidCompensation type.
margin_bpsinteger010000Compensation margin in basis points. Applied as a rate-side adjustment (does not move price).

Borrower

FieldTypeValuesDefaultDescription
credit_score *integer500850Representative credit score (middle of tri-merge).
purchase_price *number50000Purchase price or appraised value in whole dollars.
loan_amount *number50000Loan amount in whole dollars.
dtinumber06040.0Debt-to-income ratio as a percentage. Default 40.0 sits below every Non-QM DTI tier (lowest tier starts at 43.01%) so scenarios that don't specify DTI take no tier adjustment.
mtg_lates_12mointeger0, 1, 2, 30Mortgage late payments in the last 12 months. 3 means "3 or more".
housing_eventstring | nullbk_chapter_7, bk_chapter_13, foreclosure, short_sale, deed_in_lieunullMost recent adverse housing event, if any.
housing_event_months_agointeger | null0nullMonths since the housing event was discharged or completed. Should be set if housing_event is set.
citizenshipstringus_citizen, permanent_resident, non_permanent_resident, itin, foreign_nationalus_citizenBorrower citizenship / documentation status.
lien_positionstring1st, 2nd1stLien position.

Property

FieldTypeValuesDefaultDescription
occupancystringowner_occupied, second_home, non_owner_occupied, investmentowner_occupiedProperty occupancy type.
property_typestringsfr, 2_unit, 3_4_unit, 5_8_unit, condo, warrantable_condo, non_warrantable_condo, condotel, townhome, mixed_use, manufacturedsfrProperty type.
statestring2-letter US state codeCAProperty state. Uppercased server-side.
zipstring5-digit ZIP""Property ZIP code.

Pricing options

FieldTypeValuesDefaultDescription
ppp_termstringNo PPP, 1YR PPP, 2YR PPP, 3YR PPP, 5YR PPPNo PPPPrepayment penalty period. Must match a term in the program's PPP table.
ppp_table_namestring | nulle.g. 5% Fixed Fee, 6 mo InterestnullPPP table selector when a program ships multiple tables (Full Doc / Expanded Doc / DSCR ship both). null uses the first table found.
lock_daysinteger15, 30, 4530Rate lock period in days.

DSCR (investment / rental properties)

FieldTypeValuesDefaultDescription
dscr_rationumber | null010nullDebt-service coverage ratio. Required for DSCR, Multi-Unit, and Foreign National programs. Pass null for income-qualified programs (Full Doc, Expanded Doc, ITIN).

The DSCR ratio determines which LTV tier applies and which DSCR-tier LLPA row fires. DSCR ≥ 1.00 unlocks higher LTV limits and better pricing than DSCR < 1.00. Passing the wrong value (or omitting it on a DSCR program) produces an inaccurate result.

Risk & adjustment toggles

These fields each map 1:1 to a row in the "Miscellaneous Adjustments" / "Other Adjustments" LLPA grids. Multiple toggles fire independently on the same scenario, so a borrower can incur, say, escrow_waiver + interest_only + housing_history=1x30x12 adjustments simultaneously.

FieldTypeValuesDefaultFires LLPA row
escrow_waiverbooleantrue, falsefalse"Escrow waiver"
interest_onlybooleantrue, falsefalse"Interest Only" (note: term=40 already covers "40 Year Maturity")
housing_historystring | null1x30x12, 0x60x12, 0x90x12nullMatches verbatim by row label
recent_credit_eventbooleantrue, falsefalseFC/SS/DIL/BK<48M
short_term_rentalbooleantrue, falsefalse"Short-Term Rental" (DSCR programs)

If any toggle hits an NA cell in the LLPA grid, the offer is rejected with key: "llpa_na" rather than priced — NA is a hard eligibility cut-off, not a zero adjustment.


Response

{
  "rate_sheet_date": "2026-05-20",
  "eligible_count": 25,
  "rejected_count": 8,
  "eligible": [ /* OfferObject[] */ ],
  "rejected": [ /* RejectionObject[] */ ]
}
FieldTypeDescription
rate_sheet_datestring | nulleffective_at date of the rate sheet that produced this quote (YYYY-MM-DD).
eligible_countintegerNumber of eligible offers across all programs.
rejected_countintegerNumber of (program, doc_type) pairs that didn't produce any eligible offers.
eligible[]arrayPriced offers, sorted descending by final_price — the first offer is the most favourably priced for the borrower.
rejected[]arrayOne entry per (program, doc_type) that produced no offers, with structured rejection reasons.

eligible[] — Offer object

Each entry is one note rate on one program's ladder (or one rate-block row for FlexPoint) that passed every eligibility check.

{
  "lender_slug": "non-qm",
  "program_slug": "dscr",
  "program_name": "DSCR",
  "doc_type": "dscr",
  "rate": 9.125,
  "final_rate": 9.125,
  "base_price": 100.500,
  "price_adjustment": -1.250,
  "final_price": 99.250,
  "max_ltv": "85.00",
  "max_cltv": "85.00",
  "adjusters": [
    { "category": "FICO/CLTV",                 "row_label": "740 - 759",      "band": "70.01 - 75%", "value": -0.25, "source": "llpa" },
    { "category": "Purpose",                   "row_label": "Cash-Out Refi",  "band": null,          "value": -1.00, "source": "llpa" },
    { "category": "Miscellaneous Adjustments", "row_label": "Escrow waiver",  "band": null,          "value":  0.00, "source": "llpa" }
  ],
  "caps_applied": [],
  "term": 30,
  "lock_days": 30,
  "ppp_term": "No PPP"
}
FieldTypeDescription
lender_slugstring | nullLender that owns the program: "non-qm" or "flexpoint".
program_slugstringProgram identifier.
program_namestringHuman-readable program name.
doc_typestringDoc type within the program. FlexPoint agency programs default to "full_doc" because no doc-type registry is shipped on the sheet.
ratenumberNote rate from the ladder (e.g. 9.125).
final_ratenumberrate plus any rate-side adjustments (currently margin_bps).
base_pricenumberBase price for rate from rate_ladder_entries.
price_adjustmentnumberNet of every LLPA cell that fired (positive = add-on, negative = discount).
final_pricenumberbase_price + price_adjustment, after pricing caps and floors. Higher is better.
max_ltvstringMaximum LTV from the matrix row that matched this scenario (decimal as string).
max_cltvstringMaximum CLTV from the matrix row that matched this scenario.
adjusters[]arrayEvery LLPA cell that contributed to price_adjustment. Order is insertion order from the engine — meaningful for debugging but not contractual.
adjusters[].categorystringLLPA grid name (e.g. FICO/CLTV, Purpose, Doc Type, Property Type, Occupancy, Product, DSCR, Miscellaneous Adjustments).
adjusters[].row_labelstringExact row_label from llpa_adjustments — useful for cross-referencing the lender's published rate sheet.
adjusters[].bandstring | nullColumn band when the grid has bands (e.g. "70.01 - 75%"); null for single-column grids.
adjusters[].valuenumberAdjustment in points.
adjusters[].sourcestring"llpa" (LLPA grid cell), "ppp" (Non-QM PPP table), "rate_block" (FlexPoint block + lock-day column selection), or "state_adjustment" (FlexPoint per-state Fannie/Freddie adjuster).
caps_applied[]arrayPricing caps or floors that mutated final_price. Empty when no cap was hit. See Pricing caps & floors.
termintegerAmortisation term echoed from the request.
lock_daysintegerLock period echoed from the request.
ppp_termstringPPP term echoed from the request.

rejected[] — Rejection object

{
  "lender_slug": "non-qm",
  "program_slug": "expanded_doc",
  "program_name": "Expanded Doc",
  "doc_type": "bank_statements",
  "rejections": [
    {
      "key": "no_ltv_matrix_cell",
      "threshold": null,
      "provided": {
        "fico": 740,
        "loan_amount": 500000,
        "occupancy": "investment",
        "purpose": "purchase"
      },
      "source_page": null
    }
  ]
}

The engine emits a rejection at one of two levels:

  • Program-level — eligibility check failed before pricing began. rejections[] carries one entry per failing rule.
  • Rate-level (rolled up) — every rate on the ladder was rate-rejected (e.g. NA LLPA cell, min-price floor breach). The engine bubbles up a single entry summarising what happened, with rate and final_price on each rejections[] row plus a detail object describing the cell that triggered it.

Rejection keys

KeyLevelthresholdprovidedMeaning
doc_type_not_availableprogramnullsubmitted doc_typeRequested doc_type isn't registered for the program.
min_ficoprogramminimum FICOsubmitted credit_scoreCredit score below program minimum.
max_dtiprogrammaximum DTIsubmitted dtiDTI exceeds program maximum.
min_loan_amountprogramminimum loan amountsubmitted loan_amountLoan amount too small.
max_loan_amountprogrammaximum loan amountsubmitted loan_amountLoan amount too large.
dscr_ratio_requiredprogramminimum DSCRnullProgram requires a DSCR ratio and none was provided.
dscr_min_ratioprogramminimum DSCRsubmitted dscr_ratioDSCR below program floor.
eligible_citizenshipprogramaccepted typessubmitted citizenshipCitizenship not accepted by this program.
eligible_statesprogrameligible states or "all"submitted stateProperty state not eligible.
no_ltv_matrix_cellprogramnull{fico, loan_amount, occupancy, purpose}No LTV matrix row covers this combination.
max_ltv_exceededprogrammax_ltv from matrixcomputed LTVComputed LTV exceeds the matrix maximum.
max_cltv_exceededprogrammax_cltv from matrixcomputed CLTVComputed CLTV exceeds the matrix maximum.
llpa_naratenullrate that hit the NA cellAn LLPA cell required by this scenario is marked NA (hard ineligible at that intersection).
min_price_floorratefloor pricepriced valueFinal price would fall below the program's minimum price.
state_not_offeredratenull{state}FlexPoint program ships per-state adjusters but no row matched the scenario state — the lender does not offer the program in that state.

Pricing caps & floors

After LLPA adjustments are summed, every offer is clamped against per-program price caps and a minimum-price floor sourced from programs.cap_* columns. When a cap clamps a price, caps_applied[] records what happened:

{
  "caps_applied": [
    { "key": "Primary / 2nd Homes", "threshold": 102, "provided": 102.875 }
  ]
}

The cap selected for a scenario depends on occupancy, PPP term, and loan amount. Order of evaluation: tightest threshold first.

CapApplies when
Primary / 2nd HomesOccupancy is owner_occupied or second_home
Investments >= 3YR PPPOccupancy is investment with PPP term ≥ 3 years
Investments w/ < 3YR PPPOccupancy is investment with PPP term < 3 years
No PPP or State RestrictedOccupancy is investment with No PPP or in a state that bans PPP
Loan Amounts >$1.5mloan_amount > 1,500,000
Maximum PriceProgram-wide hard ceiling
Minimum PriceProgram-wide hard floor — breaching it produces a min_price_floor rate rejection rather than an offer

Examples

DSCR investment purchase

curl -X POST https://api.example.com/api/pricer/quote \
  -H "Authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d '{
    "term": 30,
    "purpose": "purchase",
    "credit_score": 740,
    "purchase_price": 625000,
    "loan_amount": 500000,
    "state": "CA",
    "occupancy": "investment",
    "property_type": "sfr",
    "dscr_ratio": 1.15
  }'

What happens internally:

  1. DSCR program is found on the latest rate sheet.
  2. Scalar rules pass: FICO 740 ≥ 640 minimum, loan $500K within $100K–$3.5M range.
  3. LTV matrix lookup: FICO 740 falls in the 740–850 premium band, loan $500K ≤ $1M → max_ltv = 85, max_cltv = 85 (DSCR ≥ 1.00 tier matches dscr_ratio = 1.15).
  4. Computed LTV = $500K / $625K = 80% ≤ 85% ✓
  5. Every non-NA rate on the ladder is priced; LLPA cells across FICO/CLTV, Purpose, Occupancy, Property Type, DSCR, and Product grids are summed into price_adjustment.
  6. Pricing caps applied (e.g. Investments >= 3YR PPP cap of 102 for a 3-year PPP scenario).
  7. Offers sorted descending by final_price.

Full Doc primary residence, 24-month seasoning

curl -X POST https://api.example.com/api/pricer/quote \
  -H "Authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d '{
    "program_slug": "full_doc",
    "doc_seasoning": "24mo",
    "term": 30,
    "purpose": "purchase",
    "credit_score": 720,
    "purchase_price": 800000,
    "loan_amount": 640000,
    "state": "TX",
    "occupancy": "owner_occupied",
    "property_type": "sfr",
    "dti": 42.0
  }'

doc_seasoning: "24mo" causes the Doc Type LLPA grid to resolve to the "24 mo Full Doc" row instead of the default "12 mo Full Doc". Omit the field to preserve legacy behaviour.

ITIN borrower

curl -X POST https://api.example.com/api/pricer/quote \
  -H "Authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d '{
    "term": 30,
    "purpose": "purchase",
    "credit_score": 700,
    "purchase_price": 500000,
    "loan_amount": 400000,
    "state": "FL",
    "occupancy": "owner_occupied",
    "property_type": "sfr",
    "citizenship": "itin",
    "dti": 45.0
  }'

Foreign National investment with PPP

curl -X POST https://api.example.com/api/pricer/quote \
  -H "Authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d '{
    "term": 30,
    "purpose": "purchase",
    "credit_score": 720,
    "purchase_price": 900000,
    "loan_amount": 675000,
    "state": "CA",
    "occupancy": "investment",
    "property_type": "sfr",
    "citizenship": "foreign_national",
    "dscr_ratio": 1.10,
    "ppp_term": "3YR PPP",
    "ppp_table_name": "5% Fixed Fee"
  }'

DSCR short-term rental with escrow waiver & interest-only

curl -X POST https://api.example.com/api/pricer/quote \
  -H "Authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d '{
    "program_slug": "dscr",
    "term": 30,
    "purpose": "cash_out_refi",
    "credit_score": 720,
    "purchase_price": 800000,
    "loan_amount": 560000,
    "state": "FL",
    "occupancy": "investment",
    "property_type": "sfr",
    "dscr_ratio": 1.25,
    "short_term_rental": true,
    "escrow_waiver": true,
    "interest_only": true
  }'

Each toggle adds its own row to adjusters[] on every priced offer (or rejects the offer if the corresponding cell is NA).

FlexPoint Conventional Fixed (30Y, owner-occupied, CA)

curl -X POST https://api.example.com/api/pricer/quote \
  -H "Authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d '{
    "program_slug": "2_conventional_fixed",
    "doc_type": "full_doc",
    "term": 30,
    "purpose": "purchase",
    "credit_score": 740,
    "purchase_price": 500000,
    "loan_amount": 400000,
    "state": "CA",
    "occupancy": "owner_occupied",
    "property_type": "sfr",
    "lock_days": 30
  }'

Returns offers from FlexPoint's Conventional 30 Yr Fixed rate block, lock_30 column. Each offer carries lender_slug: "flexpoint"; the adjuster log includes:

{
  "lender_slug": "flexpoint",
  "program_slug": "2_conventional_fixed",
  "program_name": "Conventional Fixed",
  "doc_type": "full_doc",
  "rate": 5.250,
  "base_price": 95.8826,
  "adjusters": [
    { "source": "rate_block",       "block": "Conventional 30 Yr Fixed", "lock_days": 30 },
    { "source": "state_adjustment", "state": "CA", "agency": "fannie", "value": 0.0 }
  ]
}

Submit the same payload with state: "HI" (or any state outside FlexPoint's 19-state Conv Fixed footprint) and the program appears under rejected[] with a single state_not_offered rejection instead — agency-shape eligibility lets the program survive doc-type/LTV-matrix gates so the state diagnostic is the one the broker sees.

Switching loan_amount above $766,550 (the 2026 conforming limit) automatically routes to the Conventional 30 Yr Fixed HB block when one exists for the term; HomeReady, HomePossible, RefiNow, FHA, VA, and USDA work the same way, each routing to its own block-name pattern.


Program & doc type reference

program_slugdoc_typeOccupancyNotes
full_docfull_docAllW2 / traditional income. Pair with doc_seasoning to target the 12 mo vs 24 mo row.
expanded_docbank_statementsAll12 or 24 months personal/business bank statements.
expanded_doc1099All1099-only income.
expanded_docasset_utilizationAllAsset depletion income calculation.
expanded_docpl_statementAllCPA-prepared P&L.
dscrdscrinvestmentRequires dscr_ratio.
multi_unit_mixed_usedscr_5_8_unitinvestment5–8 unit / mixed-use; requires dscr_ratio ≥ 1.00.
itinitinowner_occupied, non_owner_occupiedRequires citizenship: "itin".
fnforeign_nationalinvestmentRequires citizenship: "foreign_national" + dscr_ratio.

Use GET /api/pricer/programs to enumerate the live catalog at runtime — it reflects whatever the most recent php artisan rates:daily seeded.


Validation errors

PricerQuoteRequest returns a Laravel-standard 422 Unprocessable Entity when validation fails:

{
  "message": "The term field must be one of: 10, 15, 20, 25, 30, 40 years.",
  "errors": {
    "term": ["The term field must be one of: 10, 15, 20, 25, 30, 40 years."]
  }
}

Hard ranges enforced before the engine runs: credit_score 500–850, purchase_price/loan_amount ≥ 50000, dti 0–60, dscr_ratio 0–10, margin_bps 0–1000, state exactly 2 letters, zip exactly 5 digits. Enum-valued fields reject anything not in the documented set above.

On this page