Document Upload
Upload financial documents attached to a partner application. PGI's extraction pipeline runs automatically after upload and fires a document.extracted webhook when complete.
Endpoint
/api/v2/applications/{application_id}/documents/
Uploads a file and links it to the specified application. The upload is tenancy-gated: your API key must have an active PartnerApplication link to the target application. Attempting to upload to an application that belongs to another partner returns 403.
Authentication
Bearer token only. Use your pk_live_* or pk_test_* key. Session auth and JWT are not accepted on this endpoint.
Authorization: Bearer pk_live_abc123def456ghi789jkl012mno345
Request fields
Send the request as multipart/form-data.
| Field | Type | Description |
|---|---|---|
| filerequired | file | The document to upload. Maximum 5 MB. Allowed types listed below. |
| documentTyperequired | string | One of the canonical document type values listed below. Defaults to other if omitted or unrecognized. |
File constraints
Maximum size
5 MB per file. Requests with a larger file body return 400 before the file is processed.
Allowed MIME types
The partner upload endpoint does not accept images or binary formats. Only structured document types are permitted:
| MIME type | Extension |
|---|---|
| application/pdf | |
| application/vnd.openxmlformats-officedocument.wordprocessingml.document | .docx |
| application/vnd.ms-excel | .xls |
| application/vnd.openxmlformats-officedocument.spreadsheetml.sheet | .xlsx |
| text/csv or application/csv | .csv |
| text/markdown or text/x-markdown | .md |
application/octet-stream as the content type regardless of the actual file format. PGI falls back to file extension detection in this case, so a file named financials.xlsx will be accepted even if the client sends an octet-stream header.
Document types
Use the documentType field to tell PGI's extraction pipeline what kind of document you're uploading. Correct typing improves extraction accuracy significantly.
| Value | Full label | Use case | Required for CA |
|---|---|---|---|
loan_agreement |
Lender Agreement / Term Sheet | Loan agreement or term sheet from the lender. Required for all Canadian Purbeck submissions to validate loan terms and structure. | Yes |
profit_loss |
Profit & Loss Statement (12 months, monthly) | Monthly P&L showing revenue, COGS, EBITDA, and net income. Primary document for income verification. | Yes |
balance_sheet |
Balance Sheet (most recent month-end) | Assets, liabilities, and equity at the most recent month-end. Required for DSCR and leverage analysis. | Yes |
ar_aging |
Accounts Receivable Aging Summary | Outstanding receivables by aging bucket. Used to assess cash conversion and concentration risk. | Yes |
ap_aging |
Accounts Payable Aging Summary | Outstanding payables by aging bucket. Used to assess working capital and payment history. | Yes |
founder_cv |
Founder CV (startup, under 3 years) | Resume or bio for the principal guarantor. Required only for startup underwriting when the business is under 3 years old. | If startup |
financial_forecast |
Financial Forecast (startup, under 3 years) | 12-24 month revenue and expense projections. Required only for startup underwriting when the business is under 3 years old. | If startup |
Response (201 Created)
{
"documentId": "48CE8D0CCC804D72A269C452A5",
"extractionJobId": "48CE8D0CCC804D72A269C452A5",
"applicationId": "F790E7CE7ADB46BBBD56A7CDAA"
}
| Field | Type | Description |
|---|---|---|
| documentId | string | Unique identifier for the uploaded document. |
| extractionJobId | string | Job identifier for the async extraction pipeline. Currently equals documentId. Use this to correlate with the document.extracted webhook. |
| applicationId | string | The application the document was linked to. |
Error responses
| Status | When | Body |
|---|---|---|
| 400 | File is missing from the request | {"error": "No file provided. Include a 'file' field in the multipart request."} |
| 400 | File exceeds 5 MB | {"error": "File size exceeds the 5 MB limit for partner document uploads."} |
| 400 | Unsupported MIME type (e.g. image/jpeg) | {"error": "File type 'image/jpeg' is not allowed. Accepted types: PDF, DOCX, XLS, XLSX, CSV, Markdown."} |
| 401 | Missing or invalid API key | {"detail": "Invalid API key."} |
| 403 | Application does not belong to your partner account | {"error": "Application not found or not accessible with this API key."} |
| 429 | Rate limit exceeded | {"detail": "Request was throttled."} |
Sandbox isolation
When you upload with a pk_test_* key, the resulting Document record is flagged is_sandbox=True. Sandbox documents are hidden from PGI's production admin views and do not trigger real underwriter workflows. The extraction pipeline runs in full, giving you realistic extracted data to test against.
document.extracted webhook
After the extraction pipeline completes, PGI fires a document.extracted webhook to your registered webhook_url. This happens asynchronously, typically within 30 seconds of upload.
{
"event": "document.extracted",
"document_id": "48CE8D0CCC804D72A269C452A5",
"application_id": "F790E7CE7ADB46BBBD56A7CDAA",
"document_type": "profit_loss",
"extraction_status": "completed",
"extracted_data": {
"q89_last_year_ebitda": "420000",
"q88_last_year_revenue": "2100000"
},
"extracted_summary": {
"revenue_trend": "Growing 12% YoY",
"key_risks": "Concentration: top customer is 40% of revenue"
},
"timestamp": "2026-05-02T15:32:44.123456+00:00"
}
See Webhooks for the full event catalog and HMAC verification instructions.
Example request
curl -X POST https://api.pgicover.com/api/v2/applications/F790E7CE7ADB46BBBD56A7CDAA/documents/ \
-H "Authorization: Bearer pk_live_abc123def456ghi789jkl012mno345" \
-F "file=@/path/to/profit_loss_q1_2026.pdf" \
-F "documentType=profit_loss"
import requests
with open("/path/to/profit_loss_q1_2026.pdf", "rb") as f:
resp = requests.post(
"https://api.pgicover.com/api/v2/applications/F790E7CE7ADB46BBBD56A7CDAA/documents/",
headers={"Authorization": "Bearer pk_live_abc123def456ghi789jkl012mno345"},
files={"file": ("profit_loss_q1_2026.pdf", f, "application/pdf")},
data={"documentType": "profit_loss"},
)
resp.raise_for_status()
print(resp.json())