Appearance
Application detail — Offers tab
Last updated: 2026–05-27
The Offers tab (labeled Offers in the main tab bar) shows which offer is selected for the application, all eligible offers or manual offer history, and optional amortization schedules from your pricing or LMS integrations. Use it after underwriting to confirm what the applicant was offered, to add or adjust a manual offer, and to inspect payment schedules.
Open it from Agent Dashboard → application → Offers.
[NEEDS SCREENSHOT: Offers tab with Selected Offer, Eligible Offers, and Amortization sections]
Who can do what (roles)
| Action | Who sees it | Behavior |
|---|---|---|
| View all accordions | Users with application detail access | Includes Auditor (read-only). |
| Add Offer button | Admin (tenant admin, manager, sub-tenant admin, super admin) or Analyst when underwriting produced offer variables | Opens Modify Offer modal. |
| Modify Offer — change values | Admin and Analyst (when modal is open) | Analysts edit values only. |
| Modify Offer — add/remove variables, change variable dropdown | Admin only | Analysts: variable select and trash disabled. |
| Add Variable (inside modal) | Admin only | |
| Header Update Status, Request OTP | Not Auditor | Auditors can view offers but cannot use those header actions. |
Auditor summary: Auditors can read Selected Offer, tables, and amortization; they do not see Add Offer (not admin/analyst path) and cannot update manual offers through this UI.
Layout overview
| Section | Always visible? | Purpose |
|---|---|---|
| Selected Offer | Yes | Highlighted card for the active offer (engine-selected or latest manual). |
| Eligible Offers | Yes | Table of alternatives or manual offer rows. |
| Amortization Data | Only when amortization payload exists | Schedule rows (LendAPI, Totality, LoanPro, etc.). |
While data loads, you see an input skeleton and table skeleton.
Each section is a collapsible accordion (chevron on the section title). Selected Offer can also expose Add Offer on the accordion header when your role allows.
[NEEDS SCREENSHOT: Offers tab three accordions expanded]
Selected Offer accordion
Header card (when data exists)
| Area | Content |
|---|---|
| Title | Manual Offer if the latest manual offer is active; otherwise offer name / title from engine. |
| Subtitle | Manual: Added by {user}; Engine: {lender_name}. |
| Badge | Selected (success/green). |
| Timestamp | updated_at for the selected offer. |
| Field grid | Labeled metrics (amount, rate, term, payment, etc.)—labels come from server keys (title-cased). Long values truncate with tooltip on hover. |
Values are formatted using workspace variable display types when variables are registered (e.g. currency as $X.XX, percentages with %).
[NEEDS SCREENSHOT: Selected Offer card with Manual Offer badge and field grid]
Add Offer (secondary button on accordion)
Label: Add Offer
Visibility:
- Admin: Button shown on Selected Offer accordion (when accordion header actions are enabled).
- Analyst: Shown only if underwriting Offer Table produced at least one output object (variables to seed the modal).
- Others (e.g. Auditor, CSR without analyst): Button hidden.
Action: Opens the Modify Offer modal (title in UI: Modify Offer).
Empty states — Selected Offer
| Condition | What you see |
|---|---|
No selected_offer_data at all | No information to show on this tab |
selected_offer_data exists but no display fields after filtering metadata | Same empty message inside the accordion |
Metadata keys stripped from the card: updated_by, updated_at, selected_card_header, selected_offer_widget_data.
Modify Offer modal (Add Offer)
Opened from Add Offer on the Selected Offer accordion.
| Control | Description |
|---|---|
| Title | Modify Offer |
| Description | Update offer for the application. |
| Variable rows | Each row: variable selector + value input + remove (trash). |
| Add Variable | Admin only; appends a blank row. |
| Cancel | Closes without saving. |
| Update | Saves manual offer via API; disabled until form is dirty, all rows have variable + value, and at least one row exists. Shows loading while saving. |
Initial values:
- If a manual offer is already selected, rows load from current
selected_offer_data. - Otherwise rows seed from the first Offer Table underwriting result (
app_decision.offer_data[0].output[0]).
Analyst vs Admin in the modal:
| Capability | Admin | Analyst |
|---|---|---|
| Edit values | Yes | Yes |
| Change which variable a row uses | Yes | No (selector disabled) |
| Remove row | Yes | No |
| Add Variable | Yes | No |
On success: toast Offer updated successfully; modal closes; Selected Offer, Eligible Offers, and related queries refresh.
[NEEDS SCREENSHOT: Modify Offer modal with variable rows and Update button]
Use cases — Manual offer
| Scenario | Steps |
|---|---|
| Engine offer wrong; policy allows manual override | Admin or Analyst (with seed data) → Add Offer → adjust values → Update. |
| Ad-hoc counteroffer | Admin → Add Offer → Add Variable for extra fields → Update. |
| Tweak one field on existing manual offer | Add Offer → change value → Update (must change something—Update stays disabled if pristine). |
Troubleshooting — Modify Offer
| Problem | Cause | Fix |
|---|---|---|
| Update disabled | No changes, missing variable name, or empty value | Fill all rows; change at least one value. |
| Add Offer not visible | Role or no underwriting offer output | Admin: should still see button; Analyst: run decision first. |
| Failed to update offer toast | API / validation error | Check required variables; confirm app not locked by stage. |
| Analyst cannot add fields | By design | Ask admin to add variables or perform update. |
Eligible Offers accordion
Shows alternatives so you can compare paths that were not selected (or manual offer history when a manual offer is active).
Mode 1 — Engine / multi-lender offers (default)
When: No active manual offer selection (manual timestamp not driving selection).
Table behavior:
| Feature | Behavior |
|---|---|
| Columns | Lender Name (if multi-offer payload); plus dynamic columns from all offer output keys. |
| Sorting | Column headers sortable. |
| Search | Global filter enabled. |
| Row selection | Checkboxes enabled (for export/copy patterns in the table component). |
| Cell display | Long text tooltips; currency/percent formatting via variable display types. |
Data sources (first match wins in code):
lender_multi_offer.multi_offer_datawhen multi-lender offers exist.- Otherwise
offer_dataarray on the application offers response.
[NEEDS SCREENSHOT: Eligible Offers table with lender column and sortable headers]
Mode 2 — Manual offer history
When: A manual offer is the selected offer (updated_at on manual row wins in selection logic).
Table behavior:
| Feature | Behavior |
|---|---|
| Columns | Keys from manual offer objects (title-cased headers). |
| Sorting | Off |
| Search | Off |
| Rows | manual_offer_data reversed so most recent first |
Use this to audit prior manual versions, not to select a row in the UI (selection is informational).
Empty states — Eligible Offers
| Condition | Message |
|---|---|
| Manual mode, no manual rows | No information to show on this tab |
| Engine mode, no column data | Same |
| Engine mode, data but zero table rows after flattening | Same |
Amortization Data accordion
When it appears: Only if the API returns amortization under amortization_data (tried in order: LendAPI non_autopay, LendAPI root, Totality, LoanPro—normalized to row arrays).
Table behavior:
| Feature | Behavior |
|---|---|
| Columns | Dynamic keys from schedule rows (payment number, principal, interest, balance, etc.—depends on integration). |
| Sorting | Enabled |
| Search | Enabled |
| Pagination | Disabled (all rows in one scrollable table) |
| Row selection | Enabled |
When hidden: If no amortization array exists after normalization, the entire accordion is not rendered (not the same as an empty message).
[NEEDS SCREENSHOT: Amortization Data table with multiple columns]
Empty state inside accordion
If amortization exists but flattens to zero rows: No information to show on this tab.
Use cases — Amortization
| Scenario | What to do |
|---|---|
| Verify payment schedule shown to applicant | Open Amortization Data; sort by period; compare to Selected Offer payment field. |
| Totality / LoanPro boarding | Confirm schedule imported—columns reflect partner field names. |
| No accordion | Offer may not have generated amortization yet; check pricing engine or integration jobs. |
How “selected offer” is determined
The UI picks one offer object for the Selected Offer card using this logic:
- If manual offer data has entries and the latest manual row has
updated_at, treat as manual offer (most recent manual row wins). - Otherwise use engine
selected_offer_datafrom multi-offer payload or top-levelselected_offer_data[0]. - Display name, lender,
updated_by/updated_at, and a map of fields for the grid (excluding internal metadata keys).
Implication: after you Update a manual offer, Eligible Offers switches to manual history mode and Selected Offer shows Manual Offer.
Value formatting
Offer and amortization tables format cell values using workspace variables (engine_name match):
| Display type | Presentation |
|---|---|
| Currency (type 2) | $X.XX for numbers |
| Percent (type 3) | X.XX% (stored decimal × 100) |
| Other / default | Raw string or number |
| Empty | -- |
| Boolean | true / false |
Workflows (end-to-end)
Workflow A — Confirm engine offer after decision
- Complete underwriting; verify Underwriting → Offer Table has results.
- Open Offers → Selected Offer shows engine name, lender, and terms.
- Review Eligible Offers for alternate lenders/products.
- If amortization generated, validate Amortization Data against compliance disclosures.
Workflow B — Manual counteroffer (Analyst)
- Ensure decision ran so underwriting offer output exists (seeds modal).
- Add Offer → adjust allowed values → Update.
- Selected Offer shows Manual Offer and Added by.
- Eligible Offers lists manual history rows.
Workflow C — Manual offer (Admin, extra variables)
- Add Offer → Add Variable → pick variable from tenant catalog → enter value.
- Repeat for all required fields → Update.
- Share PDF or applicant portal link from other tabs as per process.
Workflow D — Auditor review
- Open Offers (no Add Offer).
- Read Selected Offer and Eligible Offers / amortization.
- Cross-check Underwriting → Offer Table trace for how numbers were produced.
Examples
Example 1 — Multi-lender eligibility
Selected Offer shows Lender A’s 60-month term. Eligible Offers lists Lender A and Lender B with rate/term columns; underwriter confirms applicant was presented Lender A only. Amortization Data shows 60 rows matching APR.
Example 2 — Analyst adjustment
Underwriting output included loan_amount and apr. Analyst opens Add Offer, changes loan_amount, cannot add origination_fee (selector disabled). Admin later adds fee via Add Variable.
Example 3 — Empty eligible table
Decision approved but pricing returned no rows—Eligible Offers shows No information to show on this tab while Selected Offer may still show widget data if backend sent selected_offer_data only. Escalate to pricing engine configuration.
Troubleshooting (quick reference)
| Symptom | Check |
|---|---|
| Everything empty on Offers tab | Application session / API; whether offers endpoint returned data. |
| Add Offer missing for Analyst | Underwriting Offer Table must have produced output variables first. |
| Selected card empty but button existed | Filtered fields—all keys may be metadata-only. |
| Wrong offer labeled selected | Manual updated_at may override engine; review manual history table. |
| Amortization columns look odd | Partner-specific field names (Totality/LoanPro/LendAPI). |
| Currency looks unformatted | Variable not registered in workspace catalog for that engine_name. |
Related documentation
- Application detail (overview)
- Underwriting tab — Credit, Decision Flow, Offer Table trace
- Pricing Engine — Offer tables
- Agent dashboard