Skip to main content
Tasks are assignments that require action during the loan origination process. There are two types of tasks: borrower tasks (assigned to borrowers) and loan officer tasks (assigned to loan officers). Tasks guide users through exactly what’s needed for underwriting and loan processing.

Types of tasks

Borrower tasks

Borrower tasks are assignments that require borrowers to:
  • Upload documents (pay stubs, bank statements, tax returns, etc.)
  • Review and sign disclosures
  • Provide additional information or clarification

Loan officer tasks

Loan officer tasks are assignments that require loan officers to:
  • Upload documents on behalf of borrowers
  • Review and verify documents
  • Complete underwriting conditions
  • Handle disclosure-related tasks
Tasks are automatically created as the loan progresses through processing and underwriting. Each task includes:
  • Task type (document upload, disclosure review, etc.)
  • Status (not started, in progress, completed)
  • Description of what’s needed
  • Due date (if applicable)
  • Document links (for uploaded documents)

Retrieving tasks

Query both borrower tasks and loan officer tasks through the loan query:
query GetTasks($loanId: ID!) {
  loan(id: $loanId) {
    id
    borrowers(first: 10) {
      edges {
        node {
          id
          personalInformation {
            firstName
            lastName
          }
          borrowerTasks {
            id
            title
            status
            type
            priority
            dueDate
            completedAt
            createdOn
            borrowerFacingDescription
            documentLinks {
              id
              title
              url
            }
            details {
              borrowerId
              borrowerName
              entityKind
            }
          }
        }
      }
    }
    loanOfficerDocumentTasks {
      id
      title
      status
      type
      priority
      dueDate
      completedAt
      createdOn
      description
      documentLinks {
        id
        title
        url
      }
      details {
        borrowerId
        borrowerName
        entityKind
      }
    }
  }
}

Task types

Task TypeDescriptionAction Required
DocumentRequiredBorrower or loan officer must upload a documentUpload document via REST endpoint
DocumentProcessingA submitted document is being processedWait for processing to complete
InputRequiredAdditional input is needed from the borrowerProvide the requested input

How to use these document specifications

Each document page provides detailed specifications including required data fields, suggested questions, and validation rules. You have three options for handling these documents:
  1. Build your own forms - Use the required data fields and suggested questions from each document page to build custom forms that collect the necessary information
  2. Validate existing forms - Use the required data fields and validation rules to verify that your existing forms include all necessary fields and meet validation requirements
  3. Use pre-made templates - Use Pylon’s pre-made templates out of the box (templates will be provided separately)

Uploading documents for borrower tasks

Borrower task documents are uploaded via a REST endpoint (not GraphQL): Endpoint: POST /api/loan-applications/{loanId}/borrower-tasks/{taskId}/documents Headers:
Authorization: Bearer YOUR_ACCESS_TOKEN
Content-Type: multipart/form-data
Form field name: borrower-task-files Example:
async function uploadBorrowerTaskDocument(loanId, taskId, files) {
  const formData = new FormData();
  files.forEach((file) => {
    formData.append("borrower-task-files", file);
  });

  const response = await fetch(
    `https://pylon.mortgage/api/loan-applications/${loanId}/borrower-tasks/${taskId}/documents`,
    {
      method: "POST",
      headers: {
        Authorization: `Bearer ${accessToken}`,
      },
      body: formData,
    }
  );

  if (!response.ok) {
    throw new Error(`Upload failed: ${response.statusText}`);
  }

  return response.json();
}

Uploading documents for loan officer tasks

Loan officer tasks may also require document uploads. The upload process is similar, but the endpoint and form field name may differ. Check the task’s documentUploadPath field for the specific upload endpoint.

Reassigning all loan officer tasks to the borrower

When a loan officer wants to hand off every outstanding loan-officer task on a loan to the borrower — for example, once initial task generation completes and the LO wants the borrower to drive uploads directly — use the reassignLoanOfficerTasks mutation. It flips every task on the loan whose assignedTo is LOAN_OFFICER to BORROWER in one call, in a single transaction.
mutation ReassignLoanOfficerTasks($loanId: ID!) {
  loan {
    reassignLoanOfficerTasks(input: { loanId: $loanId }) {
      reassignedCount
    }
  }
}
loanId accepts either the loan’s ID or its friendly ID. The response’s reassignedCount is the number of tasks that were actually reassigned — 0 is a valid, non-error response when the loan has no outstanding loan-officer tasks.

