Skip to main content
Documents belong to deals. The API never proxies file bytes: uploads and downloads go straight to storage through short-lived signed URLs. Reading needs documents:read; uploading and requesting need documents:write.

Upload a document

Three steps: register the document, PUT the bytes, finalise.
1

Register the document

Response 201
The document row exists now (upload_pending: true) but stays hidden from the app and from GET /v1/deals/{id}/documents until you finalise it. upload_url is valid for about two hours, until expires_at; if it lapses, register again.
2

PUT the file to upload_url

Send the bytes with exactly the headers from the response and nothing else. In particular, no Authorization header: the URL carries its own signature. Content-Type must match the mime_type you registered.
3

Finalise

Response 200
Finalising confirms the object is in storage, records the real size, makes the document visible, fires document.uploaded and, for bank statements, recomputes the deal’s statement coverage. If nothing was uploaded yet it answers 409 with code: "upload_not_found". Bank statements are not analysed automatically (analysis is billed per file): an underwriter starts it from the deal’s Underwrite tab in the app. When that runs, document.processed fires per file and statement.analyzed when the deal’s analysis is complete, and GET /v1/deals/{id}/statement-analysis fills in.
Finalising is what makes an upload count. If your process dies between the PUT and the finalise call, the file is in storage but invisible; call finalise again, it is safe to repeat.

Document types

type is one of:

List and download

GET /v1/deals/{id}/documents lists finalised documents with their type and processing state (ocr_status), optionally filtered by ?type=. GET /v1/documents/{id} returns the metadata plus download_url, a signed link that works for 15 minutes (download_url_expires_at says exactly when). Fetch a fresh one each time you need the file rather than storing the link.

Request documents from the merchant

POST /v1/deals/{id}/document-requests creates a request the merchant fulfils through a unique upload link, the same flow the app’s “Request documents” button uses. It sends messages, so use an Idempotency-Key.
Response 201
items[].type is one of bank_statement, voided_check, drivers_license, business_license, tax_returns, proof_of_ownership or utility_bill (underscores, unlike the hyphenated document type); label overrides the text the merchant sees. person_id defaults to the deal’s primary owner, then the business’s primary contact. expires_in_days (1 to 90, default 30) sets expires_at. delivery reports what actually happened; channel echoes what you asked for. Files the merchant uploads through the link become documents on the deal, fire document.uploaded and are matched to the requested items automatically: items[].status becomes received with received_document_id, and the request’s own status moves from pending through partial to complete.

Statement analysis

GET /v1/deals/{id}/statement-analysis returns what the underwriter sees after bank statements are parsed: per-month figures, a summary with the revenue trend, the deal’s MCA positions, and coverage per account. It answers status: "not_analyzed" with empty figures when no statement has been analysed yet (not an error), and pending or processing while analysis is still running.
Response 200
What the numbers mean:
  • Deposits (months[].deposits) are true deposits: transfers and MCA credits are excluded when the parser identified them. summary.avg_monthly_deposits is the bank-verified revenue that lender matching uses instead of the stated annual_revenue whenever statements are analysed, which is why uploading statements before matching changes the results.
  • Average daily balance and ending balance are summed across accounts for the month; summary.avg_daily_balance is the mean of the monthly figures.
  • NSF count is the number of returned or non-sufficient-funds items. Negative days is the number of days the account closed below zero (the maximum across accounts for the month). Both feed the computed paper grade and many lender gates.
  • Positions are existing advances on the deal: detected: true for those found from recurring debits to known funders in the statements, false for positions entered on the deal. Lenders use the count and the total payment when deciding on a new position.
  • Coverage says whether the months the location requires are on file for every account: the last required_months full calendar months (3 by default, a location setting) plus a month-to-date statement when mtd_required is true. status is complete, missing (see missing[] and accounts[].months_missing), needs_analysis (statements uploaded but not yet parsed; unanalyzed_documents counts them) or no_statements.
  • Trend (summary.revenue_trend) compares deposits in the most recent half of the analysed period with the earlier half: up at +10% or more, down at −10% or less, otherwise flat; null with fewer than two months.
For the exact field names and types, open the endpoint in the API reference tab.