Uploading/Downloading Documents
A document is a file attached to a record. Over the API a document has two parts: its metadata, which is a Document object with a RecordID, a Name, a FileName, and optionally a Category, and its content, which is the file bytes. Small files can travel inside the metadata in one call. Large files are uploaded in chunks after the metadata is created, and downloaded straight to disk, so that nothing has to fit in memory at once.
List a record's documents
GetDocumentsAsync(recordID) returns the documents on a record one page at a time, and takes optional name and category filters. A record with no documents returns an empty list.
var docs = await client.GetDocumentsAsync(recordID, pageNum: 1, numPerPage: 50);
foreach (var doc in docs)
Console.WriteLine($"{doc.ID}: {doc.Name} ({doc.FileName}, {doc.PageCount} pages, category {doc.Category})");
Raw request:
GET /rest/v2/records/123/documents?pageNum=1&numPerPage=50
Authorization: Bearer dmapi_...
Upload a small file in one call
For files of a few megabytes, put the bytes in DocumentValue and save. FileName is required whenever bytes are present, because its extension tells DocMgt what kind of file it is. Name is the title shown in the web app, so set it to something a person will recognize.
var path = @"C:\Outbound\invoice-2041.pdf";
var doc = new Document
{
RecordID = recordID,
Name = "Invoice 2041",
FileName = Path.GetFileName(path),
Category = "Invoices",
DocumentValue = await File.ReadAllBytesAsync(path)
};
var saved = await client.SaveDocumentAsync(doc);
Console.WriteLine($"Uploaded document {saved.ID}");
Raw request, with the file bytes Base64-encoded into the JSON:
POST /rest/v2/documents
Authorization: Bearer dmapi_...
Content-Type: application/json
{
"ID": 0,
"RecordID": 123,
"Name": "Invoice 2041",
"FileName": "invoice-2041.pdf",
"Category": "Invoices",
"DocumentValue": "JVBERi0xLjQKJ..."
}
The response is the document's metadata with its new ID. The bytes are never echoed back. GetMaxFileSizeAsync returns the largest upload the site accepts in one request; anything larger must use the chunked upload below.
Upload a large file in chunks
For anything that might be large, create the document first with no bytes, then send the content with UploadDocumentAsync. The helper splits the file into chunks and sends each one as a multipart file POST, and the server assembles them. The default chunk is 45 MB and that is also the maximum. The optional progress callback receives the running byte count and returns false to stop the upload.
var path = @"C:\Outbound\drawings.zip";
var fileName = Path.GetFileName(path);
var doc = await client.SaveDocumentAsync(new Document
{
RecordID = recordID,
Name = "Site drawings",
FileName = fileName
});
await client.UploadDocumentAsync(doc.ID, fileName, await File.ReadAllBytesAsync(path),
performOCR: false,
updateFunction: bytesSoFar => { Console.WriteLine($"{bytesSoFar:N0} bytes sent"); return true; });
Raw requests, one per chunk. The first chunk replaces whatever is there, the following chunks append, and the last one drops SkipRevision so the server finalizes the file:
POST /rest/v2/documents/456/binary?SkipRevision=true&AppendBytes=false
Authorization: Bearer dmapi_...
Content-Type: multipart/form-data
[form field "File" containing chunk 1]
POST /rest/v2/documents/456/binary?SkipRevision=true&AppendBytes=true
[form field "File" containing chunk 2]
POST /rest/v2/documents/456/binary?SkipRevision=false&AppendBytes=true&PerformOCR=0
[form field "File" containing the last chunk]
A file that fits in one chunk is a single POST with AppendBytes=false and SkipRevision=false. PerformOCR is 1 to run OCR, 0 to skip it, and omitted to let the Record Type's setting decide. Pass null for performOCR in the helper for that default.
UploadDocumentCompleteAsync exists for a different path, where the bytes were written directly to storage rather than posted through the API; you do not call it after UploadDocumentAsync.
Replace a document's content
Posting to /binary with AppendBytes=false on an existing document replaces its file. With SkipRevision=false the previous content is kept as a revision in the document's history, and SkipRevision=true suppresses that. In the helper, UploadDocumentAsync on an existing document ID replaces the file.
Download a document to disk
DownloadDocumentToFileAsync streams the file to the path you give it without holding it in memory, writes to a temporary file, and moves it into place only when the download completes, so a failed download never leaves a truncated file behind. It returns the number of bytes written and accepts an optional progress reporter and cancellation token. This is the right call for anything that might be large.
var docs = await client.GetDocumentsAsync(recordID);
foreach (var doc in docs)
{
var target = Path.Combine(@"C:\Inbound", $"{doc.ID}-{doc.FileName}");
var bytes = await client.DownloadDocumentToFileAsync(doc.ID, target);
Console.WriteLine($"Saved {doc.Name} to {target} ({bytes:N0} bytes)");
}
Raw request. The response body is the raw file with its real content type, not JSON, so write it straight to disk:
GET /rest/v2/documents/456/content
Authorization: Bearer dmapi_...
Add ?Audit=true to record a Download entry in the document's view history, as the web app does. It is off by default so bulk exports do not fill the history.
Download into memory
When you need the bytes in memory, for example to forward them to another system, DownloadDocumentAsync returns a byte[]. DownloadDocumentAsPDFAsync returns the document converted to PDF, with applyAnnos: true burning any annotations into the pages, which is the usual choice when sending a copy outside DocMgt.
byte[] original = await client.DownloadDocumentAsync(docID);
byte[] pdf = await client.DownloadDocumentAsPDFAsync(docID, applyAnnos: true);
Raw request. This endpoint returns the bytes Base64-encoded as a JSON string, so decode before saving:
GET /rest/v2/documents/456/binary
Authorization: Bearer dmapi_...
Because that JSON wraps the whole file, keep it for small files and use /content for the rest.
Pages and thumbnails
GetDocumentPageCountAsync(docID) returns the page count, GetDocumentPageAsync(docID, page) returns one page rendered as an image, and GetDocumentPageThumbAsync(docID, page) returns a thumbnail of it. These are what a viewer or a preview list needs, and they avoid downloading the whole file.
Delete a document
DeleteDocumentAsync(id) moves the document to the recycle bin, where an administrator can restore it. PurgeDocumentAsync(id) removes it permanently and cannot be undone.
What happens after an upload
An uploaded document is treated exactly like one added in the web app. OCR runs if the Record Type calls for it or you asked for it, a thumbnail is generated, and any workflow that starts on a new document starts. OCR runs in the background, so full text is not searchable the instant the upload returns.
TIPS
- Always set FileName with the right extension; it decides how the file is viewed and converted.
- Create, then upload for anything that might be large, and let the helper chunk it.
- Download with DownloadDocumentToFileAsync unless you truly need the bytes in memory.
- Use /content, not /binary, for raw HTTP downloads of anything bigger than a few megabytes.
- Ask the Record Type to decide on OCR by leaving performOCR null, unless you know the file has no text worth reading.