Skip to main content

Submit a Lock Request

This page covers the lock-without-LOS workflow from getting pricing through submitting the lock request: search for rates, create a price quote, set the scenario from the selected result, create the lock, run pre-lock validations, and submit the lock request.


Authentication

Obtain an access token before any other call.

Endpoint: POST {{protocol}}{{domainAuth}}/oauth/token

Headers:

HeaderValue
AuthorizationBasic cHBlLWxlbmRlcnByaWNlOmxwc2VjcmV0
Content-Typemultipart/form-data

Body (form data):

username:   {{username}}
password: {{password}}
grant_type: password
client_id: ppe-lenderprice

Response:

{
"access_token": "......",
"token_type": "bearer",
"refresh_token": ".....",
....
}

Use access_token as Authorization: Bearer {{accessToken}} on all subsequent calls. See the Authentication guide for more detail.


Step 1: Get Pricing

Search for available rates using your loan criteria.

Endpoint: POST /rest/v1/lp-ppe-api/pricing/search?userIdToUse={{userId}}

Headers:

HeaderValue
AuthorizationBearer {{accessToken}}

Body:

The pricing search accepts over 200 fields to fine-tune results. Key fields include:

{
"accessCriteria": {
"companyIds": [],
"marketPlaceSearch": false,
"userContext": {
"groups": []
}
},
"brokerCriteria": {
"adjustments": [],
"rateTypes": [],
"sortView": "LenderPaid",
"leadSourceType": "Retail"
},
"criteria": {
"purchasePrice": 500000,
"loanAmount": 400000,
"computeApr": false,
"mortgageTypes": [],
"pmiType": "Monthly",
"loanPurpose": "Purchase",
"loanType": "Fixed"
},
"property": {
"state": "CA",
"county": "Los Angeles",
"propertyType": "SingleFamily",
"occupancyType": "PrimaryResidence",
"numberOfUnit": 1
},
"search": {
"current": true
}
}
note
  • userIdToUse in the URL is whoever is performing the action; userIdToImpersonate is whoever the pricing should come from (they can be different users).
  • brokerCriteria.adjustments will not accept new adjustments directly from the search payload. If an adjustment needs to be made, it has to come from the rate grid.
  • For the initial search, set search.current = true in the payload.

Response:

The response contains resultMap.resultRates.data with an array of lineResult objects. Save the lineResult you want to lock -- you will use it in subsequent steps.

Pricing When a Lock Already Exists

Once a lock exists, price through the price quote instead, so all added concessions, extensions, and relock fees are correctly applied:

Endpoint: POST /rest/v1/lp-ppe-api/price-quote/{{priceQuoteId}}/search?userIdToUse={{userId}}&lockStatusId={{nextLockStatusId}}

nextLockStatusId is the status of the action the user wants to perform next (for a relock worst case, the relock worst-case status id; for a reprice, the reprice status id). Get it from Get Next Lock Actions.


Step 2: Create a Price Quote

Create a new Price Quote with the given search data. This is the container for the entire lock lifecycle.

Endpoint: POST /rest/v1/lp-ppe-api/price-quote/create

Headers:

HeaderValue
AuthorizationBearer {{accessToken}}

Body:

{
"search": "{{search}}",
"name": "Test1",
"borrowers": [
{
"printPositionType": "Borrower",
"firstname": "Jane",
"lastname": "Doe",
"borrowerId": "BorId1"
}
]
}

search is the full search object used in Step 1.

Response (partial):

{
"id": "65a1b2c3d4e5f6a7b8c9d001",
"companyId": "65a1b2c3d4e5f6a7b8c9d010",
"userId": "65a1b2c3d4e5f6a7b8c9d011",
"name": "Test1",
"integrationRef": {
"DUMMY": "65a1b2c3d4e5f6a7b8c9d001"
},
"loanApplication": {
"mismoVersionId": "2.3.1",
"borrower": [ ... ]
}
}

The response id is the priceQuoteId -- save it, it is used in every subsequent step. The loanApplication object follows the ten03Application MISMO format.

important

Save integrationRef.DUMMY as well -- it is required to update the same loan later. If your company wants to use its own integration name (and put its own loan unique identifier in this field), that requires a code change on the Lender Price EXT side.

Updating the Price Quote

Updates use the same create endpoint. Include the integrationRef from the create response so the call updates the existing loan instead of creating a new one:

{
"search": "{{modified_search}}",
"name": "Test1",
"integrationRef": {
"DUMMY": "65a1b2c3d4e5f6a7b8c9d001"
},
"borrowers": [
{
"printPositionType": "Borrower",
"firstname": "JaneUpdated",
"lastname": "DoeUpdated",
"borrowerId": "BorId1"
},
{
"printPositionType": "CoBorrower",
"firstname": "Jane2Updated",
"lastname": "Doe2Updated",
"borrowerId": "BorId2"
}
]
}

Step 3: Get Price Quote

Retrieve the Price Quote. Use this whenever you need the current lock state -- in particular the latest lockEventId.