What gets reassigned

Only tasks with assignedTo = LOAN_OFFICER are moved. Every other task on the loan is untouched by construction:
Task’s current assignedToResult
LOAN_OFFICERFlipped to BORROWER
BORROWERUnchanged
PROCESSOR (internal)Unchanged
The mutation only updates each task’s assignedTo; taskName, status, dueDate, uploaded documents, and every other field are preserved.
Why taskName is intentionally preserved: the inbound task sync only recomputes a task’s assignee when its taskName changes. Leaving the name alone is what keeps a later re-sync from silently reverting the reassignment.
This is a manual, per-loan action — call it explicitly when you want to hand tasks over. It is not automatic, and it can be called on a frozen loan.

Rejecting a submitted document

When a borrower uploads a document that doesn’t meet requirements — illegible, the wrong document, or incomplete — reject it with the rejectDocument mutation. Rejecting a document archives it and reopens the task it had satisfied, so the borrower is prompted to upload a replacement.
mutation RejectDocument($input: RejectDocumentInput!) {
  borrower {
    rejectDocument(input: $input) {
      taskId
    }
  }
}
Example variables:
{
  "input": {
    "loanId": "loan_...",
    "documentId": "docmeta_...",
    "archiveReasonNotes": "Pay stub is illegible — please re-upload a clearer scan."
  }
}

Input fields

FieldTypeRequiredDescription
loanIdIDYesThe loan’s ID or friendly ID.
documentIdIDYesThe ID of the document to reject. Obtain it from a task’s documentLinks { id }.
archiveReasonNotesStringYesFree-text note explaining why the document is being rejected.
borrowerIdIDNoOptional. Not required to reject a document.

Response

FieldTypeDescription
taskIdIDThe task associated with the rejected document — the task that is reopened for the borrower to act on.

Behavior

  • The rejected document is moved to an archived state and its associated task is reopened, so a fresh upload is required to satisfy it again.
  • archiveReasonNotes is recorded against the rejection and explains why the document was not accepted.
Unlike task-status polling, this mutation is not a silent no-op when nothing matches. If the documentId can’t be found on the loan, or the document has no task associated with it, the mutation returns an error rather than succeeding with an empty result.
rejectDocument requires the update:borrower scope, and can be called on a frozen loan. It is not idempotent — rejecting a document that is already archived will re-issue the rejection.

Task status

After uploading a document, the task status updates automatically. Poll the tasks query to verify the upload was successful:
  • NotStarted - Task has been created but no action taken
  • InProgress - Document upload in progress
  • Completed - Task is complete (document uploaded and verified)
  • AutomaticallyCancelled - Task was cancelled by the system
  • ManuallyCancelled - Task was cancelled manually

Polling for new tasks

Tasks are created automatically as the loan progresses. Poll tasks every 15-30 minutes to detect new tasks:
async function pollForNewTasks(loanId, previousTaskIds) {
  const query = `
    query GetTasks($loanId: ID!) {
      loan(id: $loanId) {
        borrowers(first: 10) {
          edges {
            node {
              borrowerTasks {
                id
                title
                status
                type
              }
            }
          }
        }
        loanOfficerDocumentTasks {
          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 borrowerTasks = data.loan.borrowers.edges.flatMap(
    (edge) => edge.node.borrowerTasks
  );
  const allTasks = [...borrowerTasks, ...data.loan.loanOfficerDocumentTasks];
  const currentTaskIds = new Set(allTasks.map((t) => t.id));
  const newTasks = allTasks.filter((task) => !previousTaskIds.has(task.id));

  return { newTasks, currentTaskIds };
}

Task details

Each task includes a details field that provides context about the entity the task is associated with:
  • borrowerId - The borrower this task is for (if applicable)
  • borrowerName - Display name of the borrower
  • entityKind - The type of entity (e.g., “BORROWER”, “LOAN”)
  • entityDisplayName - Human-readable name of the entity

Best practices

  1. Poll regularly - Check for new tasks every 15-30 minutes
  2. Notify users - Alert borrowers and loan officers when new tasks are assigned
  3. Track completion - Monitor task status to ensure all required documents are uploaded
  4. Handle errors - Implement retry logic for failed uploads
  5. Validate files - Check file types and sizes before uploading
  6. Monitor both types - Track both borrower tasks and loan officer tasks to ensure nothing is missed