> ## Documentation Index
> Fetch the complete documentation index at: https://docs.pylon.mortgage/llms.txt
> Use this file to discover all available pages before exploring further.

# Asset verification

> Learn how Pylon automatically verifies borrower assets using Plaid, eliminating manual documentation and bank statement uploads.

## What is asset verification?

**Asset verification** is the process of confirming a borrower's asset balances and account information to assess their ability to cover down payment, closing costs, and required reserves. Lenders must verify that borrowers have sufficient funds in their accounts to complete the mortgage transaction.

Traditionally, asset verification has been a manual, time-consuming process requiring borrowers to:

* Upload bank statements (typically 2-3 months)
* Provide account verification letters (VOA/VOD)
* Manually enter account balances and transaction history
* Wait for manual document review and verification

Pylon automates this entire process using **Day 1 Certainty (D1C) and Asset and Income Modeler (AIM) approved services** that verify asset data automatically, reducing manual documentation and speeding up loan processing.

## Why D1C/AIM asset verification matters

**Day 1 Certainty (D1C)** and **Asset and Income Modeler (AIM)** are programs from Fannie Mae and Freddie Mac that allow lenders to use automated verification sources to reduce conditions and speed up loan processing.

When asset data comes from D1C/AIM approved sources, it's considered a "verified" source, meaning no additional documentation is typically required, underwriting conditions are reduced, and loan processing is faster.

## Pylon's asset verification integrations

Pylon currently uses **Plaid** for D1C/AIM approved asset verification. Pylon will expand to support Truv for asset verification in the future.

* **Plaid** - Account connection-based asset verification (requires borrower opt-in and your API call)

<Info>
  **Assets only**: Plaid is approved for D1C and AIM for **assets only**. While Plaid offers income verification products, those are not yet approved for D1C/AIM, so income items still require traditional documentation. See [Income Verification](/guides/getting-started/order-outs-and-integrations/d1c-aim/income-verification) for income verification options.
</Info>

## Plaid

**Plaid** is an account connection-based asset verification service that allows borrowers to connect their bank accounts to automatically verify asset balances and account information. Pylon is a reseller of Plaid and will create an account and API credentials on your behalf.

### How Plaid works

