UploadDocumentWithHandler3 API¶
Finalizes a chunked file upload and creates a new document or a new version of an existing document at the specified path, with extended options supplied through an XML parameters string. This is the most flexible handler-finalization method and supports all upload options including version comment, publish option, checkout, keywords, text-only content, manual version numbers, custom dates, and email notifications.
Endpoint¶
Methods¶
- GET
/srv.asmx/UploadDocumentWithHandler3?authenticationTicket=...&path=...&uploadHandler=...&xmlParameters=... - POST
/srv.asmx/UploadDocumentWithHandler3(form data) - SOAP Action:
http://tempuri.org/UploadDocumentWithHandler3
Parameters¶
| Parameter | Type | Required | Description |
|---|---|---|---|
authenticationTicket |
string | Yes | Authentication ticket obtained from AuthenticateUser. |
path |
string | Yes | Full infoRouter destination path including file name (e.g. /Finance/Reports/Q1-Report.pdf). |
uploadHandler |
string (GUID) | Yes | The upload handler GUID returned by CreateUploadHandler after all chunks have been uploaded. |
xmlParameters |
string | Yes | XML string with additional upload options. Pass an empty string "" for default behavior. See XML Parameters section below. |
XML Parameters Format¶
The xmlParameters is an XML string with the same format as UploadDocument4. The root element is <xmlparameters> and each option is an <item> element with NAME and VALUE attributes:
<xmlparameters>
<item NAME="DESCRIPTION" VALUE="Quarterly financial summary"/>
<item NAME="KEYWORDS" VALUE="finance quarterly 2024"/>
<item NAME="VERSIONCOMMENT" VALUE="Updated figures"/>
<item NAME="CHECKOUT" VALUE="TRUE"/>
<item NAME="PUBLISHOPTION" VALUE="Publish"/>
<item NAME="SENDEMAILS" VALUE="true"/>
<item NAME="TEXTONLYCONTENT" VALUE="Plain text version of the document"/>
<item NAME="CREATIONDATE" VALUE="2024-01-15"/>
<item NAME="MODIFICATIONDATE" VALUE="2024-03-31"/>
<item NAME="MPVERSIONMAJOR" VALUE="2"/>
<item NAME="MPVERSIONMINOR" VALUE="0"/>
<item NAME="MPVERSIONREVISION" VALUE="1"/>
</xmlparameters>
See UploadDocument4 for the full list of supported keys and valid values.
Chunked Upload Workflow¶
1. CreateUploadHandler — negotiate chunk size, receive handler GUID
2. UploadFileChunk — send chunks sequentially; include CRC32 per chunk
3. UploadDocumentWithHandler3 — finalize: set path, metadata, and XML parameters
4. DeleteUploadHandler — release the server-side temp file immediately after success
Each chunk must include a CRC32 hex checksum of that chunk's bytes (ChunkHEXCRC). On the last chunk the server returns the accumulated filehexcrc of the entire file — compare it against your locally computed file CRC to verify a clean transfer. If they differ, abort and retry the upload from step 1.
The server may respond with tryagain="true" on a transient failure for a single chunk — retry that same chunk without advancing the offset.
Commits content staged with UploadFileChunk, taking the same parameter document¶
UploadDocument4 does. It is required even when empty - send <parameters /> -
and a malformed one is refused with 4000.
A handler is consumed by the call that commits it: committing the same one twice is 4000
"upload handler cannot be found".
JavaScript Sample¶
The sample below uses the browser File API and fetch. It covers all four steps, computes CRC32 in-browser, handles tryagain retries, verifies the final file checksum, and explicitly deletes the upload handler after a successful finalization.
/**
* Upload a file to infoRouter using the chunked upload API.
*
* @param {string} baseUrl Server root, e.g. "https://your-server"
* @param {string} authenticationTicket Ticket from AuthenticateUser
* @param {File} file File object from <input type="file">
* @param {string} irPath Destination path, e.g. "/Finance/Reports/Q1.pdf"
* @param {string} xmlParameters XML options string, or "" for server defaults
* @returns {{ documentId: string, versionId: string }}
*/
async function uploadWithChunks(baseUrl, authenticationTicket, file, irPath, xmlParameters) {
const endpoint = `${baseUrl}/srv.asmx`;
// ── CRC32 lookup table (IEEE 802.3 polynomial) ────────────────────────────
const crcTable = (() => {
const t = new Uint32Array(256);
for (let i = 0; i < 256; i++) {
let c = i;
for (let j = 0; j < 8; j++) c = (c & 1) ? (0xEDB88320 ^ (c >>> 1)) : (c >>> 1);
t[i] = c;
}
return t;
})();
function crc32(bytes) {
let crc = 0xFFFFFFFF;
for (let i = 0; i < bytes.length; i++)
crc = crcTable[(crc ^ bytes[i]) & 0xFF] ^ (crc >>> 8);
return (crc ^ 0xFFFFFFFF) >>> 0; // unsigned 32-bit
}
function toHex(n) { return n.toString(16).toUpperCase(); }
function parseXml(text) {
return new DOMParser().parseFromString(text, "text/xml").documentElement;
}
// POST application/x-www-form-urlencoded fields
async function post(action, fields) {
const res = await fetch(`${endpoint}/${action}`, {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: new URLSearchParams(fields).toString()
});
return parseXml(await res.text());
}
// POST a single binary chunk as multipart/form-data
async function postChunk(uploadHandler, chunkBytes, chunkHexCrc, lastChunk) {
const form = new FormData();
form.append("authenticationTicket", authenticationTicket);
form.append("uploadHandler", uploadHandler);
form.append("FileChunk", new Blob([chunkBytes]));
form.append("ChunkHEXCRC", chunkHexCrc);
form.append("LastChunk", String(lastChunk));
const res = await fetch(`${endpoint}/UploadFileChunk`, { method: "POST", body: form });
return parseXml(await res.text());
}
// ── Step 1: CreateUploadHandler ───────────────────────────────────────────
const handlerXml = await post("CreateUploadHandler", {
authenticationTicket,
ChunkSize: 512 * 1024 // suggest 512 KB; server may return a different size
});
if (handlerXml.getAttribute("success") !== "true")
throw new Error("CreateUploadHandler failed: " + handlerXml.getAttribute("error"));
const uploadHandler = handlerXml.getAttribute("UploadHandler");
const chunkSize = parseInt(handlerXml.getAttribute("ChunkSize"), 10);
// ── Step 2: Read file bytes and compute full-file CRC32 ───────────────────
const fileBytes = new Uint8Array(await file.arrayBuffer());
const fileCrc = crc32(fileBytes);
// ── Step 3: Upload chunks ─────────────────────────────────────────────────
let offset = 0;
while (offset < fileBytes.length) {
const chunk = fileBytes.slice(offset, offset + chunkSize);
const chunkCrc = crc32(chunk);
const lastChunk = (offset + chunk.length) >= fileBytes.length;
let xml;
// Inner retry loop: repeat the same chunk if server responds tryagain="true"
while (true) {
xml = await postChunk(uploadHandler, chunk, toHex(chunkCrc), lastChunk);
if (xml.getAttribute("success") === "true") break;
if (xml.getAttribute("tryagain") === "true") continue; // transient — retry
throw new Error("UploadFileChunk failed: " + xml.getAttribute("error"));
}
// After the last chunk, verify the server's accumulated file CRC
if (lastChunk) {
const serverCrc = parseInt(xml.getAttribute("filehexcrc"), 16);
if (serverCrc !== fileCrc)
throw new Error(
`File CRC mismatch — local: ${toHex(fileCrc)}, server: ${xml.getAttribute("filehexcrc")}`
);
}
offset += chunk.length;
}
// ── Step 4: Finalize ──────────────────────────────────────────────────────
const finalXml = await post("UploadDocumentWithHandler3", {
authenticationTicket,
path: irPath,
uploadHandler,
xmlParameters
});
if (finalXml.getAttribute("success") !== "true")
throw new Error("UploadDocumentWithHandler3 failed: " + finalXml.getAttribute("error"));
// ── Step 5: DeleteUploadHandler ───────────────────────────────────────────
// Release the server-side temp file immediately. The server would eventually
// clean it up on its own, but calling this right after a successful finalize
// frees the storage without waiting for the background cleanup interval.
await post("DeleteUploadHandler", { authenticationTicket, uploadHandler });
return {
documentId: finalXml.getAttribute("DocumentId"),
versionId: finalXml.getAttribute("VersionId")
};
}
Usage¶
document.getElementById("uploadBtn").addEventListener("click", async () => {
const file = document.getElementById("fileInput").files[0];
const xmlParameters = [
"<xmlparameters>",
' <item NAME="VERSIONCOMMENT" VALUE="Uploaded via JS"/>',
' <item NAME="PUBLISHOPTION" VALUE="Publish"/>',
' <item NAME="SENDEMAILS" VALUE="true"/>',
"</xmlparameters>"
].join("\n");
try {
const { documentId, versionId } = await uploadWithChunks(
"https://your-inforouter-server",
"your-auth-ticket",
file,
"/Finance/Reports/" + file.name,
xmlParameters
);
console.log("Uploaded — DocumentId:", documentId, "VersionId:", versionId);
} catch (err) {
console.error("Upload failed:", err.message);
}
});
Response¶
Success Response¶
Error Response¶
Required Permissions¶
The calling user must have write (upload) permission on the destination folder.
Example¶
POST Request¶
POST /srv.asmx/UploadDocumentWithHandler3 HTTP/1.1
Content-Type: application/x-www-form-urlencoded
authenticationTicket=3f2504e0-4f89-11d3-9a0c-0305e82c3301
&path=/Finance/Reports/Q1-2024-Report.pdf
&uploadHandler=a1b2c3d4-e5f6-7890-abcd-ef1234567890
&xmlParameters=<xmlparameters><item NAME="VERSIONCOMMENT" VALUE="Revised figures"/><item NAME="PUBLISHOPTION" VALUE="Publish"/></xmlparameters>
Notes¶
- In
xmlParameters,DESCRIPTIONtakes 4000 characters and each keyword inKEYWORDStakes 128; a longer value is refused with4000. - Passing an empty string for
xmlParametersuses server defaults. - This is the recommended approach for large file uploads requiring full metadata control.
- See
UploadDocument4for the complete XML parameter reference.
Related APIs¶
- CreateUploadHandler - Create an upload handler for chunked uploads
- UploadFileChunk - Upload a single chunk
- UploadDocumentWithHandler - Basic handler finalization
- UploadDocumentWithHandler1 - Finalize with version comment
- UploadDocumentWithHandler2 - Finalize with manual version numbers
- UploadDocument4 - Direct byte-array upload with the same XML parameters
Error Codes¶
The errorCode values this operation returns, checked against a running server:
errorCode |
When |
|---|---|
4010 |
the ticket is expired or unknown, or there is no ticket at all |
4000 |
the upload handler is unknown, expired, or has already been committed |
4000 |
a document is already at that path and is not checked out by the caller |
4041 |
no folder at the parent of the path |
4030 |
the caller may not add documents there, or the folder rules forbid the file type |
HTTP 400 |
xmlParameters was empty; it is required, so send <parameters /> |
4000 |
the document was not well-formed XML; the message carries the parser's own words |
4080 |
(REST) the request was still arriving when the FileUploadTimeOut time ran out; see GetGeneralAppSettings |
| Error | Description |
|---|---|
[900] Authentication failed |
Invalid or missing authentication ticket. |
[901] Session expired or Invalid ticket |
The ticket has expired or does not exist. |
Invalid upload handler. |
The GUID is not a valid or active upload handler. |
| Invalid parameter value | An XML parameter key has an invalid value. |
| Folder not found | The destination folder in the path does not exist. |
| Access denied | The user does not have upload permission on the destination folder. |
SystemError:... |
An unexpected server-side error occurred. |