Post-Lock Operations
After a loan has been locked, several operations are available: repricing, extending the lock period, requesting a post-lock price concession, relocking at worst case pricing, cancelling the lock, and allowing current market pricing.
All lock events on this page use POST /rest/v1/lp-ppe-api/price-quote/addLockEvent with the Authorization: Bearer {{accessToken}} header. In every case, the lockStatusId comes from Get Next Lock Actions, selected by its logic fields (see Identifying Lock Statuses), and the lockEventId is always the last event from Get Price Quote (currentLock.lockEvents[currentLock.lockEvents.length - 1].id).
Every addLockEvent on this page returns { "isAutoLock": ..., "queueItemId": ... } -- see Check Queue Item.
Reprice
Repricing changes a loan's price and relocks with the new price. Reprice is done when the loan is already locked. It can also represent a renegotiation.
Status to use: lockStatusLogic == "ModificationRequested" and requireReprice == true.
The reprice workflow:
-
Check lock desk hours (optional) -- see Check Lock Desk Hours.
-
Get Price Quote to have the latest
lockEventId--POST /rest/v1/lp-ppe-api/los/price-quote/get/{{priceQuoteId}}. -
Get Pricings --
POST /rest/v1/lp-ppe-api/pricing/search?userIdToUse={{userId}}, withsearch.current = falsein the search payload. The user then selects the new rate. -
Set Scenario From Result (BuyRequest) -- because the loan is already locked, use the BuyRequest variant:
Endpoint:
POST /rest/v1/lp-ppe-api/price-quote/setScenarioFromResult/EllieMaeBuyRequest{
"priceQuoteId": "{{priceQuoteId}}",
"search": "{{search}}",
"lineResult": "{{lineResult}}"
}Same payload as the pre-lock Set Scenario From Result; only the URL changes from
EllieMaeQuoteRequesttoEllieMaeBuyRequest. (Despite the name, it does not involve EllieMae.) -
Add the reprice lock event:
Endpoint: POST /rest/v1/lp-ppe-api/price-quote/addLockEvent
Body:
{
"priceQuoteId": "{{priceQuoteId}}",
"lockId": "{{lockId}}",
"lockStatusId": "{{repriceAfterLockStatusId}}",
"userIdToUseWorseCase": "{{userId}}",
"lockEventExceptionInfo": null,
"lockEventExtensionInfo": null,
"scenario": "{{scenario}}",
"lockDateUpdate": null,
"hasSellSide": false,
"historicalPricing": true,
"exportAndLock": true,
"lockEventId": "{{lockEventId}}",
"lockEventComment": "Whatever comment you would like"
}
After requesting a reprice, the lock desk can approve it (status with lockStatusLogic == "Locked") or deny it (see Deny Lock Request). Possible next statuses of a reprice request are Locked and Locked (Post-Lock Request Denied).
Extension Request
Request a lock extension before the lock expires. The list of allowed extension days is configurable on the lock policy.
Status to use: lockExtender.enable == true and lockExtender.type == "Request".
Endpoint: POST /rest/v1/lp-ppe-api/price-quote/addLockEvent
Body:
{
"priceQuoteId": "{{priceQuoteId}}",
"lockId": "{{lockId}}",
"lockStatusId": "{{extensionStatusId}}",
"lockEventExceptionInfo": null,
"lockEventExtensionInfo": {
"lockExtension": true,
"dayExtension": 4,
"dayExtensionRequested": 4,
"pointExtensionRequested": 0.018,
"pointExtension": 0.018,
"extensionOverrideDifferences": null
},
"scenario": null,
"hasSellSide": false,
"sellSideLine": null,
"lockDateUpdate": null,
"lockEventId": "{{lockEventId}}",
"lockEventComment": "Whatever comment you would like."
}
Field notes:
dayExtensionanddayExtensionRequestedmust be the same number.pointExtensionandpointExtensionRequestedmust match as well.- No scenario is sent with extension events.
Approve Extension
Approve an extension request. An extension grant is always auto-status back to a Locked event.
Status to use: lockExtender.enable == true and lockExtender.type == "Response" (the lockedExtensionId).
Endpoint: POST /rest/v1/lp-ppe-api/price-quote/addLockEvent
Body:
{
"priceQuoteId": "{{priceQuoteId}}",
"lockId": "{{lockId}}",
"lockStatusId": "{{lockedExtensionId}}",
"lockEventExceptionInfo": null,
"lockEventExtensionInfo": "{{lockExtensionObject}}",
"scenario": null,
"hasSellSide": false,
"sellSideLine": null,
"lockDateUpdate": null,
"lockEventId": "{{lockEventId}}",
"lockEventComment": "whatever comments"
}
lockExtensionObject is the pending request's extension info: pull the price quote with Get Price Quote and take currentLock.lockEvents[last].lockEventExtensionInfo.
To deny the extension instead, see Deny Lock Request -- the deny after a post-lock request auto-returns to Locked.
Request Post-Lock Price Concession
A price concession (exception) can be requested after a Locked event.
Status to use: lockException.enable == true and lockException.requestType == "Request".
Endpoint: POST /rest/v1/lp-ppe-api/price-quote/addLockEvent
Body:
{
"priceQuoteId": "{{priceQuoteId}}",
"lockId": "{{lockId}}",
"lockStatusId": "{{exceptionStatusId}}",
"lockEventExceptionInfo": {
"exceptionValueType": "Points",
"requestType": "Request",
"reason": "",
"holdback": false,
"excludeFromPriceCap": false,
"values": { "Price": 0 },
"valuesList": [
{
"type": "Price",
"value": "-0.03",
"amount": null,
"reason": "Points and Fees",
"costCenter": null,
"comment": null,
"exceptionPriceType": "CUSTOM",
"exceptionDataId": "{{priceQuoteId}}{{randomString}}",
"purpose": null
}
],
"exceptionOverrideDifferences": {}
},
"lockEventExtensionInfo": null,
"scenario": null,
"hasSellSide": false,
"sellSideLine": null,
"lockDateUpdate": null,
"lockEventId": "{{lockEventId}}",
"lockEventComment": "Whatever comment you would like."
}
- The value can be negative or positive.
valuesis deprecated -- usevaluesList.exceptionPriceType--"CUSTOM","BRANCH", or"CORPORATE".- No scenario is sent with exception events.
- Possible next statuses:
Locked Concession,Locked (Post-Lock Request Denied).
Holdback Concession
To request a holdback, set "holdback": true inside lockEventExceptionInfo (same payload otherwise), e.g. with a reason like "Close without redisclosing" and a timezone field on the event:
{
"priceQuoteId": "{{priceQuoteId}}",
"lockId": "{{lockId}}",
"lockStatusId": "{{exceptionStatusId}}",
"lockEventExceptionInfo": {
"exceptionValueType": "Points",
"requestType": "Request",
"reason": "",
"holdback": true,
"excludeFromPriceCap": false,
"values": { "Price": 0 },
"valuesList": [
{
"type": "Price",
"value": "-0.06",
"amount": null,
"reason": "Close without redisclosing",
"costCenter": null,
"comment": null,
"exceptionPriceType": "{{exeptionPriceType}}",
"exceptionDataId": "{{priceQuoteId}}{{randomString}}",
"purpose": null
}
],
"exceptionOverrideDifferences": {}
},
"lockEventExtensionInfo": null,
"lockEventRequestInfo": null,
"scenario": null,
"hasSellSide": true,
"sellSideLine": null,
"lockDateUpdate": null,
"lockEventId": "{{lockEventId}}",
"lockEventComment": "Whatever comment you would like",
"timezone": "America/Los_Angeles"
}
Approve Exception
Approve (grant) a concession request. An exception grant is always auto-status back to a Locked event.
Status to use: lockException.enable == true and lockException.requestType == "Grant" (the lockedExceptionId).
Endpoint: POST /rest/v1/lp-ppe-api/price-quote/addLockEvent
Body:
{
"priceQuoteId": "{{priceQuoteId}}",
"lockId": "{{lockId}}",
"lockStatusId": "{{lockedExceptionId}}",
"lockEventExceptionInfo": "{{lockExceptionObject}}",
"lockEventExtensionInfo": null,
"scenario": null,
"hasSellSide": false,
"sellSideLine": null,
"lockDateUpdate": null,
"lockEventId": "{{lockEventId}}",
"lockEventComment": "whatever comments"
}
lockExceptionObject is the pending request's exception info: currentLock.lockEvents[last].lockEventExceptionInfo from Get Price Quote.
Edit or Remove an Exception
Edit or delete concession adjustments after the fact. Multiple adjustments can be edited and deleted in one call.
Endpoint: POST /rest/v1/event/fee-change/{{priceQuoteId}}
Headers:
| Header | Value |
|---|---|
| Authorization | Bearer {{accessToken}} |
Body:
[
{
"lockEventId": "{{lockEventId_concession_wants_to_be_changed}}",
"exceptionDataId": "{{priceQuoteId}}{{randomString}}",
"current": "{{newValue}}",
"feeChangeAction": "UPDATE",
"reason": "",
"lockFeeType": "Concession",
"diffMap": {}
},
{
"lockEventId": "{{lockEventId_concession_wants_to_be_deleted}}",
"exceptionDataId": "{{priceQuoteId}}{{randomString}}",
"current": null,
"feeChangeAction": "DELETE",
"reason": "",
"lockFeeType": "Concession",
"diffMap": {}
}
]
exceptionDataId is the id you set when requesting the concession.
Cancel Lock
Cancel a lock using the addLockEvent call.
Status to use: lockStatusLogic == "CanceledOrWithdrawn".
Endpoint: POST /rest/v1/lp-ppe-api/price-quote/addLockEvent
Body:
{
"priceQuoteId": "{{priceQuoteId}}",
"lockId": "{{lockId}}",
"lockStatusId": "{{cancelStatusId}}",
"lockEventExceptionInfo": null,
"lockEventExtensionInfo": null,
"scenario": null,
"lockDateUpdate": null,
"hasSellSide": false,
"historicalPricing": false,
"exportAndLock": false,
"lockEventId": "{{lockEventId}}",
"lockEventComment": "whatever comments"
}
After a cancel (or an expire, which Lender Price's daily job creates with lockStatusLogic == "Expired"), the possible next statuses are: Priced, Relock Requested (Higher-Of), and Allow Current Market Pricing.
Allow Current Market Pricing
An empty event triggered by the lock desk. If triggered, the lock desk allows the loan officer to price with the current market (i.e., to trigger a Price event) after a cancel or expire.
Status to use: lockStatusLogic == "None".
Endpoint: POST /rest/v1/lp-ppe-api/price-quote/addLockEvent
Body:
{
"priceQuoteId": "{{priceQuoteId}}",
"lockId": "{{lockId}}",
"lockStatusId": "{{allowPricingStatusId}}",
"lockEventExceptionInfo": null,
"lockEventExtensionInfo": null,
"scenario": null,
"lockDateUpdate": null,
"hasSellSide": false,
"historicalPricing": false,
"exportAndLock": false,
"lockEventId": "{{lockEventId}}",
"lockEventComment": "whatever comments"
}
Relock (Worst Case)
Relock a loan after it has been cancelled or has expired, at the worse of historical and current pricing ("higher-of").
Status to use: worseCase == true and requireReprice == true.
Both of these must be checked under Pricer configs > Loan Pricer Configuration > Pricer Options > Result Settings:
- Restrict to eligible note rate stack only
- Automate Get Pricing
The relock workflow:
1. Get Pricings (with worst-case comparison)
POST /rest/v1/lp-ppe-api/pricing/search?userIdToUse={{userId}} with search.current = true in the payload.
In the relock worst-case flow, the search response includes, for each line result, both the historical and current line and which one is worse:
{
"dataEventId": "65a1b2c3d4e5f6a7b8c9d040-76341",
"resultMap": [
{
"resultRates": {
"data": {
"2.199": [
{
"historical": { ... },
"current": { ... },
"worstCaseResult": "HISTORICAL"
}
]
},
"resultsWithHistorical": true
}
}
],
"executeWorseCaseFlow": true
}
"resultsWithHistorical": true means the current/historical worst-case comparison was done for all results.
2. Set Scenario From Result (BuyRequest)
POST /rest/v1/lp-ppe-api/price-quote/setScenarioFromResult/EllieMaeBuyRequest
Select the line the comparison points to:
- If
worstCaseResult == "HISTORICAL", sendlineResult = historical. - If
worstCaseResult == "CURRENT", sendlineResult = current, andsearch.date= the date when the search was launched.
That behavior assumes the Apply search Day Lock term on Relock Worse Case events config (applySearchDayLockTermOnRelockWorseCaseEvents) is off. If it is on, the historical day-lock term is only used to determine which pricing is worse -- the day-lock term of the relock itself must be the term used on the search. In that case, set search.brokerCriteria.dayLocks and search.dayLocksCriteria from the last priced event before calling Set Scenario From Result.
3. Add the relock lock event
Endpoint: POST /rest/v1/lp-ppe-api/price-quote/addLockEvent
Body:
{
"priceQuoteId": "{{priceQuoteId}}",
"lockId": "{{lockId}}",
"lockStatusId": "{{worseCaseStatusId}}",
"userIdToUseWorseCase": "{{userId}}",
"lockEventExceptionInfo": null,
"lockEventExtensionInfo": null,
"scenario": "{{scenario}}",
"lockDateUpdate": null,
"hasSellSide": false,
"historicalPricing": false,
"exportAndLock": true,
"lockEventId": "{{lockEventId}}",
"lockEventComment": "Whatever you want",
"worstCaseData": "{{worstCaseData}}",
"overrideSetting": {
"newExtensionFee": 0,
"newConcessionFee": 0,
"newRelockFee": 0,
"newExpirationDate": null
}
}
scenario-- from the Set Scenario From Result response.worstCaseData-- the selected line info:{ "historical": {...}, "current": {...}, "worstCaseResult": "HISTORICAL" | "CURRENT" }.overrideSetting-- optional; also available on other events if the user wants to override extension/concession/relock fees or the expiration date.
Possible next statuses of a relock request: Locked and Relock Request Denied (a deny here returns to whichever of Expired or Cancelled came before the request).
Check Queue Item
Each addLockEvent responds with a queueItemId, except addLockEvent for pricing (root) events, which do not use the queue:
{
"isAutoLock": true,
"queueItemId": "65a1b2c3d4e5f6a7b8c9d020"
}
Check the status of the queued action:
Endpoint: GET https://{{domain}}/rest/queue/{{queueItemId}}
Response (partial):
{
"priceQuoteId": "65a1b2c3d4e5f6a7b8c9d001",
"actionType": "ADD_LOCK_EVENT",
"status": "Done",
"numberOfError": 0,
"numberOfTry": 0,
"stopRetry": false,
"groupId": "LosQueueAction-65a1b2c3d4e5f6a7b8c9d001"
}
Three kinds of status:
NotProcessed-- the item is waiting to be processedProcessing-- the action is being processedDone-- the action is done
Check Queue Items by Loan
List items still pending (NotProcessed or Processing) for a loan:
Endpoint: GET /rest/v1/integration/ellie/loanLock/ellieQueueDataNotProcessed?priceQuoteId={{priceQuoteId}}
Auto Reprice
Automatically reprice a price quote.
Endpoint: POST /rest/v1/lp-ppe-api/price-quote/auto-reprice
Headers:
| Header | Value |
|---|---|
| Authorization | Bearer {{accessToken}} |
Body:
{
"priceQuoteId": "{{priceQuoteId}}",
"action": "PRICE"
}
action is "PRICE" or "PRICE_WITH_DISQUALIFIED".
Possible errors:
"No eligible results found on auto reprice. Price Quote ${priceQuote.id}"- Any pricing error, returned in the form:
{
"mode": "SystemError",
"diff": {},
"error": "Cannot find Zip detail information for : 91502-9150",
"allowToContinue": null,
"disqualificationReasons": null
}
Common pricing validation errors include:
| Category | Examples |
|---|---|
| IllegalArgumentException | "DTI cannot be negative"; "Monthly income must be > 0"; "Monthly Debt must be >= 0"; "Own Properties must be >= 0"; "Subordinate Loan Amount must be >= 0" (non-HELOC); "CLTV must be >= 0"; "guaranteeFeeFinanced can't be null when UsdaRural is requested"; "FICO must be >= 300 and <= 850"; "LoanPurpose is mandatory in MultiCriteriaSearch"; "Missing Information for County and Zip"; "Invalid format for County"; "Cannot find Zip detail information"; "Cannot match Zip with criteria"; "Business Source Type cannot be N/A or empty for DTC loan" |
| InvalidPurchaseValueException | "Purchase price must be > 0"; "Purchase price or appraised value must be > 0" |
| InvalidSmoException | "SMO doesn't exist"; "SMO ID and Name mismatch"; "SMO ID cannot be found"; "SMO Name cannot be found" |
| General | "miCriteria is null, please omit this parameter if you are not planning to use it"; "criteria is null, this object can't be null"; "criteria.varaiableLoanTypes is null, please omit..."; "property is null, this object can't be null"; "Area Median Income does not exist for County FIPS..."; "ZipCode is not valid" |