> ## 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.

# Credit pulls

> Learn how to properly obtain consent for credit pulls, understand the difference between SOFT and HARD pulls, and implement credit pull workflows with Pylon's waterfall approach.

Credit pulls are a critical component of the loan origination process. Pylon requires explicit consent before pulling credit, and the consent type must match the type of credit pull (`SOFT` or `HARD`). Understanding the difference between these pull types and when to use each is essential for protecting borrowers and avoiding trigger leads.

## Understanding SOFT vs. HARD pulls

The choice between `SOFT` and `HARD` credit pulls has significant implications for borrower experience and deal retention.

### SOFT pulls

A `SOFT` pull (also called a "soft inquiry"):

<CardGroup cols={2}>
  <Card title="No credit impact" icon="shield-check">
    Does not affect the borrower's credit score
  </Card>

  <Card title="No trigger leads" icon="bell-off">
    Does not generate trigger leads to other lenders
  </Card>

  <Card title="Pre-qualification" icon="search">
    Suitable for pre-qualification and initial pricing
  </Card>

  <Card title="Borrower-friendly" icon="heart">
    Better borrower experience with no unwanted solicitations
  </Card>
</CardGroup>

**Use `SOFT` pulls when:**

* Running pre-qualification
* Getting initial pricing scenarios
* Pulling credit before the loan is in underwriting stage
* Borrower is still shopping or comparing options

### HARD pulls

A `HARD` pull (also called a "hard inquiry"):

<CardGroup cols={2}>
  <Card title="Credit impact" icon="alert-triangle">
    May slightly lower credit score (typically 5-10 points)
  </Card>

  <Card title="Trigger leads" icon="bell">
    Generates trigger leads, resulting in solicitations from competing lenders
  </Card>

  <Card title="Underwriting" icon="file-check">
    Required for final underwriting and loan approval
  </Card>

  <Card title="High intent" icon="target">
    Best used when borrower is committed to proceeding
  </Card>
</CardGroup>

**Use `HARD` pulls when:**

* Loan is in underwriting stage
* Borrower has high intent and is committed to closing
* Final credit verification is required for approval

**Trigger leads can lose you the deal**: `HARD` pulls generate trigger leads that notify competing lenders about the borrower's mortgage application. These lenders will immediately contact the borrower with competing offers. This can:

* Annoy borrowers with unwanted calls and emails
* Lead to price shopping and deal loss
* Damage borrower trust in your relationship

Always use `SOFT` pulls early in the process to avoid trigger leads until the borrower is committed to closing with you.

## Sandbox environment limitations

<Warning>
  **Sandbox only supports HARD pulls**: When building end-to-end testing scenarios in sandbox, you must use `HARD` pulls. `SOFT` pulls will not return any credit data or create liabilities. This limitation only applies to sandbox. Production supports both `SOFT` and `HARD` pulls.
</Warning>

## Consent requirements

You MUST obtain consent from borrowers before pulling credit. The consent type must exactly match the type of credit pull (`SOFT` or `HARD`). Pulling credit without proper consent is a compliance violation.

### Setting consent

Before pulling credit, set the borrower's credit pull consent:

```graphql theme={null}
mutation {
  borrower {
    updateConsent(
      input: {
        borrowerId: "borrower_abc123"
        softCreditConsentType: ELECTRONIC
        softCreditConsentDate: "2024-01-15T10:00:00Z"
        # For HARD pulls, set hardCreditConsentType / hardCreditConsentDate instead
      }
    ) {
      borrowerId
      softCreditConsentType
      softCreditConsentDate
    }
  }
}
```

The consent type (`ELECTRONIC`, `VERBAL`, or `WRITTEN`) records how the borrower granted consent. Set the soft consent fields before a `SOFT` pull and the hard consent fields before a `HARD` pull.

## Credit pull workflow

Credit pulls are **asynchronous operations**. Here's the complete workflow:

<Steps>
  <Step title="Obtain consent">
    Set the borrower's credit pull consent with the appropriate type (`SOFT` or
    `HARD`).
  </Step>

  <Step title="Initiate credit pull">
    Trigger the credit pull mutation with the consent type.
  </Step>

  <Step title="Receive job ID">
    The mutation returns a job ID for tracking the credit pull status.
  </Step>

  <Step title="Poll for completion">
    Poll the credit pull status until it completes. See [Tracking Loan
    Updates](/recipes/loan-updates) for polling implementation.
  </Step>

  <Step title="Automatic liability creation">
    Upon successful credit pull, Pylon automatically creates liabilities on your
    behalf. You do not need to create these manually.
  </Step>
</Steps>

### Initiating a credit pull

```graphql theme={null}
mutation {
  credit {
    startIndividualCreditPullJob(
      input: {
        borrower: {
          id: "borrower_abc123"
          consentType: ELECTRONIC
        }
        pullType: SOFT # or HARD
      }
    ) {
      job {
        id
        status
      }
      userErrors
    }
  }
}
```

### Polling for credit results

Use the credit pull job `id` to poll for status. When the job status is `SUCCEEDED`, the `result` field is populated with the credit report:

```graphql theme={null}
query {
  credit {
    job(id: "credit_pull_job_abc123") {
      id
      status
      result {
        id
        pullTime
        expiryStatus
        bureauStatuses {
          bureau
          fileReturned
          resultStatus
        }
      }
    }
  }
}
```

<Info>
  **Polling frequency**: For active credit pulls, poll every 30 seconds to 2
  minutes. Most credit pulls complete within 2-5 minutes.
</Info>

## Waterfall approach

Pylon uses a **waterfall approach** for credit pulls, meaning if one vendor is unavailable, the system automatically falls back to backup vendors.

