# 1st & 2nd Look Marketplace

## Why Take Advantage of the Offer Wall API for 1L & 2LM

Some channel partners prefer a lightweight integration with Engine’s marketplace that delivers personalized offers without heavy engineering effort. To support this, Engine offers the ability to direct users into an Engine-hosted offer wall, through the **Offer Wall API for 1L & 2LM**—ideal for partners who are able to send lead data via API without having to build a frontend Offer Wall page themselves, which allows partners to launch an integration quickly and speed up the timeline to realizing revenue, with a data-backed UX to maximize conversion rates. Because offer details are returned in the API response, this also gives partners first-party visibility into each lead's offers, allowing you to store that data for monitoring, analytics, and optimization.&#x20;

## **Three Types of Engine Integrations for 1L + 2LM - Comparison Chart**&#x20;

<table><thead><tr><th width="135.50750732421875"></th><th width="155.16204833984375">Hosted Integration</th><th width="202.03875732421875">Native API (Build Your Own)</th><th width="326.48309326171875">Offer Wall API </th></tr></thead><tbody><tr><td><strong>Features</strong></td><td><p>Engine white-labeled or co-branded partner page/embed; </p><p>No first-party visibility into lead offers </p></td><td><p>Partner collects user information and controls user experience end-to-end; </p><p>Has first-party visibility into each lead's offers </p></td><td><p>Partners collects user information and controls user experience through submission of lead to request offers <strong>without having to build a frontend Offer Wall page themselves;</strong> </p><p></p><p>Engine-hosted, co-branded partner page/embed for offer display; <br><br>Partners have first-party visibility into offers returned from Engine</p></td></tr><tr><td><strong>Integration Effort</strong></td><td><strong>Low</strong>: Partners implement 1-4 lines of code, Engine controls the UX</td><td><strong>High</strong>: Partners must shape request structure to Engine requirements and parse Engine responses accordingly for smooth integration; Partners design own front-end experience</td><td><strong>Medium:</strong> Partners must shape request structure to Engine requirements, but Engine controls the offer display experience</td></tr><tr><td><strong>Compliance Effort</strong></td><td><strong>Low</strong>: Partners can take advantage of Engine's pre-approved compliance process</td><td><strong>High</strong>: Partners must  get approval from Engine <em>and</em> Financial Institutions before enablement can go live</td><td><strong>Medium</strong>: Partners can take advantage of Engine's pre-approved compliance process for offer display; (partner <em>does</em> need to get approval for application UX)</td></tr><tr><td><strong>Maintenance Effort</strong></td><td><strong>Low</strong>: Engine makes updates to UX with no effort required from partners</td><td><strong>High</strong>: Any updates will require partner's engineering resources, and a new compliance approval (for any material change to the UX)</td><td><p><strong>Medium</strong>: Any updates to the application UX will require the partner's engineering resources;</p><p></p><p>Any updates to the <strong>Offer Wall</strong> UI will not require partners' engineering resources</p></td></tr></tbody></table>

## High-Level Integration Overview

**Business Preparation:**&#x20;

* To obtain a `partnerPageUrl` in API responses, partners must first contact their Engine Partner Manager to enable the **Offer Wall API** for that bearer token.

**Technical integration overview:**&#x20;

1. **Define `productTypes`**: Decide which product types to include in your request. You can opt to receive offers for both 1st Look and 2nd Look Marketplace, or limit to only 1L or only 2LM offers.
2. **Collect Lead Data**: Gather all required user and contextual fields needed for the API payload.
3. **Submit to Offer Wall API**: Send a `POST` request to the /leads/rateTables endpoint with the assembled lead data as JSON.
4. **Check for Offers**: Review the response's `loanOffers` , `specialOffers`, and `pendingResponses` to confirm the presence of offers (or the potential for offers to be resolved, in the case of `pendingResponses`.
5. **Make Subequent Request(s)**: If the `pendingResponses` array is not empty, poll the `GET /rateTables` endpoint once per second, until `pendingResponses` is empty. Once `pendingResponses` is empty, if either the `loanOffers` array or `specialOffers` array is not empty (i.e. contains one or more offer objects), that means the lead has offers and should be redirected.
6. **Redirect to the Engine Offer Wall**: Redirect the lead into the `partnerPageUrl` from the response (at the top level), and users will see their offers in a co-branded experience.&#x20;

