> For the complete documentation index, see [llms.txt](https://even-financial.gitbook.io/developer-center/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://even-financial.gitbook.io/developer-center/channel-partner-reporting/report-options/engine-analytics-api.md).

# Engine Analytics API

For partners who would prefer to fetch their reporting programmatically, we offer an Analytics API for you to receive lead-level reporting on funnel metrics.

{% hint style="info" %}
**Please Note:** `leadUuid` in this document refers to Engine's Lead UUID (the `leadUuid` returned in responses when you POST requests to `/leads/*`

Please see [Engine's API Reference](https://engine.tech/docs/api-reference/#engine-by-moneylion-api-analytics) for technical instructions and more info on the Engine API in general.
{% endhint %}

## Endpoints Overview

We provide the following endpoints for real-time insights into the user acquisition to conversion funnel:

<table><thead><tr><th width="272.7158203125">Endpoint</th><th>Description</th></tr></thead><tbody><tr><td><strong>GET /leadEvents</strong></td><td>Retrieve lead conversion event data (creation, clicks, approvals, rejections, etc.)</td></tr><tr><td><strong>GET /leadPayouts</strong></td><td>Track payouts per conversion on a per-lead basis</td></tr><tr><td><strong>GET /leadClientTags</strong></td><td>Retrieve client tags (e.g., subID, clickID, traffic source) for segmentation and attribution</td></tr><tr><td><strong>GET /leadsInfo/{leadUuid}</strong></td><td>On-demand snapshot of a single lead’s full data (events, payouts, tags)</td></tr></tbody></table>

{% hint style="info" %}
All Engine Supply Analytics API endpoints share a common request and response pattern. The `/leadEvents`, `/leadPayouts`, and `/leadClientTags` endpoints support polling.
{% endhint %}

## Basic Request & Response Format

#### Authentication

All requests require:

* **Bearer Token** in the `Authorization` header
  * If the token is missing, expired, or invalid, the API will return a `401 Unauthorized`.
* **API-Version** header set to `2025-04-01`

**Example**:

```http
Authorization: Bearer {accessToken}
API-Version: 2025-04-01
```

{% hint style="info" %}
The token you use to POST to `/leads/rateTables` may not necessarily have the right scopes to access the Supply Analytics API. Please confirm in advance with your Partner Manager.

\*\*If you have more than one integration live with Engine - different products or different placements - you will need to hit *each* endpoint with a different token specific to that specific integration. Each token can only retrieve records associated with that specific integration.
{% endhint %}

#### Key Parameters

All **streaming endpoints** (`/leadEvents`, `/leadPayouts`, `/leadClientTags`) support the following query parameters:

* **sinceTimestamp** – Lower bound for records to include. Use ISO 8601 UTC format with `Z`. Recommended for initial requests.
* **untilTimestamp** – Upper bound for records to include (exclusive). Useful for controlled backfills.
* **paginationToken** – Cursor for fetching the next page of results. Obtained from the previous response.
* **eventType** *(for `/leadEvents` only)* – Optional array filter to return only specific event types. Accepts multiple values (comma-separated).

These parameters apply only to the streaming endpoints above. They do not apply to `/leadsInfo/{leadUuid}`, which is a direct lookup endpoint returning a full snapshot for a single lead.

#### Response Structure

Each request will return a JSON object with the following properties:

```json
{
  "data": [...],
  "nextUrl": "https://api.engine.tech/...paginationToken=...",
  "shouldContinuePolling": true,
  "paginationToken": "string"
}
```

* **data** – Array of records as **objects** (events, payouts, or client tags depending on the endpoint).
* **nextUrl** – Pre-built URL for the next request. Always follow this rather than constructing your own.
* **shouldContinuePolling** – Boolean indicating whether additional data is available at the next URL.
* **paginationToken** – Cursor used for paging. Already included in `nextUrl`, but exposed separately for convenience.

**Important Notes**

* Responses are subject to a maximum size limit (can be 10s of thousands of records, depending on the endpoint and the volume you send). If more data exists, `shouldContinuePolling` will be `true` and you should immediately follow the `nextUrl` until `shouldContinuePolling` is `false`.
* Always advance using the `nextUrl` provided to avoid missing or duplicating records.
* Unless backfilling historical data, polling should be no more than once every 5 minutes - data is only updated every 5 minutes.
* Timestamps without an explicit timezone are interpreted as UTC. We recommend including `Z` (e.g. `2025-01-01T00:00:00Z`) for consistency.

## Lead Events API – `/leadEvents`

This endpoint returns events associated with each lead. The event type is indicated by the `eventType` property. Each event will also include other data applicable to the eventType (see below). It allows easy consumption of the events into your database so that you can integrate it into your BI framework and compare it alongside data from other systems to assess performance holistically.

**Example Request**

```bash
curl -X GET "https://api.engine.tech/supplyAnalytics/leadEvents?sinceTimestamp=2024-09-01T00:00:00Z" \
-H "Authorization: Bearer {accessToken}" \
-H "API-Version: 2025-04-01"
```

**Example Response**

```json
{
  "nextUrl": "https://api.engine.tech/supplyAnalytics/leadEvents?paginationToken={paginationToken}",
  "shouldContinuePolling": true,
  "paginationToken": "{paginationToken}",
  "data": [
    {
      "id": "{eventRecordId1}",
      "leadUuid": "{leadUuid1}",
      "leadCreatedAt": "2025-01-01T11:24:45Z",
      "eventType": "offerClicked",
      "eventCreatedAt": "2025-01-01T12:00:00Z",
      "financialInstitutionUuid": "{fiUuid1}",
      "financialInstitutionName": "FI Partner A",
      "productType": "loan",
      "productSubType": "personal_loan",
      "unifiedProductType": "UnsecuredPersonalLoan",
      "supplySubAccountUuid": "{supplySubAccountUuid1}",
      "supplySubAccountName": "Partner Loans Marketplace",
      "offerUuid": "{offerUuid1}",
      "isTest": false,
      "paginationTimestamp": "2025-01-01T12:00:00Z"
    },
    {
      "id": "{eventRecordId2}",
      "leadUuid": "{leadUuid2}",
      "leadCreatedAt": "2025-01-02T09:42:06Z",
      "eventType": "apiApproved",
      "eventCreatedAt": "2025-01-02T09:44:28Z",
      "financialInstitutionUuid": "{fiUuid2}",
      "financialInstitutionName": "FI Partner B",
      "isTest": false,
      "paginationTimestamp": "2025-01-02T09:44:28Z"
    }
  ]
}
```

### Event Types by Marketplace Vertical

Not every product emits every event type. The lists below are typical - your integration may see a subset depending on lenders, placements, and campaign type.

* **Lending**
  * `leadCreated`, `appSubmitted`, `apiApproved`, `apiRejected`, `offerClicked`, `affiliateOfferClicked`, `applied`, `approved`, `listed`, `funded`, `lenderQualifiedLead`
* **Deposits**
  * `leadCreated`, `offerClicked`, `applied`, `opened`, `funded`
* **Credit Cards**
  * `leadCreated`, `applied`, `approved`, `opened`, `funded`, `conversion`
* **2nd Look Marketplace (2LM) & Special Offers**
  * `leadCreated`, `appSubmitted`, `apiApproved`, `apiRejected`, `offerClicked`, `conversion`, `isContacted`, `salesQualified`, `firstPayment`, `lenderQualifiedLead`
* **Mortgage**
  * `leadCreated`, `applied`, `approved`, `funded`
* **Auto Insurance**
  * `leadCreated`, `applied`, `approved`, `funded`

#### Event Type Reference <a href="#event-type-reference" id="event-type-reference"></a>

For the complete list of values returned by the API.

| Event Type              | What It Means                                            | Typical Reporting Usage                    |
| ----------------------- | -------------------------------------------------------- | ------------------------------------------ |
| `leadCreated`           | Lead registered in Engine                                | Lead volume                                |
| `appSubmitted`          | Marketplace application completed                        | Submitted / entered funnel                 |
| `apiApproved`           | API pre-approval for an offer                            | API approval rate (per lender)             |
| `apiRejected`           | API rejection for an offer                               | Decline analysis                           |
| `offerClicked`          | User selected an offer on the **marketplace** rate table | Marketplace clicks                         |
| `affiliateOfferClicked` | User clicked through an **affiliate / link** placement   | Affiliate clicks                           |
| `listed`                | User listed with a lender                                | Mid-funnel (product-specific)              |
| `applied`               | Lender reports an application                            | Lender-reported submissions                |
| `approved`              | Lender reports an approval                               | Lender-reported approvals                  |
| `opened`                | Account or product opened (for example savings, cards)   | Open / monetization                        |
| `funded`                | Loan funded                                              | Funded volume; may include `amountInCents` |
| `conversion`            | Partner monetization / conversion milestone              | Common for 2LM, CPC, special offers        |
| `lenderQualifiedLead`   | Lender qualified lead                                    | Debt relief and similar flows              |
| `isContacted`           | User contacted by lender or partner                      | 2LM / outbound flows                       |
| `salesQualified`        | Sales-qualified milestone                                | 2LM                                        |
| `firstPayment`          | First payment made                                       | 2LM / installment products                 |
| `firstContact`          | First contact milestone                                  | Product-specific                           |
| `rateLocked`            | Rate lock milestone                                      | Mortgage and related products              |

#### Event Funnels are Product-specific

Funnels are **not strictly linear**. Order and availability depend on product, lenders, and integration type.

There is **no `continue` (or bridge-step) event type** in the Analytics API. Bridge or widget “continue” steps from legacy summary reports are not exposed as a dedicated `eventType`; use `appSubmitted`, click events, and client tags for attribution instead.

**Typical personal-loan (API) flow:**

`leadCreated` → `appSubmitted` → `apiApproved` (0..n lenders) → `offerClicked` → `applied` → `approved` → `funded`

**Typical 2LM / special-offer flow:**

`leadCreated` → `offerClicked` → `conversion` (and optionally `isContacted`, `salesQualified`, `firstPayment`)

Important distinctions:

* **`apiApproved`** — API **pre-qualification** for a specific lender offer. Can occur **before** a click. Multiple per `leadUuid` are normal.
* **`approved`** — **Lender-reported** approval after the user applies in the FI’s experience. Not interchangeable with `apiApproved`.
* **`appSubmitted`** — User completed the **Engine marketplace** application.
* **`applied`** — User **applied with the lender** (reported via external demand events).

### Interpreting Event-level Data <a href="#understanding-event-level-data" id="understanding-event-level-data"></a>

The `/leadEvents` endpoint returns **one row per event occurrence**, not one row per consumer. A single `leadUuid` (one lead in Engine) often appears on **many rows** because:

* A consumer can be **pre-approved by multiple lenders** (`apiApproved`).
* A consumer can **click multiple offers** (`offerClicked`).
* A financial institution can send **multiple downstream updates** over time (`conversion`, `funded`, and so on).

Each event includes a stable **`id`**. It is a unique identifier for that specific event instance (derived from `leadUuid`, `eventType`, and `eventCreatedAt`). Use **`id`** when you need to count or deduplicate **events**. Use **`leadUuid`** when you need to count **people / leads** moving through a funnel.

{% hint style="info" %}
**For business stakeholders:** Think of `leadUuid` as “this user’s journey in Engine” and each event row as “something that happened on that journey.” Daily summary reports you may receive from Engine are usually **already aggregated** (for example, by day and placement). The Analytics API gives you the **underlying event stream** so you can rebuild those summaries in your own BI tools.
{% endhint %}

{% hint style="info" %}
**For implementers:** Exclude test traffic with `isTest = false` in production reporting. Use `/leadsInfo/{leadUuid}` to inspect one lead’s full `events`, `payouts`, and `clientTags` when validating your logic.
{% endhint %}

### Logic for Aggregating Events

The API does not return pre-aggregated funnel tables. Use the patterns below to align event-level data with common channel-partner metrics.

| Primary KPI                              | Corresponding /leadEvents Logic                                                | Notes                                                                              |
| ---------------------------------------- | ------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------- |
| Leads                                    | `COUNT(DISTINCT leadUuid)` where `eventType = 'leadCreated'`                   | One lead per Engine UUID                                                           |
| Submitted applications (marketplace)     | `COUNT(DISTINCT leadUuid)` where `eventType = 'appSubmitted'`                  | User completed the Engine application                                              |
| Submitted applications (lender-reported) | `COUNT(DISTINCT leadUuid)` where `eventType = 'applied'`                       | Reported by the FI after handoff — not the same as `appSubmitted`                  |
| API approvals                            | `COUNT(DISTINCT leadUuid)` where `eventType = 'apiApproved'`                   | Pre-qual / API approval; **multiple rows per lead** are common                     |
| Clicks (marketplace)                     | `COUNT(DISTINCT leadUuid)` or count of rows where `eventType = 'offerClicked'` | Use **distinct leads** for funnel rates; use **row counts** for total click volume |
| Clicks (affiliate / link)                | Same as above for `affiliateOfferClicked`                                      | Separate from marketplace `offerClicked`                                           |
| Conversions                              | `COUNT(DISTINCT leadUuid)` where `eventType = 'conversion'`                    | Meaning varies by product; common for 2LM and special offers                       |
| Approvals (lender-reported)              | `COUNT(DISTINCT leadUuid)` where `eventType = 'approved'`                      | Not the same as `apiApproved`                                                      |
| Funded loans                             | `COUNT(DISTINCT leadUuid)` where `eventType = 'funded'`                        | `amountInCents` may be present when the FI reports an amount                       |
| Revenue / payout                         | Use **`/leadPayouts`**, not event rows                                         | Sum `payoutInCents`; deduplicate on payout **`uuid`**; honor **`deletedAt`**       |

{% hint style="warning" %}
**Multiple events per lead are usually correct.** For example, three `offerClicked` rows for one `leadUuid` often means the user clicked three different lenders. Several `conversion` rows can reflect multiple billable milestones from a partner. Choose distinct-lead vs event-row counting based on whether your KPI is “how many users” or “how many actions.”
{% endhint %}

### Placement and Attribution

There is **no single `placement` field** on lead events. Partners typically identify placement (widget, bridge page, co-branded experience, affiliate link, savings integration, and so on) using a combination of:

1. **Access token / integration** — Each bearer token is scoped to **one Engine integration**. If you operate multiple placements, you may need **separate tokens** (see Authentication above) or a deliberate strategy to merge streams in your warehouse.
2. **`supplySubAccountUuid` / `supplySubAccountName`** (on each lead event) — Identifies which supply integration produced the lead. Prefer **`supplySubAccountUuid`** as the stable key; names can change.
3. **`/leadClientTags`** — Your `key` / `value` pairs (for example `docid`, `module`, `referrer`, `subId`, `campaignId`) for page- or campaign-level segmentation. See the [attribution tracking page](/developer-center/channel-partner-reporting/attribution-tracking.md)
4. **Event type** — `affiliateOfferClicked` vs `offerClicked` helps separate **affiliate / inline link** traffic from **marketplace offer-list** clicks.

Work with your Partner Manager to document which client tags and sub-accounts map to each of your internal placement names.

### Product Categorization <a href="#product-categorization" id="product-categorization"></a>

Event rows include product metadata about the **financial institution (demand) offer** tied to that event:

<table><thead><tr><th width="257.3515625">Field</th><th>Use</th></tr></thead><tbody><tr><td><strong><code>unifiedProductType</code></strong></td><td><strong>Preferred</strong> when populated — Offer Catalog product type (vertical / offer family).</td></tr><tr><td><strong><code>productSubType</code></strong></td><td>More specific offer subtype (for example <code>personal_loan</code>, <code>debt_relief</code>, <code>education_offers</code>).</td></tr><tr><td><strong><code>productType</code></strong></td><td>Legacy demand-partner classification (<code>loan</code>, <code>savings</code>, <code>credit_card</code>, <strong><code>other</code></strong>, and so on).</td></tr></tbody></table>

**`productType = other`** is common for second-look marketplace, special offers, education, debt relief, and other partners that are not classic loan or savings products. That does **not** mean the data is wrong — use **`unifiedProductType`** and **`productSubType`** (and `financialInstitutionName`) for reporting slices when `productType` is `other`.

Filtering “loan offer clicks” with `eventType = 'offerClicked' AND productType = 'loan'` works for **traditional loan lenders** but may **exclude** valid clicks on 2LM or special-offer partners.

## Lead Payouts API – `/leadPayouts`

This endpoint allows you to collect granular payout information for leads you have submitted. It returns a unique identifier for the record (`uuid`) payout information (amount and timestamp), the Financial Institution the converted with, the `productType`/`unifiedProductType`, and the `leadUuid`.

**Example Request**

```bash
curl -X GET "https://api.engine.tech/supplyAnalytics/leadPayouts?sinceTimestamp=2019-08-24T14%3A15%3A22Z" \
-H "Authorization: Bearer {accessToken}" \
-H "API-Version: 2025-04-01"
```

**Example Response**

```json
{
  "nextUrl": "https://api.engine.tech/supplyAnalytics/leadPayouts?paginationToken={paginationToken}",
  "shouldContinuePolling": false,
  "paginationToken": "{paginationToken}",
  "data": [
    {
      "uuid": "{recordUuid1}",
      "leadUuid": "{leadUuid1}",
      "bookedAt": "2025-01-01T00:00:00Z",
      "payoutInCents": 20000,
      "deletedAt": null,
      "financialInstitutionUuid": "{fiUuid1}",
      "financialInstitutionName": "FI Partner A",
      "productType": "savings",
      "unifiedProductType": "{OCProductType1}",      
      "paginationTimestamp": "2025-01-01T00:00:00Z"
    },
    {
      "uuid": "{recordUuid2}",
      "leadUuid": "{leadUuid1}",
      "bookedAt": "2025-01-02T00:00:00Z",
      "payoutInCents": 30000,
      "deletedAt": null,
      "financialInstitutionUuid": "{fiUuid2}",
      "financialInstitutionName": "FI Partner B",
      "productType": "savings",
      "unifiedProductType": "{OCProductType1}",
      "paginationTimestamp": "2025-01-01T00:00:00Z"
    },
    {
      "uuid": "{recordUuid3}",
      "leadUuid": "{leadUuid2}",
      "bookedAt": "2025-01-02T00:00:00Z",
      "payoutInCents": 10000,
      "deletedAt": null,
      "financialInstitutionUuid": "{fiUuid3}",
      "financialInstitutionName": "FI Partner C",
      "productType": "savings",
      "unifiedProductType": "{OCProductType1}",      
      "paginationTimestamp": "2025-01-02T00:00:00Z"
    }
  ]
}
```

#### Deleted Payouts

If the record's `deletedAt` is not `null`, you should understand this to be a deletion of a previous payout you would have already received in a previous response (you can identify it by the payout **`uuid`** corresponding to the previous record received).

Deleted payouts are not frequent, but they are not rare. A payout may be deleted if:

* A funded loan/conversion was canceled
* A record was reported in error
* A partner contract (either FI or Channel partner) was updated with a retroactive date
  * In this case, you would receive two records in the same response - one for the deleted payout with a non-null `deletedAt` (with the old **`uuid`**), and one for the new payout with a new `payoutInCents` and a null `deletedAt` (with a new **`uuid`**).

#### Financial Institution Payout Events

Each response also contains two fields identifying the Financial Institution the lead monetized with:

* `financialInstitutionUuid` – stable identifier of the financial institution.
* `financialInstitutionName` – human-readable name of the institution.

These fields help disambiguate which Financial Institution a payout is tied to, especially in cases where multiple payouts exist for a single lead (typically, but not always, with different Financial Institutions). Most, but not all payouts are associated with a specific FI, so these fields may not always be present.

## Lead Client Tags API – `/leadClientTags`

This endpoint returns the client tags associated with a lead.

Client tags are *your* key/value pairs of information that you set up when generating leads (through either a Hosted Integration or Native API Integration) to enable attribution back to your own identifiers, thereby enabling custom segmentation. Client tags provides you a method to be able to segment leads to support uses cases like:

* See how various traffic sources on the supply partners site perform
* See what preferences their users from different segments have

**Please never include PII** in the `clientTags` object when posting leads into Engine's API.

**Example Request**

```bash
curl -X GET "https://api.engine.tech/supplyAnalytics/leadClientTags?sinceTimestamp=2019-08-24T14%3A15%3A22Z" \
-H "Authorization: Bearer {accessToken}" \
-H "API-Version: 2025-04-01"
```

**Example Response**

```json
{
  "nextUrl": "https://api.engine.tech/supplyAnalytics/leadClientTags?paginationToken={paginationToken}",
  "shouldContinuePolling": true,
  "paginationToken": "{paginationToken}",
  "data": [
    {
      "id": "{recordUuid1}",
      "leadUuid": "{leadUuid1}",
      "key": "subId",
      "value": "12345",
      "createdAt": "2025-01-05T14:00:00Z"
    }
  ]
}
```

For more information sending your Client Tags to Engine (for retrieval through this `leadClientTags` endpoint, please refer to <https://engine.tech/developer-center/references/appendix/appendix-e-appending-client-tags-to-leads-posted-to-engine>.

## Lead Info API – `/leadsInfo/{leadUuid}`

Get a **full snapshot** of one lead’s lifecycle in a single call: `events`, `payouts`, and `client tags`. It includes all of the same information as the combination of `leadEvents`, `leadPayouts` and `leadClientTags` endpoints on demand, without having to sync responses from 3 endpoints and tie them together using coding logic.

**Example Request**

```bash
curl -X GET "https://api.engine.tech/supplyAnalytics/leadsInfo/{leadUuid}" \
-H "Authorization: Bearer {accessToken}" \
-H "API-Version: 2025-04-01"
```

**Example Response**

```json
{
  "data": {
    "events": [
      {
        "id": "{eventUuid1}",
        "leadUuid": "{leadUuid1}",
        "leadCreatedAt": "2025-05-23T21:29:13Z",
        "eventType": "leadCreated",
        "eventCreatedAt": "2025-05-23T21:29:13Z",
        "financialInstitutionUuid": "{fiUuid}",
        "financialInstitutionName": "FI Partner A",
        "isTest": false,
        "paginationTimestamp": "2025-05-23T21:29:13Z"
      }
    ],
    "payouts": [
      {
        "uuid": "{payoutUuid12",
        "leadUuid": "{leadUuid2}",
        "bookedAt": "2025-05-23T21:29:13Z",
        "payoutInCents": 5000,
        "deletedAt": null,
        "paginationTimestamp": "2025-05-23T21:29:13Z"
      }
    ],
    "clientTags": [
      {
        "hash": "{hashValue}",
        "leadUuid": "{leadUuid}",
        "key": "campaignId",
        "value": "abc123",
        "createdAt": "2025-05-23T21:29:13Z",
        "paginationTimestamp": "2025-05-23T21:29:13Z"
      }
    ]
  },
  "warnings": []
}
```

## Timing & Polling

Because reporting data depends on when Financial Institutions send information to Engine, there are important timing rules to follow.

**Polling frequency**

* Poll at least **hourly** for new data.
* Do **not** poll more than once every 5 minutes — requests made too soon after the last poll may fail due to SLA limits.

`shouldContinuePolling` **flag**

* If `shouldContinuePolling` is `true`, call the `nextUrl` to obtain more data associated with your query.
* If `shouldContinuePolling` is `false`, there are no more records currently available for your query — wait until your next polling interval before trying again.

**Timestamps in responses**

<table><thead><tr><th width="202.453125">Field</th><th width="211.7421875">Applies to</th><th>Meaning</th></tr></thead><tbody><tr><td><strong><code>eventCreatedAt</code></strong></td><td><code>/leadEvents</code></td><td>When the <strong>business event occurred</strong> (semantic event time), including lender-reported milestones when applicable. Use this for <strong>funnel timing</strong> and daily event rollups.</td></tr><tr><td><strong><code>paginationTimestamp</code></strong></td><td>Streaming endpoints</td><td>When the record became <strong>available for pagination</strong> in Supply Analytics (ingestion / processing time). Use for <strong>polling and backfills</strong>, not as the user action date.</td></tr><tr><td><strong><code>bookedAt</code></strong></td><td><code>/leadPayouts</code></td><td>When the <strong>payout was booked</strong> for your account (monetization time).</td></tr><tr><td><strong><code>leadCreatedAt</code></strong></td><td><code>/leadEvents</code></td><td>When the lead was first created in Engine.</td></tr></tbody></table>

Payout and lender-reported events may appear in the API **days after** the user action because financial institutions report on their own schedules. See [Example Flow](#example-flow) below.

{% hint style="info" %}
When reconciling to legacy daily summary files, confirm with your Partner Manager whether those files use **event date**, **booked date**, or **processing date** — then map to `eventCreatedAt`, `bookedAt`, or `paginationTimestamp` accordingly.
{% endhint %}

**Response idempotency - depends on the query**

* A request with the same `sinceTimestamp` and `untilTimestamp` will always return the same results.
  * Even if payouts are later deleted, those deletions are logged under the **timestamp of the deletion event**, not the original creation time.
* Two requests with the same `sinceTimestamp` but *no* `untilTimestamp` may not always return the same results - because new records may have been added in that time.

**Lag considerations**

* Leads and conversions may not appear immediately.
* Always account for possible reporting delays and backfill windows (e.g., a loan funded on Day 1 may not appear until Day 3 due to FI reporting cycles, and some subverticals may take much longer due to their business cycles).

#### Example Flow <a href="#example-flow" id="example-flow"></a>

1. A loan is funded on **9/15 at 17:30 UTC**.
2. The FI sends the report to Engine on **9/16 at 15:30 UTC**.
3. Engine processes the report and loads it into the data warehouse on **9/17 at 12:30 UTC**.

**API behavior:**

* `/leadPayouts` will return the event with `bookedAt: 2025-09-15T17:30:00Z`.
* But the record itself is only retrievable when querying with the **processing timestamp** (9/17 12:30 UTC).

#### Event Definitions

The list below summarizes common fields on each lead event. Not all fields appear on every `eventType`. For definitions of each `eventType` value, see [Event type reference](#event-type-reference).

**Common attributes**

* `id` — Unique event instance (see [Understanding event-level data](#understanding-event-level-data))
* `eventType`, `leadUuid`, `leadCreatedAt`, `eventCreatedAt`, `paginationTimestamp`
* `supplySubAccountUuid`, `supplySubAccountName` — Your integration / placement on the supply side
* `financialInstitutionUuid`, `financialInstitutionName` — Demand partner (lender) when applicable
* `productType`, `productSubType`, `unifiedProductType` — See [Product categorization](#product-categorization)
* `offerUuid` — Offer identifier when applicable
* `amountInCents` — Monetary amount for some lender-reported events (for example `funded`)
* `isTest` — Exclude from production reporting when `true`
* `eventDeletedAt` — Present if the event was removed; treat as a deletion of a previously streamed event

**Click events**

* **`offerClicked`** — The user selected a specific lender offer on the **Engine marketplace** (rate table / offer list). This is the usual “marketplace click” metric.
* **`affiliateOfferClicked`** — The user clicked through an **affiliate or inline link** placement. Do not combine with `offerClicked` without an explicit business rule.

## Error Handling

<table data-search="false"><thead><tr><th width="118.71875">Code</th><th>Meaning</th></tr></thead><tbody><tr><td>200</td><td>Success</td></tr><tr><td>206</td><td>Partial success (result truncated)</td></tr><tr><td>400</td><td>Bad request</td></tr><tr><td>401</td><td>Unauthorized (missing/invalid token)</td></tr><tr><td>404</td><td>Resource not found</td></tr><tr><td>422</td><td>Invalid request logic</td></tr><tr><td>5xx</td><td>Server error</td></tr></tbody></table>

## Best Practices

* If you need to retrieve historical data (e.g., when first enabling the API), set your initial `sinceTimestamp` at 00:00:00Z (midnight UTC) of your go-live date, and you'll be sure to capture all possible events for your account. Use `untilTimestamp` only (or no timestamps at all) to trigger a wider backfill windows, and request there is no `nextUrl`in your response.
  * If you want to retrieve all records SINCE a particular date, set only `sinceTimestamp`
  * if you want something up UNTIL a date, set only `untilTimestamp`
* Poll hourly; avoid polling more than once every 5 minutes
* Use `paginationToken` for paging, not `sinceTimestamp` after the first request
* Use explicit UTC timestamps, ideally with `Z` suffix to avoid any ambiguity.
* Build flexible integrations (new fields may be added)
* *NEVER* store PII in client tags when sending leads to the Engine API

#### Historical Data Retrieval & Backfills

Some of our channel partners may wish to pull a large amount of historical data (e.g. a backfill for catch-up). For most partners, the initial call can be made with no parameters to start from the beginning of time and then continuously follow the `nextUrl`. For retrieving historical data or performing a backfill:

1. Set a `sinceTimestamp` if you only want data *since* a particular date.
2. Optionally, also set an `untilTimestamp` for controlled ranges
3. Continue following the `nextUrl` until `shouldContinuePolling` is `false`

**JavaScript Pagination Example**

Here's a practical example of how to loop through pagination for data retrieval:

```javascript
async function fetchAllLeadEvents(apiToken, sinceTimestamp = null) {
  const allEvents = [];
  let url = 'https://api.engine.tech/supplyAnalytics/leadEvents';
  
  // Add timestamp for backfill if provided
  if (sinceTimestamp) {
    url += `?sinceTimestamp=${encodeURIComponent(sinceTimestamp)}`;
  }
  while (url) {
    try {
      const response = await fetch(url, {
        headers: {
          'Authorization': `Bearer ${apiToken}`,
          'API-Version': '2025-04-01'
        }
      });
      if (!response.ok) {
        throw new Error(`HTTP ${response.status}: ${response.statusText}`);
      }
      const data = await response.json();
      allEvents.push(...data.data);
      
      // Follow nextUrl if more data is available
      url = data.shouldContinuePolling ? data.nextUrl : null;
      
      // Add delay between requests to respect rate limits
      if (url) {
        await new Promise(resolve => setTimeout(resolve, 1000));
      } 
    } catch (error) {
      console.error('Error fetching lead events:', error);
      break;
    }
  }
  return allEvents;
}

// Usage examples:
// Fetch all historical data
const allEvents = await fetchAllLeadEvents(apiToken);

// Fetch data since specific timestamp
const recentEvents = await fetchAllLeadEvents(apiToken, '2025-09-01T00:00:00Z');
```

## Data Timing & Availability

Data appears in the API at different times depending on the source:

* Non-monetization event types: Typically available within minutes
* **Monetization events typically take 1 day to 2 weeks to be returned (since the Financial Institution must wait for the lead to convert, and then Engine must wait for the FI to report the conversion)**
* Recent data (last 5 minutes): Blocked by blackout window


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://even-financial.gitbook.io/developer-center/channel-partner-reporting/report-options/engine-analytics-api.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
