Uploading documents
Documents are attached to a client, then picked up when you create a submission. There are two ways to upload — pick whichever fits your stack.
Accepted formats & size limits
Both upload paths accept the same file types:
| Kind | Extensions |
|---|---|
.pdf |
|
| Images | .jpg .jpeg .png .bmp .heic .heif .gif .webp .tif .tiff |
| Office / spreadsheets | .xls .xlsx .csv .doc .docx .odt .txt .rtf .ppt .pptx |
.eml .msg |
|
| Archive | .zip (we expand it) |
Size limits: presigned upload (Option A) — 1 GB per file; direct multipart upload (Option B) — 10 MB per request. Anything over a path's limit is rejected (a too-large multipart request returns 413 pointing you to the presigned flow). There's no fixed cap on the number of documents per client or per prepare call.
Option A — Presigned exchange (recommended)
You upload the bytes directly to storage; they never pass through the API, so large or many files don't tie up your request or ours. Three steps:
Prepare —
POST /v1/clients/{id}/documents/prepare{ "documents": [ { "filename": "W2.pdf", "content_type": "application/pdf" }, { "filename": "1099.pdf", "content_type": "application/pdf" } ] }Returns an
upload_targets[]array, each with adocument_id, anupload_url, and a set offields.Optional integrity check (
content_md5). Add a base64-encoded 128-bit MD5 of a file's bytes to its prepare entry and we pin it into the upload target, so storage rejects the upload if the bytes don't match — catching truncated or corrupted transfers. The returnedfieldsthen include aContent-MD5you must submit with the upload.{ "documents": [ { "filename": "W2.pdf", "content_type": "application/pdf", "content_md5": "XUFAKrxLKna5cZ2REBfFkg==" } ] }It's optional and per-document — omit it to skip the check. A malformed digest is rejected with
400 invalid_content_md5.Upload each file to its
upload_urlasmultipart/form-data— include every key/value infields, then thefilepart last. A success is HTTP204.Upload URL lifetime & CORS. Each
upload_urlis valid for up to 7 days; after that, request a freshprepare. The presigned POST is designed for server-side upload — uploading directly from a browser needs cross-origin (CORS) permission on the storage bucket, which isn't enabled by default. Contact us if you need browser-direct upload.Finalize —
POST /v1/clients/{id}/documents/finalizewith thedocument_ids you uploaded:{ "document_ids": [456, 457] }Finalize verifies the objects landed and marks them ready. Anything that didn't upload comes back in
failed_document_ids; just retry those. (The olderfailed_document_indicesis still returned but is deprecated.)
A document isn't attached to a submission until it's finalized. If you try to create a submission while a prepared document is still unfinalized, the request is rejected with
409 documents_still_uploading— finalize (or retry) all prepared documents first. An abandoned upload (apreparethat never completed) won't block the client forever: once it's about 1 hour old, the next time you create a submission it's automatically swept aside. You don't have to wait, though — list the client's documents, find the one still in stateuploading, andDELETEit to clear the block immediately. (Finalizing a never-uploaded document does not clear it — delete it.)
Option B — Direct multipart upload
For thin clients that can't do the presigned dance, send the files straight to us in one request. The bytes stream through our API, so this path is capped at 10 MB per request — meant for a handful of small documents. For larger or bulk uploads, use the presigned exchange (Option A); an over-limit request is rejected with 413 and a pointer to /prepare.
curl -s -H "Authorization: Bearer $MAGNETIC_API_KEY" \
-F "[email protected]" -F "[email protected]" \
$BASE/v1/clients/123/documents
Returns the created documents (each with its id) and — like prepare/finalize — a failed_document_ids and a deprecated failed_document_indices. No finalize step is needed. On this path a failed file never got created, so it has no id: failed_document_ids is always empty here and the failed entries appear in failed_document_indices (or as fewer documents than files sent). See Deprecations.
Which should I use?
| Presigned (A) | Multipart (B) | |
|---|---|---|
| Bytes flow | Browser/server → storage directly | Through our API |
| Best for | Large/many files, server-to-server | Simple clients, small files (≤ 10 MB/request) |
| Round trips | prepare → upload → finalize | one |
After uploading
Uploaded documents sit on the client until you create a submission, which picks up every finalized document not already attached to one (see which documents get attached) and starts processing. Upload everything first, then submit.