Uploading
One flow, three calls, at every file size up to 2 GB. Your bytes go straight to storage and never pass through the API.1
Initiate
matterId, filename, contentType and sizeBytes are all required. folderId is optional, and handwritten: true routes the file through OCR tuned for handwriting.Answers 201 with {uploadId, partSize, totalParts, parts, expiresAt}. Each entry in parts carries a partNumber and a presigned url.All three upload calls need documents:write.2
PUT the bytes
Split the file on exactly the
partSize boundary the initiate response gave you, and PUT each piece to the matching partNumber. partSize is not advisory: parts are reassembled in order and every part except the last must be that size, so a different split produces a corrupt object that nothing rejects until complete.Keep the ETag each PUT returns. That ETag is the only proof the bytes landed.A small file comes back as a single part, so there is no separate small-file path to implement. That is the point.3
Complete
202 with an operation. When it reaches succeeded, resource.id is your documentId.Presigned URLs expire, and
expiresAt tells you when. If one lapses part way through a large upload, re-sign that single part with GET /v1/uploads/{uploadId}/parts/{partNumber} rather than starting the whole upload again.Supported file types
The Workspace API accepts every format Vaquill supports: 37 extensions and 42 media types, spanning PDF and Word, plain text and markup, spreadsheets, presentations, images, audio, email and archives.contentType are checked at initiate, against their own allowlists.
Reading a document
The list takes one filter,
folderId, and it is the only content filter anywhere on this API:
status uses the same five values as an operation: queued, running, succeeded, failed, cancelled. It is never the internal ingestion vocabulary, so you can branch on it the same way everywhere.
GET .../text answers {documentId, text, chunkCount, truncated}. Check truncated: on a very large document the text is capped, and a caller that ignores the flag will silently analyze part of a contract.
A 409 document-not-readable on /text means ingestion has not finished or did not produce text. Poll the ingestion operation rather than retrying blindly.
Downloading originals
/download streams the file with its real content type and filename:
documents:download, a scope separate from documents:read. Listing metadata to check ingestion status and pulling an original out of a client file are different risks, so an integration that only needs the first should not be able to do the second.
Deleting
204. This is a hard delete with no undo, and it is the same delete the web app’s button performs.