Endpoint: POST /rest/v1/lp-ppe-api/los/price-quote/get/{{priceQuoteId}}

Headers:

HeaderValue
AuthorizationBearer {{accessToken}}

Response (partial):

{
"id": "65a1b2c3d4e5f6a7b8c9d001",
"name": "Test1",
"integrationRef": { "DUMMY": "65a1b2c3d4e5f6a7b8c9d001" },
"currentLock": {
"id": "65a1b2c3d4e5f6a7b8c9d002",
"priceQuoteId": "65a1b2c3d4e5f6a7b8c9d001",
"lockExpirationDateMarker": "2025-03-20T19:05:21.710Z",
"lastStatus": { ... },
"lockEvents": [
{
"id": "65a1b2c3d4e5f6a7b8c9d003",
"scenario": { ... },
"status": { ... },
"lockId": "65a1b2c3d4e5f6a7b8c9d002"
}
]
}
}

currentLock.lockEvents always holds the list of events -- always take the last one for the current lockEventId and scenario.


Step 4: Set Scenario From Result

Set the scenario from the pricing result the user selected. This must be done each time pricing is selected by the user while the loan is UNLOCKED.

Endpoint: POST /rest/v1/lp-ppe-api/price-quote/setScenarioFromResult/EllieMaeQuoteRequest

Headers:

HeaderValue
AuthorizationBearer {{accessToken}}

Body:

{
"priceQuoteId": "{{priceQuoteId}}",
"search": "{{search}}",
"lineResult": "{{lineResult}}"
}
  • priceQuoteId -- the id from Create a Price Quote.
  • search -- the search that was sent in Get Pricing.
  • lineResult -- the pricing line the user selected from the Get Pricing results.
note

Even though the URL mentions EllieMae, this call does not involve EllieMae. Once the loan is locked, use the EllieMaeBuyRequest variant instead (see Post-Lock Operations).


Step 5: Lock With Scenario

Create the Lock. This is done only the first time after the first Set Scenario From Result. It creates the Lock and the very first event, Priced.

Condition: the user must be assigned to a Lock Policy within a user group workflow.

Endpoint: POST /rest/v1/lp-ppe-api/price-quote/lockWithScenario

Headers:

HeaderValue
AuthorizationBearer {{accessToken}}

Body:

{
"priceQuoteId": "{{priceQuoteId}}",
"search": "{{search}}",
"lineResult": "{{lineResult}}",
"exportAndLock": false
}

Response (partial):

{
"currentLock": {
"id": "65a1b2c3d4e5f6a7b8c9d002",
"priceQuoteId": "65a1b2c3d4e5f6a7b8c9d001",
"lockEvents": [
{ "id": "65a1b2c3d4e5f6a7b8c9d003", "scenario": { ... }, "status": { ... } }
]
}
}

From this response, save:

  • lockId = currentLock.id
  • lockEventId = currentLock.lockEvents[currentLock.lockEvents.length - 1].id

These two values, along with priceQuoteId, do not change for the life of the loan (lockEventId advances with each event -- always re-read the last event from Get Price Quote).


Step 6: Get Next Lock Actions

Get the available next lock status actions, as defined by the lock policy.

Endpoint: GET /rest/v1/lp-ppe-api/price-quote/getNextLockStatus/{{lockId}}/{{lockEventId}}

Headers:

HeaderValue
AuthorizationBearer {{accessToken}}

Response:

Returns the list of statuses the user can move to. Identify the one you need by its logic fields, never by name (see Identifying Lock Statuses):

[
{
"id": "65a1b2c3d4e5f6a7b8c9d030",
"root": true,
"primeState": false,
"autoStatus": false,
"autoStatusLockStatusId": null,
"code": "Priced",
"buttonName": "Price",
"lockStatusLogic": "Other",
"requireReprice": false,
"worseCase": false,
...
},
{
"id": "65a1b2c3d4e5f6a7b8c9d031",
"root": false,
"code": "Lock Requested",
"buttonName": "Request Lock",
"lockStatusLogic": "Requested",
"requireReprice": true,
...
}
]

For a lock request, use the status with lockStatusLogic == "Requested" as your lockRequestId.


Step 7: Pre-Lock Validation Checks

Before submitting the lock request, run these validation checks.

Check Lock Desk Hours

Optional -- Returns whether the user is within business hours according to the lock policy.

Loan providers have operating hours where lock requests can be entered. These operating hours are referred to as desk hours, and they differ based on the loan provider. Before submitting a lock request, first confirm that the request is being made during the loan provider's desk hours.

Endpoint: GET /rest/v1/lp-ppe-api/checkLockStatus/{{lockId}}/{{lockRequestId}}

Headers:

HeaderValue
AuthorizationBearer {{accessToken}}

Response:

{
"isInLockDeskHours": true,
"isPostLockRequest": false
}

Check Rate Period Status

Checks whether the previously selected rate is still active (has not expired).

