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

# Polling for updates

> Monitor loan application status changes by polling the GraphQL API

While Elements handle the UI and user interactions, you'll need to monitor loan status changes by polling the GraphQL API. Polling is the recommended approach to track loan progress, stage changes, and completion status.

## Why polling?

When users interact with Elements, the loan progresses through various stages. To keep your application in sync with these changes, you need to poll the GraphQL API to detect:

* Loan application completion
* Stage transitions (e.g., from "Processing" to "Underwriting")
* Task assignments and completions
* Document uploads
* Pre-approval status

## Recommended polling intervals

| What to monitor        | Recommended interval | Detection method            |
| ---------------------- | -------------------- | --------------------------- |
| Loan stage             | 15-30 minutes        | Compare `currentStage`      |
| Tasks                  | 15-30 minutes        | Track task IDs and `status` |
| Application completion | 15-30 minutes        | Check `submitted` field     |
| Pre-approval status    | 15-30 minutes        | Check `preApprovalStatus`   |

## Basic polling implementation

Here's a simple example that polls for loan application completion:

```typescript theme={null}
async function pollLoanStatus(loanId: string) {
  const query = `
    query GetLoanStatus($id: ID!) {
      loan(id: $id) {
        id
        submitted
        currentStage
        preApprovalStatus
      }
    }
  `;

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

  const { data } = await response.json();
  return data.loan;
}

// Poll every 30 minutes
const intervalId = setInterval(async () => {
  const loan = await pollLoanStatus(loanId);
  
  if (loan.submitted) {
    // Application is complete - transition to Borrower Dashboard
    clearInterval(intervalId);
    // Show Borrower Dashboard Element
  }
}, 30 * 60 * 1000);
```

## Detecting stage changes

To detect when a loan moves to a new stage:

```typescript theme={null}
let previousStage: string | null = null;

async function pollLoanStage(loanId: string) {
  const query = `
    query GetLoanStage($id: ID!) {
      loan(id: $id) {
        id
        currentStage
        stages {
          name
          timestamp
        }
      }
    }
  `;

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

  const { data } = await response.json();
  const currentStage = data.loan.currentStage;

  if (previousStage !== null && previousStage !== currentStage) {
    console.log(`Stage changed: ${previousStage} → ${currentStage}`);
    // Handle stage change (notify user, update UI, etc.)
  }

  previousStage = currentStage;
}
```

## Monitoring tasks

Poll for new tasks that borrowers need to complete:

```typescript theme={null}
const previousTaskIds = new Set<string>();

async function pollTasks(loanId: string) {
  const query = `
    query GetTasks($loanId: ID!) {
      loan(id: $loanId) {
        borrowers(first: 10) {
          edges {
            node {
              borrowerTasks {
                id
                title
                status
                type
              }
            }
          }
        }
      }
    }
  `;

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

  const { data } = await response.json();
  const allTasks = data.loan.borrowers.edges.flatMap(
    (edge: any) => edge.node.borrowerTasks
  );
  
  const currentTaskIds = new Set(allTasks.map((t: any) => t.id));
  const newTasks = allTasks.filter(
    (task: any) => !previousTaskIds.has(task.id)
  );

  if (newTasks.length > 0) {
    console.log(`New tasks: ${newTasks.length}`);
    // Notify borrower, update UI, etc.
  }

  previousTaskIds.clear();
  currentTaskIds.forEach(id => previousTaskIds.add(id));
}
```

## Complete polling guide

For comprehensive polling patterns, implementation examples, and best practices, see the [Tracking loan updates](/recipes/loan-updates) guide. That guide covers:

* Focused queries for efficient polling
* State management and change detection
* Error handling and retry logic
* Polling multiple loans
* Monitoring different entity types (stages, tasks, documents, etc.)

## Best practices

1. **Use focused queries** - Only request the fields you need to detect changes
2. **Store previous state** - Cache values you're monitoring to detect changes
3. **Handle errors gracefully** - Implement retry logic for transient failures
4. **Adjust intervals** - Poll more frequently for active loans, less for inactive ones
5. **Stop polling when done** - Clear intervals when loans are complete or no longer need monitoring

## Next steps

* [Tracking loan updates](/recipes/loan-updates) - Complete polling guide with examples
* [Customize the theme](/elements/loan-application/theming) - Style the Element
* [Borrower Dashboard Element](/elements/borrower-dashboard/introduction) - For managing completed applications