<CardGroup cols={3}>
  <Card title="Primary vendor" icon="check-circle">
    System attempts primary credit vendor first
  </Card>

  <Card title="Automatic fallback" icon="refresh">
    If primary vendor is down, automatically tries backup vendors
  </Card>

  <Card title="High reliability" icon="shield">
    Ensures credit pulls succeed even during vendor outages
  </Card>
</CardGroup>

<Note>
  **Why this matters**: Credit is a critical piece of the upfront origination
  process and is required in many cases for loan sale. The waterfall approach
  ensures high reliability and availability.
</Note>

## Automatic liability creation

<Info>
  **No manual work required**: Upon successful credit pull, Pylon automatically
  creates liabilities on your behalf based on the credit report. You do not need
  to manually create these liabilities.
</Info>

After a credit pull completes successfully, query the loan to see the automatically created liabilities:

```graphql theme={null}
query {
  loan(id: "loan_abc123") {
    borrowers {
      liabilities {
        edges {
          node {
            id
            type
            accountIdentifier
            monthlyPayment
            balance
            bankName
          }
        }
      }
    }
  }
}
```

## Best practices

1. **Always get consent first**: Never pull credit without explicit consent from the borrower.

2. **Match consent type**: Ensure the consent type exactly matches the credit pull type.

3. **Use `SOFT` pulls early**: For loan applications before underwriting, always use `SOFT` pulls to avoid trigger leads.

4. **Use `HARD` pulls late**: Only use `HARD` pulls when the loan is in underwriting and the borrower is committed to closing.

5. **Poll appropriately**: Use appropriate polling intervals (30 seconds to 2 minutes) for active credit pulls.

6. **Handle errors gracefully**: If a credit pull fails, check the error message and retry if appropriate.

## Agent prompts

```
When building loan applications, you must first obtain and set credit pull consent for the borrower before attempting to pull credit. The consent type (SOFT or HARD) must match the type of credit pull you intend to perform. Never pull credit without proper consent.

If you're building a loan application and pulling credit before the loan is in the underwriting stage, always use a SOFT pull to avoid trigger leads. Trigger leads from HARD pulls can annoy borrowers and may result in losing the deal due to price shopping. Only use HARD pulls when the loan is in underwriting and the borrower is committed to closing.
```

## Example: complete credit pull flow

Here's a complete TypeScript example:

```typescript theme={null}
async function pullCredit(
  loanId: string,
  borrowerId: string,
  pullType: "SOFT" | "HARD",
  accessToken: string
): Promise<CreditReport> {
  // Step 1: Set consent
  const setConsentMutation = `
    mutation SetCreditConsent(
      $borrowerId: ID!
      $consentType: CreditPullConsentType!
      $consentDate: DateTime!
    ) {
      borrower {
        updateConsent(
          input: {
            borrowerId: $borrowerId
            softCreditConsentType: $consentType
            softCreditConsentDate: $consentDate
          }
        ) {
          borrowerId
          softCreditConsentType
          softCreditConsentDate
        }
      }
    }
  `;

  await fetch("https://pylon.mortgage/graphql", {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      Authorization: `Bearer ${accessToken}`,
    },
    body: JSON.stringify({
      query: setConsentMutation,
      variables: {
        borrowerId,
        consentType: "ELECTRONIC",
        consentDate: new Date().toISOString(),
      },
    }),
  });

  // Step 2: Initiate credit pull
  const pullCreditMutation = `
    mutation PullCredit($input: StartIndividualCreditPullJobInput!) {
      credit {
        startIndividualCreditPullJob(input: $input) {
          job {
            id
            status
          }
          userErrors
        }
      }
    }
  `;

  const pullResponse = await fetch("https://pylon.mortgage/graphql", {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      Authorization: `Bearer ${accessToken}`,
    },
    body: JSON.stringify({
      query: pullCreditMutation,
      variables: {
        input: {
          borrower: { id: borrowerId, consentType: "ELECTRONIC" },
          pullType,
        },
      },
    }),
  });

  const { data: pullData } = await pullResponse.json();
  const creditPullJobId = pullData.credit.startIndividualCreditPullJob.job.id;

  // Step 3: Poll for completion
  const query = `
    query GetCreditPull($id: ID!) {
      credit {
        job(id: $id) {
          id
          status
          result {
            id
            pullTime
            expiryStatus
            bureauStatuses {
              bureau
              fileReturned
              resultStatus
            }
          }
        }
      }
    }
  `;

  // Poll with exponential backoff
  for (let attempt = 0; attempt < 20; attempt++) {
    const pollResponse = await fetch("https://pylon.mortgage/graphql", {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
        Authorization: `Bearer ${accessToken}`,
      },
      body: JSON.stringify({
        query,
        variables: { id: creditPullJobId },
      }),
    });

    const { data: pollData } = await pollResponse.json();
    const creditJob = pollData.credit.job;

    if (creditJob.status === "SUCCEEDED") {
      return creditJob.result;
    }

    if (creditJob.status === "FAILED") {
      throw new Error("Credit pull failed");
    }

    // Wait before next poll (30 seconds to 2 minutes)
    const delay = Math.min(30000 * (attempt + 1), 120000);
    await new Promise((resolve) => setTimeout(resolve, delay));
  }

  throw new Error("Credit pull polling timeout");
}
```

## Related resources

* [Tracking loan updates](/recipes/loan-updates) - Learn how to poll for asynchronous job status
* [Order-Outs Overview](/guides/getting-started/order-outs-and-integrations/overview) - Understand when order-outs are triggered
* [Complete integration guide](/guides/getting-started/e2e-build) - Build a full loan origination flow
