Skip to main content
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”):

No credit impact

Does not affect the borrower’s credit score

No trigger leads

Does not generate trigger leads to other lenders

Pre-qualification

Suitable for pre-qualification and initial pricing

Borrower-friendly

Better borrower experience with no unwanted solicitations
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”):

Credit impact

May slightly lower credit score (typically 5-10 points)

Trigger leads

Generates trigger leads, resulting in solicitations from competing lenders

Underwriting

Required for final underwriting and loan approval

High intent

Best used when borrower is committed to proceeding
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

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.
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. Before pulling credit, set the borrower’s credit pull consent:
mutation {
  borrower {
    update(
      id: "borrower_abc123"
      input: {
        creditPullConsent: {
          type: SOFT # or HARD
          consentedAt: "2024-01-15T10:00:00Z"
        }
      }
    ) {
      borrower {
        id
        creditPullConsent {
          type
          consentedAt
        }
      }
    }
  }
}
If you set consent for SOFT pull, you can only perform SOFT pulls. If you set consent for HARD pull, you can perform HARD pulls (but SOFT pulls are also allowed with HARD consent).

Credit pull workflow

Credit pulls are asynchronous operations. Here’s the complete workflow:
1

Obtain consent

Set the borrower’s credit pull consent with the appropriate type (SOFT or HARD).
2

Initiate credit pull

Trigger the credit pull mutation with the consent type.
3

Receive job ID

The mutation returns a job ID for tracking the credit pull status.
4

Poll for completion

Poll the credit pull status until it completes. See Tracking Loan Updates for polling implementation.
5

Automatic liability creation

Upon successful credit pull, Pylon automatically creates liabilities on your behalf. You do not need to create these manually.

Initiating a credit pull

mutation {
  credit {
    pull(
      loanId: "loan_abc123"
      type: SOFT # or HARD
    ) {
      id
      status
    }
  }
}

Polling for credit results

query {
  credit {
    pull(id: "credit_pull_abc123") {
      id
      status
      completedAt
      report {
        ficoScore
        creditBureau
        # ... other credit report fields
      }
      error {
        message
        code
      }
    }
  }
}
Polling frequency: For active credit pulls, poll every 30 seconds to 2 minutes. Most credit pulls complete within 2-5 minutes.

Waterfall approach

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

Primary vendor

System attempts primary credit vendor first

Automatic fallback

If primary vendor is down, automatically tries backup vendors

High reliability

Ensures credit pulls succeed even during vendor outages
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.

Automatic liability creation

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.
After a credit pull completes successfully, query the loan to see the automatically created liabilities:
query {
  loan(id: "loan_abc123") {
    borrowers {
      liabilities {
        id
        type
        accountType
        monthlyPayment
        balance
        creditorName
      }
    }
  }
}

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:
async function pullCredit(
  loanId: string,
  borrowerId: string,
  pullType: "SOFT" | "HARD",
  accessToken: string
): Promise<CreditReport> {
  // Step 1: Set consent
  const setConsentMutation = `
    mutation SetCreditConsent($id: ID!, $type: CreditPullType!, $consentedAt: DateTime!) {
      borrower {
        update(
          id: $id
          input: {
            creditPullConsent: {
              type: $type
              consentedAt: $consentedAt
            }
          }
        ) {
          borrower {
            id
            creditPullConsent {
              type
              consentedAt
            }
          }
        }
      }
    }
  `;

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

  // Step 2: Initiate credit pull
  const pullCreditMutation = `
    mutation PullCredit($loanId: ID!, $type: CreditPullType!) {
      credit {
        pull(loanId: $loanId, type: $type) {
          id
          status
        }
      }
    }
  `;

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

  const { data: pullData } = await pullResponse.json();
  const creditPullId = pullData.credit.pull.id;

  // Step 3: Poll for completion
  const query = `
    query GetCreditPull($id: ID!) {
      credit {
        pull(id: $id) {
          id
          status
          completedAt
          report {
            ficoScore
            creditBureau
          }
          error {
            message
            code
          }
        }
      }
    }
  `;

  // 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: creditPullId },
      }),
    });

    const { data: pollData } = await pollResponse.json();
    const creditPull = pollData.credit.pull;

    if (creditPull.status === "COMPLETED") {
      return creditPull.report;
    }

    if (creditPull.status === "FAILED") {
      throw new Error(creditPull.error?.message || "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");
}