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

# Prequalification

> Determine maximum affordability across all products with just 8 pieces of information. This asynchronous endpoint validates against all guidelines and pricing to find the highest loan amount a borrower can qualify for.

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?

<CardGroup cols={2}>
  <Card title="All products at once" icon="grid">
    Returns maximum affordability across all products in a single call, rather
    than requiring separate calculations per product.
  </Card>

  <Card title="Guideline-aware" icon="check-circle">
    Validates against all encoded guidelines and pricing rules, ensuring
    accurate qualification estimates.
  </Card>

  <Card title="Minimal input" icon="input">
    Requires only 8 pieces of information to get comprehensive affordability
    results.
  </Card>

  <Card title="Real-time pricing" icon="bolt">
    Uses live rates and guidelines at the time of calculation, not static
    assumptions.
  </Card>
</CardGroup>

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

<Steps>
  <Step title="Create prequalification">
    Submit a mutation with borrower information to create a prequalification
    job.
  </Step>

  <Step title="Receive job ID">
    The mutation returns a job ID that you'll use to poll for results.
  </Step>

  <Step title="Poll for completion">
    Poll the prequalification query endpoint until the job status indicates
    completion.
  </Step>

  <Step title="Retrieve results">
    Once complete, fetch the results showing maximum affordability across all
    eligible products.
  </Step>
</Steps>

## Creating a prequalification

To create a prequalification, you need 8 pieces of information:

```graphql theme={null}
mutation {
  preQualification {
    conventional(
      input: {
        fipsCountyCode: "06037"
        qualifyingFicoScore: 750
        qualifyingAssetsTotalAmount: 120000
        qualifyingMonthlyIncome: 10000
        qualifyingMonthlyExpenses: 2000
        propertyUsageType: PRIMARY_RESIDENCE
        neighborhoodHousingType: SINGLE_FAMILY
        hoaDues: 0
        maxDiscountPointsTotal: 0
      }
    ) {
      id
    }
  }
}
```

### Required input fields

| Field                         | Type              | Description                                           |
| ----------------------------- | ----------------- | ----------------------------------------------------- |
| `fipsCountyCode`              | `String!`         | Five-digit FIPS county code for the property location |
| `qualifyingFicoScore`         | `Int!`            | FICO score used for qualification                     |
| `qualifyingAssetsTotalAmount` | `NonNegativeInt!` | Borrower's total qualifying assets in dollars         |
| `qualifyingMonthlyIncome`     | `NonNegativeInt!` | Borrower's total qualifying monthly income            |
| `qualifyingMonthlyExpenses`   | `NonNegativeInt!` | Borrower's total qualifying monthly expenses          |

### Optional input fields

| Field                     | Type                      | Default             | Description                                          |
| ------------------------- | ------------------------- | ------------------- | ---------------------------------------------------- |
| `propertyUsageType`       | `PropertyUsageType`       | `PRIMARY_RESIDENCE` | `PRIMARY_RESIDENCE`, `SECOND_HOME`, or `INVESTMENT`  |
| `neighborhoodHousingType` | `NeighborhoodHousingType` | `SINGLE_FAMILY`     | Property type (e.g., `SINGLE_FAMILY`, `CONDOMINIUM`) |
| `hoaDues`                 | `NonNegativeInt`          | `0`                 | Monthly HOA dues in dollars                          |
| `maxDiscountPointsTotal`  | `Float`                   | `0`                 | Maximum total discount points                        |

### Response

The mutation returns a job ID:

```json theme={null}
{
  "data": {
    "preQualification": {
      "conventional": {
        "id": "prequal_abc123xyz"
      }
    }
  }
}
```

<Info>Save the `id` from the response. You'll use it to poll for results.</Info>

## Polling for results

After creating a prequalification, poll the query endpoint to check the job status and retrieve results:

```graphql theme={null}
query {
  preQualification {
    conventional(id: "prequal_abc123xyz") {
      id
      status
      result {
        product {
          id
          description
          type
        }
        loanAmount
        salesContractAmount
        ltv
        rate
        apr
        discountPointsTotal
        discountPointsTotalAmount
      }
      productBreakdown {
        product {
          id
          description
          type
        }
        result {
          ... on EligiblePreQualification {
            loanAmount
            salesContractAmount
            ltv
            rate
            apr
          }
          ... on IneligiblePreQualification {
            details {
              __typename
            }
          }
        }
      }
    }
  }
}
```

### Job status

The `status` field indicates the current state of the prequalification:

| Status       | Description                       | Action                  |
| ------------ | --------------------------------- | ----------------------- |
| `PROCESSING` | Job is queued and processing      | Continue polling        |
| `SUCCEEDED`  | Calculation finished successfully | Read `result` field     |
| `FAILED`     | Calculation encountered an error  | Surface failure to user |