Endpoint: GET /rest/v1/lp-ppe-api/isRatePeriodStillActive/{{priceQuoteId}}/{{lockRequestId}}

Headers:

HeaderValue
AuthorizationBearer {{accessToken}}

Response:

true

Returns a boolean (true/false) indicating if the rate period is still active.


Step 8: Submit Lock Request (No SellSide)

Submit the lock request event.

Endpoint: POST /rest/v1/lp-ppe-api/price-quote/addLockEvent

Headers:

HeaderValue
AuthorizationBearer {{accessToken}}

Body:

{
"priceQuoteId": "{{priceQuoteId}}",
"lockId": "{{lockId}}",
"lockStatusId": "{{lockRequestId}}",
"lockEventExceptionInfo": null,
"lockEventExtensionInfo": null,
"scenario": "{{scenario}}",
"hasSellSide": false,
"lockDateUpdate": null,
"lockEventId": "{{lockEventId}}",
"lockEventComment": "Whatever comment you would like"
}

Response:

{
"isAutoLock": true,
"queueItemId": "65a1b2c3d4e5f6a7b8c9d020"
}

Every addLockEvent returns a queueItemId, except pricing (root) events, which do not use the queue. Use the queue item to track when the action has been processed -- see Check Queue Item.


Step 9: Submit Lock Request (With SellSide)

Submit the lock event with sell-side line information. This is used when selling to the secondary market. The payload is the same as Step 8 with hasSellSide: true and a sellSideLine object.

Endpoint: POST /rest/v1/lp-ppe-api/price-quote/addLockEvent

Headers:

HeaderValue
AuthorizationBearer {{accessToken}}

Body:

{
"priceQuoteId": "{{priceQuoteId}}",
"lockId": "{{lockId}}",
"lockStatusId": "{{lockRequestId}}",
"lockEventExceptionInfo": null,
"lockEventExtensionInfo": null,
"scenario": "{{scenario}}",
"hasSellSide": true,
"sellSideLine": {
"id": "65a1b2c3d4e5f6a7b8c9d050",
"mergeSet": null,
"createdBy": null,
"creationDate": "2023-05-18T18:39:17Z",
"modifiedBy": null,
"modifiedDate": "2023-05-18T18:39:17Z",
"origin": "Lenderprice",
"order": 0,
"name": null,
"comment": null,
"search": "{{search}}",
"pricedOnMarketPlace": false,
"searchPoint": {
"id": "65a1b2c3d4e5f6a7b8c9d051",
"mergeSet": null,
"createdBy": null,
"creationDate": "2023-05-18T18:39:17Z",
"modifiedBy": null,
"modifiedDate": "2023-05-18T18:39:17Z",
"referenceId": null,
"date": null,
"lineResult": "{{lineResult}}"
},
"currentSearchPoint": null,
"searchPointTarget": {
"id": "65a1b2c3d4e5f6a7b8c9d052",
"mergeSet": null,
"createdBy": null,
"creationDate": "2023-05-18T18:39:17Z",
"modifiedBy": null,
"modifiedDate": "2023-05-18T18:39:17Z",
"searchOrigin": "All",
"sameLender": true,
"lenderName": null,
"lenderId": null,
"sameProgram": true,
"ratePeriodName": null,
"ratePeriodId": null,
"rateProgramName": null,
"rateProgramId": null,
"rateGridName": null,
"rateGridId": null,
"rateRange": {
"@class": "com.cre8techlabs.entity.rate.adjustment.table.RangeGreaterEqThanLowerEqThan",
"from": null,
"to": null
},
"pointsRange": {
"@class": "com.cre8techlabs.entity.rate.adjustment.table.RangeGreaterEqThanLowerEqThan",
"from": null,
"to": null
},
"compensationRange": {
"@class": "com.cre8techlabs.entity.rate.adjustment.table.RangeGreaterEqThanLowerEqThan",
"from": null,
"to": null
},
"closingCostRange": {
"@class": "com.cre8techlabs.entity.rate.adjustment.table.RangeGreaterEqThanLowerEqThan",
"from": null,
"to": null
},
"useAlert": false,
"alertType": "Range",
"rateAlertRangeConfig": {
"id": "65a1b2c3d4e5f6a7b8c9d053",
"mergeSet": null,
"durationInDays": 90,
"cutOffDate": null,
"lastTimeSent": null,
"alertSentCount": 0
},
"rateAlertSubscriptionConfig": {
"id": "65a1b2c3d4e5f6a7b8c9d054",
"mergeSet": null,
"durationInDays": 90,
"cutOffDate": null,
"lastTimeSent": null,
"alertSentCount": 0,
"frequency": "EveryDay"
},
"emails": "",
"lastUpdate": null,
"rateAlert": null
}
},
"lockDateUpdate": null,
"lockEventId": "{{lockEventId}}",
"lockEventComment": "Whatever comment you would like"
}

Response: Same as Step 8 -- { "isAutoLock": ..., "queueItemId": ... }.

Once the lock request is submitted, continue with Approve or Reject a Lock.