Use these comprehensive queries as starting points. Trim them down to only the fields you need for your specific polling use case.
Full loan
Full loan data including stages, pricing, rate lock, and associated entities.
Copy
query GetLoan($id: ID!) { loan(id: $id) { id friendlyId loanNumber loanPurpose loanTermYears noteRatePercent maxLoanAmount maxPurchasePrice purchasePrice ltv outOfPocketMax totalAssetsAvailable earnestMoneyDeposit sellerCredit refinanceCashOutProceeds closingDate latestPreApprovalRunTime currentStage dealId isClosed isFrozen isFirstTimeHomebuyer pylonApproved useOwnTitleCompany allowedStates assignedLoanOfficer { id email firstName lastName } borrowerPreferences { closingCosts downPaymentAmount monthlyPayment rate } borrowers(first: 5) { edges { node { id dependentAges emailVerified externalUserId isFirstTimeHomeBuyer } } pageInfo { hasNextPage endCursor } } changeRequests { id status } concessions { id amount approvedAt reason } contacts { id email companyName firstName } documents { id name category uploadedAt } fees { id feeActualTotalAmount feeDescription } preapprovalProduct { id type description } productPricingRate { id apr closingCosts } productStructure { id apr closingCosts } rateLock { status expirationTime lockedTime period } stages { name timestamp } subjectProperty { id avmEstimatedValue hoaDues homeInsuranceMonthlyAmount subjectPropertyIntent { id neighborhoodHousingType } } }}
Borrower personal information
Identity and contact details.
Copy
query GetBorrowerPersonalInfo($id: ID!) { borrower(id: $id) { id dependentAges personalInformation { firstName middleName lastName email dateOfBirth citizenshipResidencyType taxIdentifierNumber taxIdentifierNumberType phoneNumbers { id number type } currentAddress { id address { line line2 city state zipCode } } } mailingAddress { line line2 city state zipCode } }}
Borrower financials (assets, income, properties)
Complete financial profile for underwriting.
Copy
query GetBorrowerFinancials($id: ID!) { borrower(id: $id) { assets { id amount borrowerIds ... on CheckingAccountAsset { accountIdentifier institutionName } ... on SavingsAccountAsset { accountIdentifier institutionName } ... on RetirementFundAsset { accountIdentifier institutionName } ... on GiftAsset { source donorName dateOfTransfer } } incomes(first: 25) { edges { node { id statedMonthlyAmount ... on StandardEmploymentIncome { employment { position startDate isCurrentEmployment ... on StandardEmployment { employer { name } } } } ... on SelfEmploymentIncome { employment { position ... on SelfEmployment { business { name } } } } } } pageInfo { hasNextPage } } ownedProperties(first: 25) { edges { node { id address { line city state zipCode } intendedDisposition currentUsageType rentalIncome { statedMonthlyAmount } liabilities { id balance } } } pageInfo { hasNextPage } } }}
If requests fail, wait progressively longer before retrying. This prevents overwhelming the API during outages and gives transient issues time to resolve.
Only cache what you need for comparison. For stage monitoring, store a Map<loanId, stage> rather than full loan objects. This keeps memory usage low and simplifies the comparison logic.
If monitoring hundreds of loans, don’t fire all requests simultaneously. Spread them over the polling interval to avoid rate limits and reduce peak load.
For lists like tasks or documents, track cursors to efficiently detect new items rather than comparing entire arrays. Store the last cursor and fetch only items after it.
Copy
// Store the cursor from your last successful polllet lastCursor: string | null = null;// On next poll, fetch only new itemsconst query = ` query GetTasks($loanId: ID!, $after: String) { loan(id: $loanId) { borrowers(first: 10) { edges { node { tasks: borrowerTasks(after: $after) { id status } } } pageInfo { endCursor } } } }`;
Keep logs of your polling activity for debugging. Include timestamps, loan IDs, and any detected changes. This helps diagnose issues when changes aren’t being detected as expected.