Uploading documents
Your system can upload a document into the vault of a deal your company is a party to. The upload goes through the app’s own upload: the same size limit, the same file checks, the same malware scan before anything is stored, the same rules for each side, and the other party is told the same way.
It needs the documents:write scope (reading the vault is documents:read, a separate grant) and an X-Idempotency-Key.
The call
Section titled “The call”POST /deals/{id}/documents takes multipart/form-data with two fields: the file in file, and its category in category.
curl "https://app.quatlasglobal.com/api/integrations/v1/deals/<deal-id>/documents" \ -H "Authorization: Bearer $QUATLAS_KEY" \ -H "X-Idempotency-Key: EBA-2026-000123-invoice" \ -F "category=CommercialInvoice" \{ "id": "<document-id>", "dealId": "<deal-id>", "category": "CommercialInvoice", "fileName": "invoice.pdf", "contentType": "application/pdf", "sizeBytes": 184213, "uploadedAt": "2026-10-15T13:20:05Z", "uploadedBySide": "Shipper", "scanStatus": "Clean", "livemode": true}The new document appears in GET /deals/{id}/documents and can be downloaded through GET /documents/{id}/download.
The file
Section titled “The file”- At most 25 MB. A larger file answers
413(Document.PayloadTooLarge). - PDF, JPEG, PNG, DOCX or XLSX, declared in the part’s content type. The file’s contents must match it: its first bytes, and for a DOCX or XLSX its package, read as Office reads it (the main part its root relationships name, so Word, Excel, LibreOffice and Google exports all pass; a ZIP without root relationships, or with more than 5,000 entries, does not, whatever its folders are called). A Word document sent as XLSX, or a workbook sent as DOCX, is kept as what it is: the answer’s
contentTypeis the real type and itsfileNamecarries that type’s extension (list.xlsxholding a Word document is kept aslist.docx). A file that is not what it says it is answers415(Document.UnsupportedMediaType) with adetailthat says what the file is. Any other type, archives (ZIP, RAR, …) included, is refused the same way. - Not empty. No file, or an empty one, answers
400(Document.FileRequired).
The malware scan
Section titled “The malware scan”Every file is scanned before it is stored, as in the app. A file that fails the scan is refused with 422 (Document.Infected) and is never stored; when the scanner cannot answer, the upload is refused with 503 (Document.ScanUnavailable) rather than stored unscanned. So an accepted document is always Clean: there is no pending state to wait for, and the 201 is the scan’s verdict.
Categories, by side and stage
Section titled “Categories, by side and stage”category names one of the app’s document categories, in any case. Which ones your side may upload depends on the deal’s stage, as the app’s screens offer them:
| Stage | Shipper | Carrier |
|---|---|---|
| Closing room (before the deal goes live) | PackingList, CommercialInvoice, DangerousGoodsCertificate (hazardous cargo only), LoadingInstruction, Other |
The transport document of the deal’s mode (RoadConsignmentNote, BillOfLading for sea, AirWaybill, RailConsignmentNote; any of the four when no mode is set), WeighbridgeTicket, Other |
| Live (the operation page) | PackingList, CommercialInvoice, DangerousGoodsCertificate (hazardous cargo only), LoadingInstruction, BillOfLading, AirWaybill, RoadConsignmentNote, RailConsignmentNote, WeighbridgeTicket, Other |
The same, without DangerousGoodsCertificate and LoadingInstruction |
| Withdrawn | Nothing: 409 (Document.DealNotOpen) |
Nothing |
A category outside your side’s list answers 400 (Document.CategoryNotOffered), with the list in args.offered. An unknown category answers 400 (Document.InvalidCategory).
CustomsDeclaration, CertificateOfOrigin and InsuranceCertificate are retired in the app (the parties handle them off the platform), and AdditionalCostReceipt belongs to a cost line on the operation’s costs tab. The API takes none of them, at any stage; a deal’s document list can still show one a person uploaded.
In the closing room, the carrier’s transport document also waits until the carrier has approved the shipper’s documents (409, Document.BillOfLadingTooEarly), as in the app.
A deal your company is not a party to answers 404 (Deal.NotFound), like one that does not exist.
What happens next
Section titled “What happens next”The document is stored where the app stores it and belongs to the deal’s vault. The other party is told as when the document is uploaded in the app, and document.uploaded goes to both parties’ webhooks (with the document’s id and category only). See Events.
The daily allowance
Section titled “The daily allowance”On top of the rate limit, each company has a daily allowance of uploads through the API: by default 50 files and 250 MB per UTC day on Exchange, 200 files and 1 GiB on Enterprise, live and sandbox counted apart. Past it, the answer is 429 (Integration.DailyUploadLimit) with a Retry-After header that runs to midnight UTC.
An upload refused for a rule (a category not offered, a type, a side rule, an unknown deal) is not counted. A file refused for malware is: it took a scan.
Errors
Section titled “Errors”| Status | Code | When |
|---|---|---|
400 |
Document.FileRequired |
No file, or an empty one. |
400 |
Document.InvalidCategory |
No category, or one the app does not know. |
400 |
Document.CategoryNotOffered |
A category the app does not offer your side at the deal’s stage; args.offered lists those it does. |
400 |
Integration.IdempotencyKeyRequired |
No X-Idempotency-Key. |
404 |
Deal.NotFound |
No such deal, or your company is not a party to it. |
409 |
Document.DealNotOpen |
The deal was withdrawn: it takes no documents. |
409 |
Document.BillOfLadingTooEarly |
The carrier’s transport document before the shipper’s papers were approved. |
413 |
Document.PayloadTooLarge |
Over 25 MB. |
415 |
Document.UnsupportedMediaType |
A type that is not allowed, or contents that do not match the declared type (an archive declared as a DOCX or XLSX included). |
422 |
Document.Infected |
The file failed the malware scan. Nothing was stored. |
422 |
Integration.IdempotencyKeyReused |
The X-Idempotency-Key was used for another file, category, file name or type on this deal. A retry of the same upload (same file and fields, any multipart boundary) replays the first answer. |
429 |
Integration.DailyUploadLimit |
The company’s daily allowance is used up. |
502 |
Document.StorageFailed |
The file could not be stored. Nothing was kept; retry. |
503 |
Document.ScanUnavailable |
The scanner could not answer. Retry shortly. |