### Polling strategy

Implement exponential backoff when polling to avoid excessive API calls:

```typescript theme={null}
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 === "SUCCEEDED") {
      return response.result;
    }

    if (response.status === "FAILED") {
      throw new Error("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");
}
```

<Warning>
  **Polling intervals**: Start with 2-5 second intervals and increase gradually.
  Most prequalifications complete within 10-30 seconds, but complex scenarios
  may take longer.
</Warning>

## Understanding results

When the prequalification completes, the `result` field contains maximum affordability information:

```json theme={null}
{
  "data": {
    "preQualification": {
      "conventional": {
        "id": "prequal_abc123xyz",
        "status": "SUCCEEDED",
        "result": {
          "product": {
            "id": "prod_conforming_30yr_fixed",
            "description": "Conforming 30-Year Fixed",
            "type": "Conforming"
          },
          "loanAmount": 450000,
          "salesContractAmount": 562500,
          "ltv": 0.8,
          "rate": 6.5,
          "apr": 6.713,
          "discountPointsTotal": 0,
          "discountPointsTotalAmount": 0
        },
        "productBreakdown": [
          {
            "product": {
              "id": "prod_conforming_30yr_fixed",
              "description": "Conforming 30-Year Fixed",
              "type": "Conforming"
            },
            "result": {
              "loanAmount": 450000,
              "salesContractAmount": 562500,
              "ltv": 0.8,
              "rate": 6.5,
              "apr": 6.713
            }
          }
        ]
      }
    }
  }
}
```

### Result fields

The `result` field is the best eligible result across all products:

| Field                       | Description                                               |
| --------------------------- | --------------------------------------------------------- |
| `product`                   | The loan product with the optimal prequalification result |
| `loanAmount`                | Maximum qualifying loan amount                            |
| `salesContractAmount`       | Maximum qualifying purchase price                         |
| `ltv`                       | Loan-to-value ratio for the result                        |
| `rate`                      | Interest rate for the result                              |
| `apr`                       | Annual percentage rate for the result                     |
| `discountPointsTotal`       | Total discount points                                     |
| `discountPointsTotalAmount` | Total discount points in dollars                          |

<Info>
  The top-level `result` represents the best eligible product. Use the
  `productBreakdown` array to show borrowers per-product results, including
  ineligibility details when a product does not qualify.
</Info>

## Complete example

Here's a complete TypeScript example that creates a prequalification and polls for results:

```typescript theme={null}
interface PrequalificationInput {
  fipsCountyCode: string;
  qualifyingFicoScore: number;
  qualifyingAssetsTotalAmount: number;
  qualifyingMonthlyIncome: number;
  qualifyingMonthlyExpenses: number;
  propertyUsageType?: "PRIMARY_RESIDENCE" | "SECOND_HOME" | "INVESTMENT";
  neighborhoodHousingType?: string;
  hoaDues?: number;
  maxDiscountPointsTotal?: number;
}

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 {
            product {
              id
              description
              type
            }
            loanAmount
            salesContractAmount
            ltv
            rate
            apr
            discountPointsTotal
            discountPointsTotalAmount
          }
          productBreakdown {
            product {
              id
              description
              type
            }
            result {
              ... on EligiblePreQualification {
                loanAmount
                salesContractAmount
                ltv
                rate
                apr
              }
              ... on IneligiblePreQualification {
                details {
                  __typename
                }
              }
            }
          }
        }
      }
    }
  `;

  // 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 === "SUCCEEDED") {
      return prequal.result;
    }

    if (prequal.status === "FAILED") {
      throw new Error("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**: When `status` is `FAILED`, surface a meaningful message to borrowers and offer to retry.

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 Code            | Description                                 | Resolution                                        |
| --------------------- | ------------------------------------------- | ------------------------------------------------- |
| `INVALID_INPUT`       | One or more input fields are invalid        | Validate inputs before submission                 |
| `GUIDELINE_VIOLATION` | Borrower doesn't meet minimum guidelines    | Explain qualification requirements to borrower    |
| `RATE_UNAVAILABLE`    | No rates available for the given parameters | Try adjusting inputs or explain market conditions |
| `TIMEOUT`             | Calculation took too long                   | Retry the prequalification request                |

## Related resources

* [Pricing scenarios](/recipes/scenarios) - Show detailed pricing options for specific loan amounts
* [Tracking loan updates](/recipes/loan-updates) - Learn how to poll for asynchronous job status
* [Marketing rates](/recipes/marketing-rates) - Display competitive rates on your website
