Skip to main content

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:

  1. Check lock desk hours (optional) -- see Check Lock Desk Hours.

  2. Get Price Quote to have the latest lockEventId -- POST /rest/v1/lp-ppe-api/los/price-quote/get/{{priceQuoteId}}.

  3. Get Pricings -- POST /rest/v1/lp-ppe-api/pricing/search?userIdToUse={{userId}}, with search.current = false in the search payload. The user then selects the new rate.

  4. 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 EllieMaeQuoteRequest to EllieMaeBuyRequest. (Despite the name, it does not involve EllieMae.)

  5. 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:

  • dayExtension and dayExtensionRequested must be the same number.
  • pointExtension and pointExtensionRequested must 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.
  • values is deprecated -- use valuesList.
  • 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:

HeaderValue
AuthorizationBearer {{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.

Required configuration

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", send lineResult = historical.
  • If worstCaseResult == "CURRENT", send lineResult = current, and search.date = the date when the search was launched.
note

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 processed
  • Processing -- the action is being processed
  • Done -- 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:

HeaderValue
AuthorizationBearer {{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:

CategoryExamples
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"