<figure><img src="https://3849802388-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MeMvVDbjd8vJWZ9Hwf0%2Fuploads%2F6WG1OoSdcsyvizcPkq9I%2FOffer%20Wall%20API%20-%201L%20%2B%202LM%20updated.png?alt=media&#x26;token=61050954-97e2-4818-9079-9fb77ca03af8" alt=""><figcaption></figcaption></figure>

(How supply partners should interact with Engine for both 1L and 2LM offers)&#x20;

## Integration Steps:&#x20;

There are two steps involved for getting offers from Engine's 2nd Look Marketplace

### Step 1: Submit Lead <a href="#step-1-submit-lead" id="step-1-submit-lead"></a>

As the first step, partners should post to the **Submit Lead** endpoint with the user's information:

`POST - https://api.engine.tech/leads/rateTables`

### Post Request Authorization <a href="#post-request-authorization" id="post-request-authorization"></a>

Engine will provide testing and production API access tokens. All requests to the Engine API must be authenticated using a bearer token specified in the Authorization header.

The header value is prefixed with the string "Bearer". Please contact your Engine Partner Manager for an appropriate bearer token for API testing.

```
Authorization: Bearer xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
```

#### Other Header Information <a href="#post-request-body-object-structure" id="post-request-body-object-structure"></a>

The supply partner must include the server's `userAgent` in request headers (for logging, access control, and analytics) or the request will be rejected by Engine's servers.&#x20;

The **required format** of the request to the Offer Wall API is as follows:&#x20;

```json
curl -L -X POST 'https://api.engine.tech/leads/rateTables' \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx' \
-H 'User-Agent: curl/7.68.0' \
--data-raw '{
  "productTypes": [
    "loan", "other"
  ],
  "personalInformation": {
    "firstName": "John",
    "lastName": "Doe",
    "email": "john@example.com",
    "city": "New York",
    "state": "NY",
    "primaryPhone": "2125556789",
    "address1": "45 West 21st Street",
    "address2": "5th Floor",
    "zipcode": "10010",
    "dateOfBirth": "1993-10-09",
    "ssn": "111-22-3333"
  },
  "loanInformation": {
    "purpose": "debt_consolidation",
    "loanAmount": 10000
  },
  "mortgageInformation": {
    "propertyStatus": "own_with_mortgage"
  },
  "creditInformation": {
    "providedCreditRating": "excellent"
  },
  "financialInformation": {
    "employmentStatus": "employed",
    "employmentPayFrequency": "weekly",
    "annualIncome": 75000
  },
  "educationInformation": {
    "educationLevel": "masters"
  },
  "legalInformation": {
    "consentsToFcra": true, // must be true
    "consentsToTcpa": true, // optional, can be false
    "consentsToSms:" true, // optional, can be false
    "tcpaLanguage": "I agree to be contacted...",
    "fcraLanguage": "By checking this box/clicking..."   
  },
  "sessionInformation": {
    "ipAddress": "8.8.8.8",
    "userAgent": "MyApp/2.1 (iOS 16.3; iPhone13,2)"
  },
  "clientTags": {
    "subId": [
      "aaa053cb"
    ]
  }
}'
```

#### Required fields:&#x20;

{% hint style="info" %}
If you as the Channel Partner do not include fields marked as required, the Engine API will still respond with a success (200) response, assuming all else is correct with the request. The Engine API's 200 response means that endpoints/auth are correct, and no fields are malformed - this is by design (since the Engine API spans multiple product types, each with different requirements). The designation of a field as "required" means most, if not all, of the Financial Institutions in that product vertical require that field to be present, and omitting that field means that all Financial Institutions who require that field will not receive that lead to check eligibility - meaning, ultimately, the omission of any of these required fields means a lead has a much lower (if not zero) chance of receiving offers.
{% endhint %}

**Product Types**

<table><thead><tr><th>Field</th><th width="94.5736083984375">Type</th><th>Required?</th><th>Sample Value</th><th>Enum/Comment</th></tr></thead><tbody><tr><td>productTypes*</td><td>Array of strings</td><td>Y</td><td>"loan", "other"</td><td><p>Enum: </p><ul><li>"loan"</li><li>"loan", "other"</li><li>"other"</li></ul></td></tr></tbody></table>