<Steps>
  <Step title="Request initialization token from Pylon">
    You request an initialization token from Pylon for the borrower. Pylon provides you with a `linkToken` to initialize the [Plaid Link](https://plaid.com/docs/link/) component.
  </Step>

  <Step title="Initialize Plaid Link component">
    Use the `linkToken` provided by Pylon to initialize the [Plaid Link](https://plaid.com/docs/link/) component for that borrower in your application.
  </Step>

  <Step title="Borrower opts in and connects accounts">
    The borrower opts in and connects their bank accounts through Plaid's secure connection flow. This allows Plaid to access account balance and transaction information.
  </Step>

  <Step title="Widget exchanges public token">
    After the borrower completes authentication, the [Plaid Link](https://plaid.com/docs/link/) widget automatically exchanges the `publicToken` with Pylon via the `onSuccess` callback (passed as a parameter to the widget). This establishes permanent access to those accounts—you don't need to explicitly exchange the token.
  </Step>

  <Step title="Pylon handles data retrieval">
    Pylon automatically retrieves verified asset data from Plaid asynchronously. This includes account balances, account types, and institution information.
  </Step>

  <Step title="Pylon uses verified data">
    Verified asset data is automatically stored in the loan file. Pylon uses this verified data for underwriting and qualification calculations.
  </Step>

  <Step title="No additional documentation needed">
    Since the data comes from Plaid (a D1C and AIM approved source), no additional asset documentation is typically required. Pylon's underwriting system accepts the verified data without additional documentation.
  </Step>
</Steps>

### Plaid characteristics

* **Client opt-in required**: Borrowers must opt in and connect their accounts through Plaid's connection flow
* **You must call it**: Request an initialization token from Pylon and initialize the [Plaid Link](https://plaid.com/docs/link/) component—it does not happen automatically
* **>95% coverage**: Plaid has over 95% coverage of banks and financial institutions
* **Assets only**: Plaid is approved for D1C/AIM for assets only, not income verification
* **Pylon handles data mapping**: Pylon automatically maps all account types (checking, savings, etc.) to appropriate Pylon asset fields—no work required on your end

### Handling stale credentials (update mode)

After a borrower connects their bank account through Plaid, the connection can become **stale** — for example, if the borrower changes their bank password or their session expires. When this happens, Plaid reports an `ITEM_LOGIN_REQUIRED` error, and the asset report cannot be refreshed until the borrower re-authenticates.

Pylon exposes an **update mode** flow that lets the borrower fix their credentials without creating a new Plaid Item or losing their existing connection.

<Steps>
  <Step title="Detect stale credentials">
    When Pylon encounters an `ITEM_LOGIN_REQUIRED` error from Plaid, the error
    is surfaced in the GraphQL response's `errorDetails.plaidErrorCode` field.
    Your application should watch for this error code.
  </Step>

  <Step title="Request an update mode token">
    Call the `createUpdateModeToken` mutation with the borrower ID and the
    Plaid item ID that needs re-authentication. Pylon returns a `linkToken`
    scoped to the existing item.

    ```graphql theme={null}
    mutation CreateUpdateModeToken($input: CreateUpdateModeTokenInput!) {
      verify {
        createUpdateModeToken(input: $input) {
          linkToken
        }
      }
    }
    ```

    **Input fields:**

    * `borrowerId` — ID of the borrower whose connection is stale
    * `itemId` — Plaid item ID of the specific connection that needs re-authentication
  </Step>

  <Step title="Open Plaid Link in update mode">
    Use the returned `linkToken` to initialize the
    [Plaid Link](https://plaid.com/docs/link/) component. Because the token
    is scoped to an existing item, Plaid Link opens in **update mode** — the
    borrower only needs to re-enter their credentials for the specific bank,
    not go through the full account selection flow.
  </Step>

  <Step title="Pylon automatically retries">
    After the borrower re-authenticates, Plaid notifies Pylon via a
    `LOGIN_REPAIRED` webhook. Pylon automatically retries the asset report
    generation — no additional API call is needed from your side.
  </Step>
</Steps>

<Info>
  **Automatic recovery**: Once the borrower re-authenticates, Pylon handles
  everything else — retrying the asset report, retrieving the data, and
  updating the loan file. You only need to detect the stale credential error
  and present the update mode Plaid Link to the borrower.
</Info>

## Plaid reseller program

Pylon is a **reseller of Plaid**, which means:

* **Credentials managed by Pylon**: Pylon can obtain Plaid credentials on behalf of clients
* **Billing through Pylon**: Plaid usage is billed through Pylon's main account
* **Simplified setup**: Clients don't need to manage their own Plaid account or credentials

<Info>
  **Simplified integration**: As a Plaid reseller, Pylon handles credential management and billing, making it easier for clients to integrate Plaid without managing separate accounts.
</Info>

## Integration options

Pylon clients integrate Plaid using **[Plaid Link](https://plaid.com/docs/link/)**, Plaid's standard web-based authentication component. Plaid Link is a pre-built UI component that handles the entire bank connection flow, allowing borrowers to securely connect their bank accounts through a modal interface.

**How Plaid Link works:**

* Borrower clicks a button in your application
* Plaid Link modal opens
* Borrower selects their bank and authenticates
* Asset report is generated and sent to Pylon

<Note>
  **Seamless experience**: Plaid Link provides a seamless borrower experience and is the standard integration method for Plaid asset verification. For complete implementation details, see the [Plaid Link documentation](https://plaid.com/docs/link/).
</Note>

## Checking asset verification status

After asset verification is initiated (after borrower opt-in for Plaid), check the `amount` field on Asset entities to see if verification was successful. When assets are verified through Plaid, the `amount` field will be populated with verified account balances, and Pylon will use this verified data for underwriting without requiring additional documentation.

For complete details on querying asset data, understanding asset types, and all asset fields, see the [Assets entity guide](/entity-models/assets).

### GraphQL query example

Poll Pylon's GraphQL API to check for verified asset values. The `amount` field will be populated when assets have been successfully verified through Plaid:

```graphql theme={null}
query GetVerifiedAssets($loanId: ID!) {
  loan(id: $loanId) {
    borrowers(first: 10) {
      edges {
        node {
          id
          firstName
          lastName
          assets {
            id
            __typename
            amount
            ... on FinancialAccountAsset {
              institutionName
              accountIdentifier
            }
            ... on CheckingAccountAsset {
              accountIdentifier
              institutionName
            }
            ... on SavingsAccountAsset {
              accountIdentifier
              institutionName
            }
          }
        }
      }
    }
  }
}
```

**Key fields:**

* `amount`: The verified account balance. `null` if not yet verified, or a number if verified through Plaid
* `__typename`: The concrete asset kind (e.g., `CheckingAccountAsset`, `SavingsAccountAsset`), used to select the matching inline fragment
* `institutionName`: The name of the financial institution (for financial account assets)
* `accountIdentifier`: The account identifier or account number (for financial account assets)

**Verification status:**

* `amount === null`: Asset has not been verified yet, or verification is not available
* `amount > 0`: Asset has been successfully verified through D1C/AIM approved sources

### Node.js/TypeScript polling example

Here's a complete example of polling for verified asset values using Node.js and TypeScript:

```typescript theme={null}
import { GraphQLClient } from 'graphql-request';

const GET_VERIFIED_ASSETS = `
  query GetVerifiedAssets($loanId: ID!) {
    loan(id: $loanId) {
      borrowers(first: 10) {
        edges {
          node {
            id
            firstName
            lastName
            assets {
              id
              __typename
              amount
              ... on FinancialAccountAsset {
                institutionName
                accountIdentifier
              }
              ... on CheckingAccountAsset {
                accountIdentifier
                institutionName
              }
              ... on SavingsAccountAsset {
                accountIdentifier
                institutionName
              }
            }
          }
        }
      }
    }
  }
`;

interface FinancialAccountAsset {
  id: string;
  __typename: string;
  amount: number | null;
  institutionName?: string;
  accountIdentifier?: string;
}

interface BorrowerNode {
  id: string;
  firstName: string;
  lastName: string;
  assets: FinancialAccountAsset[];
}

interface LoanData {
  loan: {
    borrowers: {
      edges: Array<{
        node: BorrowerNode;
      }>;
    };
  };
}

/**
 * Polls for verified asset values for a given loan
 * @param client - GraphQL client configured with your Pylon API endpoint and auth
 * @param loanId - The ID of the loan to check
 * @param maxAttempts - Maximum number of polling attempts (default: 30)
 * @param intervalMs - Polling interval in milliseconds (default: 2000)
 * @returns Promise resolving to verified assets or null if verification not available
 */
async function pollForVerifiedAssets(
  client: GraphQLClient,
  loanId: string,
  maxAttempts: number = 30,
  intervalMs: number = 2000
): Promise<FinancialAccountAsset[] | null> {
  for (let attempt = 0; attempt < maxAttempts; attempt++) {
    const data = await client.request<LoanData>(GET_VERIFIED_ASSETS, {
      loanId,
    });

    const allAssets: FinancialAccountAsset[] = [];

    // Collect all assets from all borrowers
    for (const borrowerEdge of data.loan.borrowers.edges) {
      const borrower = borrowerEdge.node;

      for (const asset of borrower.assets) {
        allAssets.push(asset);

        // Check if this asset has been verified
        if (asset.amount !== null && asset.amount > 0) {
          console.log(
            `✓ Asset verified for ${borrower.firstName} ${borrower.lastName}: ` +
            `${asset.amount} (${asset.__typename}) at ${asset.institutionName || 'Unknown Institution'}`
          );
        }
      }
    }

    // Check if any assets have been verified
    const verifiedAssets = allAssets.filter(
      (asset) => asset.amount !== null && asset.amount > 0
    );

    if (verifiedAssets.length > 0) {
      return verifiedAssets;
    }

    // If no verified assets yet, wait before next attempt
    if (attempt < maxAttempts - 1) {
      console.log(
        `No verified assets yet (attempt ${attempt + 1}/${maxAttempts}). ` +
        `Polling again in ${intervalMs}ms...`
      );
      await new Promise((resolve) => setTimeout(resolve, intervalMs));
    }
  }

  console.log('Asset verification not available after maximum attempts');
  return null;
}

// Example usage
const client = new GraphQLClient('https://api.pylon.mortgage/graphql', {
  headers: {
    Authorization: `Bearer ${process.env.PYLON_API_KEY}`,
  },
});

// Poll for verified assets
pollForVerifiedAssets(client, 'loan-123')
  .then((verifiedAssets) => {
    if (verifiedAssets) {
      console.log(`Found ${verifiedAssets.length} verified asset(s)`);
      // Use verified asset data for your application logic
    } else {
      console.log('Asset verification not available - fallback to traditional methods');
      // Fallback to requesting bank statements, VOA/VOD, etc.
    }
  })
  .catch((error) => {
    console.error('Error polling for verified assets:', error);
  });
```

**Key implementation details:**

* **Polling strategy**: The function polls at regular intervals (default: every 2 seconds) until verified assets are found or max attempts are reached
* **Verification check**: Checks if `amount` is not null and greater than 0
* **Error handling**: Includes error handling and fallback logic for when verification isn't available
* **Multiple borrowers**: Handles loans with multiple borrowers and collects all assets
* **Asset types**: Handles different asset types (checking, savings, retirement accounts, etc.)

## Benefits

### For borrowers

* **No document upload**: No need to upload bank statements when verification is successful
* **Real-time data**: Asset data is current and accurate
* **Faster processing**: Automated verification is faster than manual document review
* **Better experience**: Seamless connection process vs. manual document collection

### For lenders

* **Reduced conditions**: Pylon's underwriting system generates fewer conditions when assets are verified through D1C/AIM sources
* **Fewer borrower tasks**: Pylon creates fewer tasks for borrowers when verification is successful
* **Faster closes**: Pylon's automated processing accelerates loan processing and reduces time to close
* **Better accuracy**: Verified data from Plaid is more accurate than manual entry or document review
* **Cost savings**: Reduced manual processing and document review costs

## Limitations and fallbacks

### When automated verification doesn't work

Asset verification through Plaid may not be available if:

* **Plaid opt-in not completed**: The borrower didn't opt in to Plaid or didn't complete the account connection process
* **Bank not covered**: The borrower's bank doesn't participate in Plaid
* **Account type not supported**: Some account types may not be supported by Plaid
* **Connection issues**: Technical issues preventing successful account connection

### Fallback to traditional methods

When automatic verification isn't available, Pylon will request traditional asset verification documentation from borrowers:

* **Bank statements**: Recent bank statements (typically 2-3 months)
* **Account verification letters**: VOA (Verification of Assets) or VOD (Verification of Deposit) letters from banks
* **Account statements**: Investment account statements, retirement account statements, etc.
* **Gift letters**: For gift funds, gift letters and transfer documentation

<Warning>
  **Always have a fallback**: Don't assume all borrowers will have assets verified through Plaid. Plaid requires borrower opt-in, and some banks may not be covered. Always be prepared to use traditional verification methods when automated verification isn't available.
</Warning>

## Best practices

### Monitoring verification status

* **Poll regularly**: Check asset verification status as part of your loan status polling workflow
* **For Plaid**: Request initialization tokens from Pylon and initialize the [Plaid Link](https://plaid.com/docs/link/) component for borrowers who opt in
* **Handle failures gracefully**: If verification fails or isn't available, immediately switch to traditional verification methods

### Using verified data

* **Combine with other verifications**: Use Plaid alongside other D1C/AIM approved sources (like Truv or The Work Number for income verification) for maximum benefit
* **Pylon updates qualification automatically**: Once assets are verified, Pylon automatically recalculates qualification and eligibility, as verified assets may affect loan terms and reserve requirements

## Related resources

* [Assets Entity Guide](/entity-models/assets) - Complete guide to the Asset entity, including all asset types and fields
* [Day 1 Certainty & AIM Overview](/guides/getting-started/order-outs-and-integrations/d1c-aim/overview) - Learn about D1C and AIM programs and their benefits
* [Income Verification](/guides/getting-started/order-outs-and-integrations/d1c-aim/income-verification) - Learn about income verification through D1C/AIM sources
* [Order-Outs Overview](/guides/getting-started/order-outs-and-integrations/overview) - Understand when order-outs and verifications are triggered
