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.
GraphQL is designed for structured data queries and mutations, but it doesn’t natively support file uploads or downloads. To handle documents (PDFs, images, and other files), Pylon provides REST endpoints that work alongside the GraphQL API.
Why REST for documents?
GraphQL operates with JSON payloads and doesn’t have built-in support for binary file data. For document operations, Pylon uses REST endpoints that:
- Support multipart/form-data for file uploads
- Provide signed URLs for secure document downloads
- Handle large file transfers efficiently
- Integrate seamlessly with GraphQL for metadata
Document upload workflow
The typical workflow for uploading documents involves both GraphQL and REST:
Request upload URL via GraphQL
Use the GraphQL API to request a document upload URL. This returns a REST endpoint URL where you’ll upload the file.
Upload file to REST endpoint
Use the returned URL to upload your file(s) using a multipart/form-data POST request.
Query document metadata
Once uploaded, query document metadata through GraphQL to verify the upload and get download URLs.
Requesting an upload URL
Use the requestDocUpload mutation to get an upload URL:
mutation RequestDocumentUpload($input: DocumentUploadInput!) {
requestDocUpload(input: $input) {
url
}
}
Variables:
{
"input": {
"entityType": "BORROWER",
"entityId": "borrower_123",
"documentTypeId": "doc_type_456",
"documentTypeName": "W-2"
}
}
Response:
{
"data": {
"requestDocUpload": {
"url": "/api/document-upload/upload_request_789"
}
}
}
Uploading documents
Once you have the upload URL from GraphQL, upload your file(s) to the REST endpoint:
async function uploadDocument(uploadUrl, file) {
const formData = new FormData();
formData.append("document-files", file);
const response = await fetch(`https://pylon.mortgage${uploadUrl}`, {
method: "POST",
headers: {
Authorization: `Bearer ${accessToken}`,
},
body: formData,
});
if (!response.ok) {
throw new Error(`Upload failed: ${response.statusText}`);
}
return response.json();
}
Uploading multiple files
You can upload multiple files in a single request:
async function uploadMultipleDocuments(uploadUrl, files) {
const formData = new FormData();
files.forEach((file) => {
formData.append("document-files", file);
});
const response = await fetch(`https://pylon.mortgage${uploadUrl}`, {
method: "POST",
headers: {
Authorization: `Bearer ${accessToken}`,
},
body: formData,
});
return response.json();
}
Complete example
Here’s a complete example that combines both steps:
async function uploadDocumentForBorrower(borrowerId, documentTypeName, file) {
// Step 1: Get upload URL from GraphQL
const mutation = `
mutation RequestDocumentUpload($input: DocumentUploadInput!) {
requestDocUpload(input: $input) {
url
}
}
`;
const variables = {
input: {
entityType: "BORROWER",
entityId: borrowerId,
documentTypeName: documentTypeName,
},
};
const gqlResponse = await fetch("https://pylon.mortgage/graphql", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${accessToken}`,
},
body: JSON.stringify({ query: mutation, variables }),
});
const { data } = await gqlResponse.json();
const uploadUrl = data.requestDocUpload.url;
// Step 2: Upload file to REST endpoint
const formData = new FormData();
formData.append("document-files", file);
const uploadResponse = await fetch(`https://pylon.mortgage${uploadUrl}`, {
method: "POST",
headers: {
Authorization: `Bearer ${accessToken}`,
},
body: formData,
});
if (!uploadResponse.ok) {
throw new Error(`Upload failed: ${uploadResponse.statusText}`);
}
return uploadResponse.json();
}
Downloading documents
The workflow for downloading documents involves querying GraphQL to get signed download URLs:
Query document metadata via GraphQL
Query the loan application or borrower to get document metadata, including signed download URLs.
Use the download URL
Use the signed URL from the documentLink.url field to download the document file.
Handle the file
Process the downloaded file (save, display, etc.) and handle URL expiration if needed.
Query documents through GraphQL to get download URLs:
query GetLoanDocuments($loanApplicationId: ID!) {
loanApplication(id: $loanApplicationId) {
id
documents {
id
name
category
uploadedAt
documentLink {
url
title
uploadStatus
}
}
}
}
The documentLink.url field contains a signed URL for downloading the document. The uploadStatus indicates whether the upload is COMPLETE, UPLOADING, or FAILED.
Using download URLs
Download URLs are signed URLs that expire after a set time (usually 1 hour). Use them directly:
async function downloadDocument(document) {
// Check if upload is complete
if (document.documentLink.uploadStatus !== "COMPLETE") {
throw new Error("Document upload is not complete yet");
}
const response = await fetch(document.documentLink.url, {
headers: {
Authorization: `Bearer ${accessToken}`,
},
});
if (!response.ok) {
throw new Error(`Download failed: ${response.statusText}`);
}
const blob = await response.blob();
// Create download link
const url = window.URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = document.name;
a.click();
window.URL.revokeObjectURL(url);
return blob;
}
Complete download example
Here’s a complete example that combines querying and downloading:
async function downloadLoanDocuments(loanApplicationId) {
// Step 1: Query documents via GraphQL
const query = `
query GetLoanDocuments($loanApplicationId: ID!) {
loanApplication(id: $loanApplicationId) {
id
documents {
id
name
category
uploadedAt
documentLink {
url
title
uploadStatus
}
}
}
}
`;
const gqlResponse = await fetch("https://pylon.mortgage/graphql", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${accessToken}`,
},
body: JSON.stringify({
query,
variables: { loanApplicationId },
}),
});
const { data } = await gqlResponse.json();
const documents = data.loanApplication.documents;
// Step 2: Download each document
for (const doc of documents) {
if (doc.documentLink.uploadStatus === "COMPLETE") {
const response = await fetch(doc.documentLink.url, {
headers: {
Authorization: `Bearer ${accessToken}`,
},
});
const blob = await response.blob();
const url = window.URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = doc.name;
a.click();
window.URL.revokeObjectURL(url);
}
}
return documents;
}
Authentication
All REST endpoints require the same OAuth Bearer token authentication as GraphQL requests:
Authorization: Bearer YOUR_ACCESS_TOKEN
See the GraphQL authentication guide for details on obtaining and managing access tokens.
File requirements
- Supported file types: PDF (
.pdf), images (.jpg, .jpeg, .png, .gif), Microsoft Office (.doc, .docx, .xls, .xlsx), text files (.txt)
- Maximum file size: 50 MB per file
- Maximum total upload size: 200 MB per request
Error handling
async function uploadDocumentWithErrorHandling(uploadUrl, file) {
try {
const formData = new FormData();
formData.append("document-files", file);
const response = await fetch(`https://pylon.mortgage${uploadUrl}`, {
method: "POST",
headers: {
Authorization: `Bearer ${accessToken}`,
},
body: formData,
});
if (!response.ok) {
if (response.status === 401) {
throw new Error("Authentication failed. Check your access token.");
} else if (response.status === 413) {
throw new Error("File too large. Maximum size is 50 MB.");
} else if (response.status === 400) {
const error = await response.json();
throw new Error(`Invalid request: ${error.message}`);
}
throw new Error(`Upload failed: ${response.statusText}`);
}
return await response.json();
} catch (error) {
console.error("Document upload error:", error);
throw error;
}
}
Best practices
-
Handle URL expiration - Download URLs expire after 1 hour. Re-query document metadata to get fresh URLs if needed.
-
Check upload status - Verify
uploadStatus is COMPLETE before attempting to download.
-
Validate file types and sizes - Check file types and sizes client-side before uploading to provide better user feedback.
-
Use progress tracking - For large files, implement upload progress tracking using XMLHttpRequest or libraries that support it.