\*In the `productTypes` array, you may pass `["loan"]` for the lead to receive First Look / Personal Loan offers only), `["other"]` for the lead to receive Second Look Marketplace offers only, or `["loan", "other"]` for the lead to receive 1st Look offers when eligible, or *else* Second Look offers (if not eligible for any 1st Look offers).&#x20;

**Personal Information**

| Field          | Type   | Required? (Yes/No/Conditional) | Sample Value        | Enum/Comment                                                                                                                                                                                                 |
| -------------- | ------ | ------------------------------ | ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `firstName`    | String | Y                              | John                |                                                                                                                                                                                                              |
| `lastName`     | String | Y                              | Doe                 |                                                                                                                                                                                                              |
| `email`        | String | Y                              | <john@example.com>  | uses [Apache domain name validator](https://github.com/apache/commons-validator/blob/768d48d95dd6ef202acc8f0900d0f2582c6204c4/src/main/java/org/apache/commons/validator/routines/DomainValidator.java#L182) |
| `city`         | String | Y                              | New York            |                                                                                                                                                                                                              |
| `state`        | String | Y                              | NY                  | <p>2-letter State abbreviation</p><p><br>50 U.S. states + DC / PR / VI</p>                                                                                                                                   |
| `primaryPhone` | String | Y                              | 2125556789          | See below Regex Validations for more information                                                                                                                                                             |
| `address1`     | String | Y                              | 45 West 21st Street |                                                                                                                                                                                                              |
| `address2`     | String | N                              | 5th Floor           | Optional                                                                                                                                                                                                     |
| `zipcode`      | String | Y                              | 10010               | See below Regex Validations for more information                                                                                                                                                             |
| `dateOfBirth`  | String | Y                              | 1993-10-09          | yyyy-mm-dd                                                                                                                                                                                                   |
| `ssn`          | String | Y                              | 111-22-3333         | <p>Social Security Number.<br>See below Regex Validations for more information</p>                                                                                                                           |

**Loan Information**

| Field        | Type    | Required? (Yes/No) | Sample Value        | Comment                                                                                          |
| ------------ | ------- | ------------------ | ------------------- | ------------------------------------------------------------------------------------------------ |
| `purpose`    | String  | Y                  | debt\_consolidation | See enum at  API reference - [purpose](https://engine.tech/docs/api-reference/#tocS_LoanPurpose) |
| `loanAmount` | Integer | Y                  | 1000                | Unit in USD                                                                                      |

**Mortgage Information**

| Field            | Type   | Required? (Yes/No) | Sample Value        | Comment                                                                                                   |
| ---------------- | ------ | ------------------ | ------------------- | --------------------------------------------------------------------------------------------------------- |
| `propertyStatus` | String | Y                  | own\_with\_mortgage | See enum at API reference - [propertyStatus](https://engine.tech/docs/api-reference/#tocS_PropertyStatus) |

**Credit Information**

| Field                        | Type    | Required? (Yes/No/Conditional) | Sample Value | Comment                                                                                                               |
| ---------------------------- | ------- | ------------------------------ | ------------ | --------------------------------------------------------------------------------------------------------------------- |
| `providedCreditRating`       | String  | Conditional                    | good         | See enum at API reference - [providedCreditRating](https://engine.tech/docs/api-reference/#tocS_ProvidedCreditRating) |
| `providedNumericCreditScore` | Integer | Conditional                    | 750          | Either `providedCreditRating` or `providedNumericCreditScore` must be present                                         |

**Financial Information**

| Field                    | Type    | Required? (Yes/No/Conditional) | Sample Value | Enum/Comment                                                                                                              |
| ------------------------ | ------- | ------------------------------ | ------------ | ------------------------------------------------------------------------------------------------------------------------- |
| `employmentStatus`       | String  | Yes                            | employed     | See enum at API reference - [employmentStatus](https://engine.tech/docs/api-reference/#tocS_EmploymentStatus)             |
| `employmentPayFrequency` | String  | Yes                            | weekly       | See enum at API reference - [employmentPayFrequency](https://engine.tech/docs/api-reference/#tocS_EmploymentPayFrequency) |
| `annualIncome`           | Integer | Yes                            | 100000       | Unit in USD                                                                                                               |

**Legal Information**

<table><thead><tr><th>Field</th><th>Type</th><th width="115.79296875">Required? (Yes/No/Conditional)</th><th>Sample Value</th><th>Enum/Comment</th></tr></thead><tbody><tr><td><code>consentsToFcra</code></td><td>Boolean</td><td>Yes</td><td>true</td><td>Must be set to <code>true</code>.  </td></tr><tr><td><code>fcraLanguage</code></td><td>String</td><td>Yes</td><td>"By checking this box/clicking 'agree' I hereby consent to ..."</td><td>Must include the exact FCRA language shown to users.</td></tr><tr><td><code>consentsToSms</code></td><td>Boolean</td><td>No</td><td>true</td><td>Highly recommended</td></tr><tr><td><code>consentsToTcpa</code></td><td>Boolean</td><td>No</td><td>true</td><td>Highly recommended*</td></tr><tr><td><code>tcpaLanguage</code></td><td>String</td><td>Conditional</td><td>"By checking this box/clicking 'agree' I hereby consent to ..."</td><td>Required if <code>consentsToTcpa</code> is <code>true</code>. Must include the exact TCPA language shown to users.</td></tr></tbody></table>

{% hint style="info" %}
\*Although the FCC has [postponed](https://www.fcc.gov/document/fcc-postpones-effective-date-one-one-consent-rule) the effective date of the TCPA One-to-One Consent Rule—meaning the `consentsToTcpa` field is no longer strictly required (i.e. does not need to be `true`—Engine still strongly encourages our channel partners to obtain user consent to maintain the highest standards of compliance and performance.
{% endhint %}

**Session Information**

| Field       | Type   | Required? (Yes/No) | Sample Value                     | Enum/Comment                                                          |
| ----------- | ------ | ------------------ | -------------------------------- | --------------------------------------------------------------------- |
| `ipAddress` | String | Yes                | 8.8.8.8                          | Used for fraud detection by Engine and Financial Institution partners |
| `userAgent` | String | Yes                | MyApp/2.1 (iOS 16.3; iPhone13,2) | Used for fraud detection/prevention by Financial Institution partners |

{% hint style="info" %}
`sessionInformation.userAgent` should reflect the userAgent of the *client* (i.e. **consumer**'s device), which is different from the `User-Agent` required in request headers (which should reflect the channel partner's server).
{% endhint %}

**Client Tags**

<table><thead><tr><th width="143.78143310546875">Field</th><th width="143.0289306640625">Type</th><th>Required (Yes/No)</th><th>Sample Value</th><th>Enum/Comment</th></tr></thead><tbody><tr><td><code>clientId</code></td><td>array</td><td>No</td><td>clientId123</td><td>Client Tags can be added for ease of reporting/attribution (i.e. for the partner's convenience when Engine reports metrics to the partner). Refer to <a href="https://engine.tech/developer-center/loans-savings-second-look-marketplace/api/client-tags">Client Tags for Reporting</a> for details</td></tr></tbody></table>

**Regex Validations**

The following fields have the regex checks and will return errors if your lead submission does not adhere to this logic:

**personalInformation.primaryPhone**

The primary must be a string of 10 digits, with or without a leading 1, and with or without special characters e.g. `()-.`&#x20;

The area code cannot be the same 3 digits repeated, e.g. `222` or `333`.

**personalInformation.zipcode**

The zip code must be a string of either:

1. 5 digits
2. 5 digits, a hyphen `-`, and 4 more digits

**personalInformation.ssn**

1. The SSN cannot have all digits in each group be identical (e.g., `111-11-1111` or `222-22-2222`).
2. The following specific SSNs are not allowed because they are commonly used in examples or considered invalid:

* `123-45-6789`
* `219-09-9999`
* `078-05-1120`

3. The first group of three digits cannot be:

* `666`
* `000`
* Any number starting with `9` (e.g., `900`–`999`).

4. The middle group of two digits cannot be:

* `00`

5. The last group of four digits cannot be:

* `0000` &#x20;

#### API Response and Parsing&#x20;

#### Sample response: &#x20;

```json
{
    "uuid": "3a5ffdcd-4700-4dd0-9321-666a381c04fd",
    "leadUuid": "a8e6c5ab-3015-4048-b211-592a6b6c5bdd",
    "embedUrl": "string",
    "partnerPageUrl": "https://offers.fiona.com/partner/channel/loan/application?results=b9b212a8-b284-5986-813e-a8d8e87c1863&step=results",
    "creditCardOffers": [],
    "lifeInsuranceOffers": [],
    "lineOfCreditOffers": [],
    "loanOffers": [],
    "mortgageOffers": [],
    "savingsOffers": [],
    "specialOffers": [],
    "pendingOriginators": [
        {
            "key": "asyncMock",
            "name": "Async Originator Mock",
            "images": "https://s3.amazonaws.com/images.evenfinancial.com/logos/dev/engine_demo_loans_demand_sub_account_1-202-wqm88e4a.png",
            "companyUuid": "1ea348d9-ec08-4915-9de2-74fd55ea8d19"
        }
    ],
    "pendingResponses": [
        {
            "partner": {
                "uuid": "fa8ed168-9a3e-4612-aa2a-47781dede8f1",
                "name": "Engine Demo Loans Demand Sub Account 1",
                "description": "Demo Sub Account 1 - Description: This is a description of the FI, usually marketing material. <b> This section may include html.</b>",
                "disclaimer": "<p>Demo Sub Account 1 - Disclaimer: This is a disclaimer on the FI/offer, usually includes: \n<ul>\n<li>Legal Terms</li>\n<li>Stipulations</li>\n<li>Limitations</li>\n</ul>\n</p>\n<br/>\n<p>On occasion there may be a separate <a href=\"www.google.com\" target=\"_blank\">hyperlink to another site</a>, or <sup>1</sup>other tag types.</p>\n<br/>\n<sup>1</sup>These are common disclaimer conventions solved by the inclusion of basic HTML",
                "supportsPersonalizedOffers": false,
                "supportsPreSelect": false,
                "shouldDisplayPreSelect": false,
                "imageUrl": "https://s3.amazonaws.com/images.evenfinancial.com/logos/dev/engine_demo_loans_demand_sub_account_1-202-wqm88e4a.png"
            },
            "productTypes": [
                "loan"
            ]
        }
    ]
}
```

As shown in the sample response above, Partners will get the following information upon making a successful request:&#x20;

* `uuid` - This is the Rate Table UUID
* `leadUuid` - This is the uuid for each lead, i.e. user (unless the same PII is posted multiple times, in which case a user may have >1 leadUuid associated with them. For thie reason, we suggest using [Client Tags](https://even-financial.gitbook.io/developer-center/hybrid-integrations/offer-walls/broken-reference) for your own attribution)
* `partnerPageUrl` - This URL directs to a customized offer wall page created by the Engine for the supply partner. It displays the offers returned for that lead, if any, in a compliance-approved Engine-hosted experience
* `loanOffers`  - This is an array that contains 1st Look offers (i.e. prequalified/preapproved loan offers)
* `specialOffers`  - This is an array that contains 2nd Look Marketplace offers (i.e. alternative products such as debt relief and credit builders)
* `pendingOriginators`  - Deprecated field. Partners should ignore this field
* `pendingResponses`  - This is a list of FI partners from which Engine is awaiting responses.
  * If `pendingResponses` is empty, it means all offers from Engine's Financial Partners for the lead have been returned. The supply partner can redirect the lead into the `partnerPageUrl` immediately without the need for any further API calls (i.e. ignore Step 2 below)
  * If `pendingResponses` is NOT empty, it means Engine is still awaiting responses from FI partners. In this common scenario, the supply partner will need to poll the `GET originator/rateTables/:uuid` endpoint until all requests to FI partners are resolved.

{% hint style="info" %}
`leadUuid` is not needed to make the second call to retrieve offers, but you should record it in case you need to reach out to your Partner Manager regarding any questions or issues regarding that lead.
{% endhint %}

You may ignore other returned fields, which are reserved for products for other marketplaces (or deprecated legacy fields).

### Step 2: Poll the `GET /rateTables` endpoint <a href="#step-2-poll-the-get-ratetables-endpoint-for-a-specific-rate-table-uuid-until-pendingresponses-is-emp" id="step-2-poll-the-get-ratetables-endpoint-for-a-specific-rate-table-uuid-until-pendingresponses-is-emp"></a>

**Instructions for making the secondary request**

Supply partners must poll `GET originator/rateTables/:uuid` (`uuid`, NOT `leadUuid`) to retrieve complete offer information, until the `pendingResponses` is empty. We recommend polling once per second.

Below are the steps supply partners need to follow to make the secondary request:&#x20;

1. **Capture `uuid`**: Capture the `uuid` returned in Engine's response (the call made in Step 1)
2. **Execute GET request:** Use the captured `uuid` to make a GET request to the endpoint below:

`GET https://api.engine.tech/originator/rateTables/:uuid`

3. **Polling**: If there are `pendingResponses` (i.e. the array is not empty), continue polling every second.
   * Rate tables will typically resolve within 15 seconds if `productTypes` is set to `"loan"` or `"other"`
   * Rate tables may take up to 30 seconds to fully resolve if `productTypes` is set to `["loan", "other"]`
4. **Edge case handling**: In the edge case where there is no available offer for the lead after all `pendingResponses` are resolved, we advise partners against redirecting the lead to the `partnerPageUrl` , as it would display an empty offer wall (and you will likely want to handle no-offer cases within your own site/UX).

Below is a sample response from step 2's `GET originator/rateTables/:uuid` request. The `pendingResponses` field is empty, showing that all responses from Financial Institution partners have been received by the Engine.&#x20;

#### Sample response:&#x20;

```json
{
  "uuid": "b9b212a8-b284-5986-813e-a8d8e87c1863",
  "leadUuid": "144bd0ea-b9c6-52d5-a7ef-dccb223d060d",
  "partnerPageUrl": "https://fiona.com/network/{channel}/{zone}/compare/{uuid}",
  "loanOffers": [],
  "mortgageOffers": [],
  "savingsOffers": [],
  "specialOffers": [
      {
          "uuid": "8ddc320c-2a52-4c87-ba35-bfce3a8a7b3a",
          "name": "Mock Credit Builder Offer",
          "desc": "Description of the credit builder offer",
          "url": "https://offers.moneylion.com/ref/37089591-5d47-4219-a532-a65f7bd8c535",
          "partnerName": "Engine Demo Loans Demand Sub Account 1",
          "partnerImageUrl": "https://s3.amazonaws.com/images.evenfinancial.com/logos/dev/engine_demo_loans_demand_sub_account_1-202-wqm88e4a.png",
          "productSubType": "credit_builder",
          "disclaimer": "test special offer disclaimer 1",
          "financialInstitutionUuid": "fa8ed168-9a3e-4612-aa2a-47781dede8f1"
      }
  ],
  "creditCardOffers": [],
  "pendingResponses": [],
  "pendingOriginators": []
}
```

#### **Response Timing**

For your visibility, below are the typical response time it takes for Engine to receive offers from all our Financial Institutions partners.&#x20;

**1st Look Response Time**

| Percentile | Response Time (seconds) |
| ---------- | ----------------------- |
| p25        | 1                       |
| p50        | 2                       |
| p75        | 4                       |
| p95        | 8                       |

**2nd Look Marketplace Response Time**

| Percentile | Response Time (seconds) |
| ---------- | ----------------------- |
| p25        | <1                      |
| p50        | 1                       |
| p75        | 4                       |
| p95        | 11                      |

#### **Error codes**

Response types are mapped to HTTP status codes. In particular:

* **200 OK:** when data is successfully returned for a GET request
* **201 Created:** when new data is submitted via a POST
* **400 Bad Request:** the submitted data is malformed
* **401 Unauthorized:** when the Authorization header is missing, if the value is invalid, or if the corresponding access token lacks the required scopes to complete the request
* **404 Not Found:** the URL is invalid, or the resource ID reference in the URL does not exist
* **422 Unprocessable Entity:** the submitted data is properly formatted, but invalid according to business logic (some legacy endpoints use 409 Conflict in this case)
* **5xx:** server error
