Skip to main content
The prequalification endpoint is a powerful affordability calculator that goes far beyond traditional mortgage calculators. Unlike other affordability tools that are blind to pricing and guidelines, Pylon’s prequalification endpoint validates against all guidelines and pricing across all takeouts to determine maximum affordability within eligible products.

Why use prequalification?

All products at once

Returns maximum affordability across all products in a single call, rather than requiring separate calculations per product.

Guideline-aware

Validates against all encoded guidelines and pricing rules, ensuring accurate qualification estimates.

Minimal input

Requires only 8 pieces of information to get comprehensive affordability results.

Real-time pricing

Uses live rates and guidelines at the time of calculation, not static assumptions.

How it works

Prequalification is an asynchronous operation. When you create a prequalification request, it returns a job ID that you must poll to get the results.
1

Create prequalification

Submit a mutation with borrower information to create a prequalification job.
2

Receive job ID

The mutation returns a job ID that you’ll use to poll for results.
3

Poll for completion

Poll the prequalification query endpoint until the job status indicates completion.
4

Retrieve results

Once complete, fetch the results showing maximum affordability across all eligible products.

Creating a prequalification

To create a prequalification, you need 8 pieces of information:
mutation {
  preQualification {
    conventional(
      input: {
        monthlyIncome: 10000
        monthlyDebt: 2000
        qualifyingFicoScore: 750
        fipsCountyCode: "06037"
        propertyUsageType: PRIMARY_RESIDENCE
        neighborhoodHousingType: SINGLE_FAMILY
        isFirstTimeHomeBuyer: true
        isBorrowerSelfEmployed: false
      }
    ) {
      id
    }
  }
}

Required input fields

FieldTypeDescription
monthlyIncomeFloat!Borrower’s total monthly income
monthlyDebtFloat!Borrower’s total monthly debt payments
fipsCountyCodeString!Five-digit FIPS county code for the property location
propertyUsageTypePropertyUsageType!PRIMARY_RESIDENCE, SECOND_HOME, or INVESTMENT
neighborhoodHousingTypeNeighborhoodHousingType!Property type (e.g., SINGLE_FAMILY, CONDOMINIUM)
isFirstTimeHomeBuyerBoolean!Whether the borrower is a first-time homebuyer
isBorrowerSelfEmployedBoolean!Whether the borrower is self-employed

Optional input fields

FieldTypeDescription
qualifyingFicoScoreIntFICO score for qualification (if not provided, results may vary by score range)

Response

The mutation returns a job ID:
{
  "data": {
    "preQualification": {
      "conventional": {
        "id": "prequal_abc123xyz"
      }
    }
  }
}
Save the id from the response. You’ll use it to poll for results.

Polling for results

After creating a prequalification, poll the query endpoint to check the job status and retrieve results:
query {
  preQualification {
    conventional(id: "prequal_abc123xyz") {
      id
      status
      result {
        maxLoanAmount
        maxPurchasePrice
        maxMonthlyPayment
        products {
          product {
            name
            type
          }
          maxLoanAmount
          maxPurchasePrice
          maxMonthlyPayment
        }
      }
      error {
        message
        code
      }
    }
  }
}

Job status

The status field indicates the current state of the prequalification:
StatusDescriptionAction
PENDINGJob is queued and processingContinue polling
COMPLETEDCalculation finished successfullyRead result field
FAILEDCalculation encountered an errorRead error field
CANCELLEDJob was cancelledNo results available

Polling strategy

Implement exponential backoff when polling to avoid excessive API calls:
async function pollPrequalification(
  id: string,
  maxAttempts: number = 30,
  initialDelayMs: number = 2000
): Promise<PrequalificationResult> {
  for (let attempt = 0; attempt < maxAttempts; attempt++) {
    const response = await fetchPrequalification(id);

    if (response.status === "COMPLETED") {
      return response.result;
    }

    if (response.status === "FAILED") {
      throw new Error(response.error?.message || "Prequalification failed");
    }

    // Exponential backoff: 2s, 4s, 8s, 16s, max 30s
    const delay = Math.min(initialDelayMs * Math.pow(2, attempt), 30000);

    await new Promise((resolve) => setTimeout(resolve, delay));
  }

  throw new Error("Prequalification polling timeout");
}
Polling intervals: Start with 2-5 second intervals and increase gradually. Most prequalifications complete within 10-30 seconds, but complex scenarios may take longer.

Understanding results

