Skip to content

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.

POST /deals/{id}/documents takes multipart/form-data with two fields: the file in file, and its category in category.

Terminal window
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" \
-F "[email protected];type=application/pdf"
201 Created
{
"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.

  • 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 contentType is the real type and its fileName carries that type’s extension (list.xlsx holding a Word document is kept as list.docx). A file that is not what it says it is answers 415 (Document.UnsupportedMediaType) with a detail that 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).

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.

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.

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.

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.

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.