Automate document ingestion
Your documents don't live in Docana. They arrive from somewhere: a CRM export, a nightly report, an upload form in your product. This guide covers the loop that keeps Docana current: upload a file, wait for processing, and check what came out.
For an employee, these uploads supply lasting knowledge. A file or voice brief attached during creation stays with the preparation conversation. Use the employee's knowledge area, a connected collection, or the upload steps below for sources it should keep searching. See employee knowledge.
The steps below run in order. The CLI authenticates through docana login. Only the curl variants need the key:
export DOCANA_API_KEY=your_api_key_here # see Generating API Keys
1. Create a place for files
Documents live in collections, and collections live in libraries. Create the structure once, in the UI or from the terminal, and keep the collection id it prints:
docana libraries create -n "Operations"
docana collections create -n "Contracts" -l <library-id>
export COLLECTION_ID=<the id from the output>
If the destination is an employee, use a collection connected to its application. The employee knowledge endpoint resolves its upload collection, and the employee API guide shows the CLI equivalent. Uploading to an unrelated collection does not make the documents available to that employee.
2. Check the budget
Ingestion is metered per processed page and per media minute, not per file, and it stops with 402 once the month's budget is spent. Before a big job, read where you stand:
docana usage
Period: 2026-09-01 to 2026-09-30
Budget: $500.00
Used: $120.50
Remaining: $379.50
The same numbers come back as JSON from GET /api/v1/usage/ (or docana usage --json), so a script can size the job against budget.remaining and stop cleanly instead of discovering the limit mid-run. If you do hit it, the response is 402 with code: "budget-exceeded". That is not a 429, and retrying never succeeds. See Plan limits.
3. Upload
From a script or your shell, the CLI is the shortest path:
docana documents push "$COLLECTION_ID" contracts/*.pdf
To mirror a directory tree, pass the directory with --recursive. Each subdirectory becomes a folder in the collection (existing folders are reused by name), small files travel 20 to a request, and anything over 5 MB goes through the chunked protocol on its own:
docana documents push "$COLLECTION_ID" ./case-files --recursive
From your backend, it's one multipart request:
curl -X POST "https://platform.docana.com/api/v1/collections/${COLLECTION_ID}/documents/" \
-H "Authorization: ApiKey ${DOCANA_API_KEY}" \
The response returns one result per file, each carrying the new document:
{
"success": true,
"results": [
{ "document": { "id": 12345, "name": "contract-2026-08.pdf", … } }
]
}
Grab each document.id and keep it for the next step (export DOCUMENT_ID=12345), or look it up later with docana documents list -c "$COLLECTION_ID". Any file type Docana understands works: PDFs, Office documents, images, audio, video.
Folders
In the browser, drop files or folders into an employee’s knowledge panel or a Knowledge collection. Choose folder is also available in the employee panel and the collection’s Add Documents dialog. The selected folder and its subfolders are recreated beneath the current destination, and matching manual folders are reused. File names can repeat in different folders. Empty directories are omitted because the browser picker returns files.
For an integration, use listCollectionFolders and createCollectionFolder to resolve the destination folder, then upload with its folderId. These operations are available through REST, MCP, and the CLI.
folderId files every file of the request into one manual folder. To send files bound for different folders in one request, add folderIds: one entry per file in files order, comma-separated, empty for the collection root. Folder ids come from GET /api/v1/collections/{id}/folders/, and POST /api/v1/collections/{id}/folders/ creates one and returns it under folder in the same shape:
curl -X POST "https://platform.docana.com/api/v1/collections/${COLLECTION_ID}/documents/" \
-H "Authorization: ApiKey ${DOCANA_API_KEY}" \
-F "folderIds=12,,15"
That request files the contract into folder 12, the photo at the root, and the notes into folder 15. A structure-preserving upload of thousands of files takes one request per 20 files instead of one per file.
Large files
One request carries at most 100 MB through the hosted platform's edge, whatever the plan's document size limit says. A single request above that comes back as 413, sometimes as an HTML page from Cloudflare rather than JSON. Files between 100 MB and the plan limit go through the resumable chunked protocol: split the file into 5 MB chunks and POST each one as files, with headers X-Chunk-Index (0-based), X-Total-Chunks, X-Chunk-Size, X-Total-File-Size, X-File-Name (URI-encoded), X-File-Type, and X-Resume-Token (8 to 128 characters of A-Z a-z 0-9 _ -, the same for every chunk of one file). The response to the last chunk carries isComplete: true and the created document. The CLI does this for you for anything over 5 MB.
Both ceilings are reported before you send a byte: POST /api/v1/uploads/ returns maxFileSizeInMBytes (the plan) and maxRequestSizeInMBytes (the edge). See Upload size limits for the error codes.
Filenames with quotes
A double quote inside a filename ends the Content-Disposition header value early, and the request answers 400 with code: "upload/malformed-multipart". Escape the quote or use RFC 5987 encoding when you build multipart by hand. curl and every HTTP library do this for you.
4. Wait for processing
Ingestion is asynchronous: Docana extracts text, transcribes media, and indexes everything for search. Poll the status until it settles:
curl "https://platform.docana.com/api/v1/documents/${DOCUMENT_ID}/status/" \
-H "Authorization: ApiKey ${DOCANA_API_KEY}"
Or the same call from the terminal, no auth headers to manage:
docana api GET /api/v1/documents/12345/status/
{ "id": 12345, "status": "PROCESSING" }
The status moves from QUEUED through PROCESSING to COMPLETED or FAILED. Only act on those last two. The response is never cached, so polling is safe.
5. Check what came out
Once a document is COMPLETED, check both retrieval and extracted content before using it in the employee's work.
It's searchable. Verify from the terminal, or from your own systems through the search API:
docana documents search "termination clause" -c "$COLLECTION_ID"
Document ID Title Collection Summary
12345 contract-2026-08.pdf Contracts Services agreement with 30-day termination…
Its content is readable. The extracted text, transcription, or image description is one GET away. See Read Document Content for what each file type returns.
Moving documents between collections
POST /api/v1/documents/{id}/move/ with { "collectionId": 38782 } (and an optional folderId in the destination) relocates a document. It keeps its id, its status and its knowledge, so nothing is re-ingested and no budget is spent. From the terminal:
docana documents move "$DOCUMENT_ID" --to-collection 38782
POST /api/v1/documents/{id}/export/ is different: it copies the document into another collection as a new QUEUED document, which is ingested again, and leaves the original where it was. Use export when you want the document in both places.
Or let Docana do the syncing
If your documents live in Google Drive, SharePoint, Dropbox, OneDrive, or Box, you may not need any of this: connectors sync those sources on a schedule. Build your own ingestion when the documents come from your product or a system without a connector.
Next steps
-
Employees: connect and inspect sources in the preparation workspace
-
Read Document Content: Get the extracted text back out
-
Connectors: Managed sync for common sources
-
Enterprise Search: What indexing buys your team