When the prequalification completes, the result field contains maximum affordability information:
{
  "data": {
    "preQualification": {
      "conventional": {
        "id": "prequal_abc123xyz",
        "status": "COMPLETED",
        "result": {
          "maxLoanAmount": 450000,
          "maxPurchasePrice": 562500,
          "maxMonthlyPayment": 3500,
          "products": [
            {
              "product": {
                "name": "Conventional 30-Year Fixed",
                "type": "CONVENTIONAL"
              },
              "maxLoanAmount": 450000,
              "maxPurchasePrice": 562500,
              "maxMonthlyPayment": 3500
            },
            {
              "product": {
                "name": "FHA 30-Year Fixed",
                "type": "FHA"
              },
              "maxLoanAmount": 420000,
              "maxPurchasePrice": 466667,
              "maxMonthlyPayment": 3200
            }
          ]
        }
      }
    }
  }
}

Result fields

FieldDescription
maxLoanAmountMaximum loan amount across all eligible products
maxPurchasePriceMaximum purchase price (loan amount + down payment)
maxMonthlyPaymentMaximum monthly payment the borrower can afford
productsArray of results per product, showing maximums for each eligible product
The top-level maxLoanAmount, maxPurchasePrice, and maxMonthlyPayment represent the highest values across all products. Use the products array to show borrowers product-specific maximums.

Complete example

Here’s a complete TypeScript example that creates a prequalification and polls for results:
interface PrequalificationInput {
  monthlyIncome: number;
  monthlyDebt: number;
  qualifyingFicoScore?: number;
  fipsCountyCode: string;
  propertyUsageType: "PRIMARY_RESIDENCE" | "SECOND_HOME" | "INVESTMENT";
  neighborhoodHousingType: string;
  isFirstTimeHomeBuyer: boolean;
  isBorrowerSelfEmployed: boolean;
}

async function runPrequalification(
  input: PrequalificationInput,
  accessToken: string
): Promise<PrequalificationResult> {
  // Step 1: Create prequalification
  const createMutation = `
    mutation CreatePrequalification($input: ConventionalPreQualificationInput!) {
      preQualification {
        conventional(input: $input) {
          id
        }
      }
    }
  `;

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

  const { data: createData } = await createResponse.json();
  const prequalId = createData.preQualification.conventional.id;

  // Step 2: Poll for results
  const query = `
    query GetPrequalification($id: ID!) {
      preQualification {
        conventional(id: $id) {
          id
          status
          result {
            maxLoanAmount
            maxPurchasePrice
            maxMonthlyPayment
            products {
              product {
                name
                type
              }
              maxLoanAmount
              maxPurchasePrice
              maxMonthlyPayment
            }
          }
          error {
            message
            code
          }
        }
      }
    }
  `;

  // Poll with exponential backoff
  for (let attempt = 0; attempt < 30; 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: prequalId },
      }),
    });

    const { data: pollData } = await pollResponse.json();
    const prequal = pollData.preQualification.conventional;

    if (prequal.status === "COMPLETED") {
      return prequal.result;
    }

    if (prequal.status === "FAILED") {
      throw new Error(prequal.error?.message || "Prequalification failed");
    }

    // Wait before next poll (exponential backoff)
    const delay = Math.min(2000 * Math.pow(2, attempt), 30000);
    await new Promise((resolve) => setTimeout(resolve, delay));
  }

  throw new Error("Prequalification polling timeout");
}

Best practices

  1. Handle errors gracefully: Check the error field when status is FAILED and provide meaningful messages to borrowers.
  2. Set reasonable timeouts: Most prequalifications complete within 30 seconds, but set a maximum timeout (e.g., 2-3 minutes) to avoid indefinite polling.
  3. Cache results: Prequalification results are valid for a period of time. Consider caching results with the input parameters as a key to avoid redundant calculations.
  4. Show product breakdown: Display results per product so borrowers can see which products offer the highest affordability.
  5. Explain limitations: Help borrowers understand that prequalification is an estimate and actual qualification may vary based on complete application details.
  6. Use for lead qualification: Prequalification is perfect for initial borrower conversations to quickly assess affordability before creating a full loan application.

Error handling

Common error scenarios and how to handle them:
Error CodeDescriptionResolution
INVALID_INPUTOne or more input fields are invalidValidate inputs before submission
GUIDELINE_VIOLATIONBorrower doesn’t meet minimum guidelinesExplain qualification requirements to borrower
RATE_UNAVAILABLENo rates available for the given parametersTry adjusting inputs or explain market conditions
TIMEOUTCalculation took too longRetry the prequalification request