M15 ships five pre-built REST endpoints that form a complete async pipeline: stage raw bytes, batch them into a persistent document record, wait for background processing, submit extraction jobs, and retrieve structured results. Here is exactly what each call expects and returns.
Endpoint paths are relative to their configured service base URLs. The media and PDF extraction services may have different base URLs, but both require the application's authenticated API client.
| Phase | Endpoint | Produces |
|---|---|---|
| 1 Stage file bytes | POST/media/stage-media-file-in-chunks | 202 per chunk; client supplies fileId + offset query params |
| 2 Create upload batch | POST/pdf-extraction/upload-document-batch | batchId (upload batch) |
| 3 Poll upload batch | GET/pdf-extraction/batch/{batchId} | blobId per uploaded item |
| 4 Submit extraction jobs | POST/pdf-extraction/submit-batch | batchId (extraction batch) |
| 5 Poll extraction batch | GET/pdf-extraction/batch/{batchId} | jobId per extraction item |
| 6 Fetch extracted values | GET/pdf-extraction/status/{itemId} | extracted field values mapped to question-response pairs |
| 7 Save confirmed values | POST/pdf-extraction/batch-save | values persisted to the response set |
Each node shows the exact endpoint called and the result it produces. Decision diamonds are the two polling loops — your client retries until the batch reaches a terminal state.
Render the CleenUI Dropzone and provide its onFileUpload callback. The callback generates a stable UUID for each file, splits the file into binary chunks, sends every chunk with its absolute byte offset, and reports upload progress through updateFile.
const CHUNK_SIZE = 4 * 1024 * 1024; // 4 MB chunks
const stageFile = async (fileEntry, updateFile) => {
const stagedFileCode = crypto.randomUUID();
const chunks = [];
for (let offset = 0; offset < fileEntry.file.size; offset += CHUNK_SIZE) {
chunks.push({
body: fileEntry.file.slice(offset, offset + CHUNK_SIZE),
offset,
});
}
await Promise.all(
chunks.map(async ({ body, offset }, index) => {
await mediaApi.stageMediaFileInChunks({
fileId: stagedFileCode,
offset,
chunk: body, // raw bytes · Content-Type: application/octet-stream
});
updateFile?.({ progress: (index + 1) / chunks.length });
})
);
return { ...fileEntry, id: stagedFileCode };
};
// Wire to the CleenUI Dropzone component:
<Dropzone
accept=".pdf,image/png,image/jpeg"
onFileUpload={stageFile}
showUploadedFiles
/>The staging endpoint stores bytes temporarily in memory only — no permanent record is created, no document is parsed, no extraction begins. The client owns the generated UUID; carry it alongside the selected file because it becomes stagedFileCode in step 2. Use a separate fileId per file and do not proceed until every chunk across every selected file has successfully uploaded.
After all files are staged, call this endpoint. It captures the temporary bytes, creates a persistent document batch, parses and encrypts each document in the background, stores successful files, and releases the staged in-memory bytes.
{
"items": [
{
"extractionTypeCode": "YOUR_EXTRACTION_TYPE",
"stagedFileCode": "78962f79-97d0-41a2-99ae-6eb758d13c5a",
"password": null,
"originalFilename": "TaxReturn2025.pdf",
"hints": null
}
]
}{
"batchId": "8a14dc31-c6cb-4ecd-a7bf-2e7d42530c14",
"status": "Pending",
"totalItems": 1
}extractionTypeCodeIdentifies the configured extraction workflow or document type.stagedFileCodeThe UUID generated and used as fileId while staging in step 1.passwordThe PDF password when required; null otherwise.originalFilenameUser-visible filename, including extension.hintsOptional extraction-specific metadata; null when not needed.Persist batchId — it is the key for polling in step 3. The API responds with 202 Accepted because processing continues asynchronously in the background.
Poll this endpoint until the batch reaches a terminal state. A typical client polls every eight seconds, stops polling when the view is abandoned, and enforces a maximum attempt count or overall timeout.
{
"batchId": "8a14dc31-c6cb-4ecd-a7bf-2e7d42530c14",
"batchType": "UPLOAD_DOCUMENTS",
"status": "Pending",
"totalItems": 1,
"completedItems": 0,
"succeededItems": 0,
"failedItems": 0,
"createdUtc": "2026-07-15T01:00:28.1055312",
"completedUtc": null,
"items": [
{
"index": 0,
"status": "Running",
"blobId": null,
"jobId": null,
"errorCode": null,
"errorMessage": null
}
]
}{
"batchId": "8a14dc31-c6cb-4ecd-a7bf-2e7d42530c14",
"batchType": "UPLOAD_DOCUMENTS",
"status": "Completed",
"totalItems": 1,
"completedItems": 1,
"succeededItems": 1,
"failedItems": 0,
"createdUtc": "2026-07-15T01:00:28.1055312",
"completedUtc": "2026-07-15T01:00:30.0046745",
"items": [
{
"index": 0,
"status": "Completed",
"blobId": 121387,
"jobId": null,
"errorCode": null,
"errorMessage": null
}
]
}PendingRunningCompletedCompletedWithErrorsThe batch-level status describes the aggregate state. Each item also carries its own status, so an item can be Running while the aggregate batch still reports Pending.
Only proceed to step 4 with items whose status is Completed and whose blobId is populated. For partial failures, surface the failed filename with its errorMessage and allow the user to retry or continue with the successful items.
After the upload batch completes, submit the extraction jobs. Create one request item per successfully uploaded file and copy that item's blobId into extractionUploadBlobId.
[
{
"responseSetId": 1025,
"extractionTypeCode": "YOUR_EXTRACTION_TYPE",
"forEntityTypeId": 56,
"forEntityId": 1025,
"hints": null,
"extractionUploadBlobId": 121387
}
]{
"batchId": "7cb27722-3e91-4e8b-8468-ed632bf719c2",
"status": "Pending",
"totalItems": 1
}responseSetIdThe response set that will receive the extracted values.extractionTypeCodeThe extraction workflow to execute. Must match the code used during upload.forEntityTypeIdThe type ID of the domain entity associated with this extraction job.forEntityIdThe ID of the specific domain entity.hintsOptional workflow-specific hints such as a known form type; null when not needed.extractionUploadBlobIdThe blobId from the completed upload item in step 3.This call produces a second, distinct batchId. The first tracked document upload and produced blobId values. This one tracks extraction-job creation and produces jobId values. Keep the two batch identifiers separate — they refer to different async processes.
Poll the same batch endpoint with the extraction batchId returned in step 4. For this batch, batchType is SUBMIT_JOBS. Use the same cadence as step 3 — every eight seconds, max 60 attempts. A successfully completed item will have a populated jobId; proceed to step 6 once the batch reaches a terminal state.
This is a distinct batch from the upload batch in step 3. Keep the two batchId values separate — they refer to different async processes. Only items with a populated jobId and a Completed status are eligible for step 6.
Once the extraction batch completes, call this endpoint once per succeeded item, passing the jobId as itemId. The response contains the extracted field values for that document. On the client, map the returned values to the question-response UI — each extracted field corresponds to a question on the form. When the same field is extracted with multiple candidate values, resolve duplicates by choosing the entry with the highest confidence score.
Changes at this point are held in local UI state only — nothing is persisted yet. If the user navigates away before confirming, all extracted values are discarded. Prompt the user before allowing navigation away from an unsaved review.
When the user clicks Confirm Values, submit all question-response values together with the extraction job ID. The endpoint persists the confirmed values to the response set and marks the extraction as complete.
{
"extractionJobId": "7cb27722-3e91-4e8b-8468-ed632bf719c2",
"responses": [
{
"questionId": 101,
"value": "John Smith"
},
{
"questionId": 102,
"value": "2025-04-15"
}
]
}{
"success": true
}Disable the Confirm Values button while a save request is in flight to prevent duplicate submissions. On success, clear the local extraction state — the values now live in the response set on the server.
| What the user sees | API condition |
|---|---|
| Upload progress | Chunks are being sent to stage-media-file-in-chunks |
| Processing | Either batch is Pending or Running |
| Review extracted values | Step 6 complete; values held in local UI state |
| Partial failure | Batch is CompletedWithErrors; at least one item has errorCode |
| Saved | batch-save returned 200; values persisted to the response set |
These constraints separate a reliable document pipeline from one that silently drops files or duplicates submissions.
Use the application's authenticated API client. Do not place tokens in component code or Storybook stories.
Keep the file's stagedFileCode, upload-batch batchId, blobId, extraction-batch batchId, and jobId as distinct values.
Disable the Continue button until every selected file is fully staged across all chunks.
Cancel the polling cycle when the component unmounts or the user cancels the flow.
Treat both Completed and CompletedWithErrors as terminal. Do not wait for a state that will never arrive.
Only submit extraction jobs for items whose blobId is present and whose upload status is Completed.
Show errorCode and errorMessage per failed file. Do not discard successful items because one item failed.
Disable batch submission while a request or poll cycle is active.
Every endpoint, the chunked-upload React component, the batch-polling logic, and the C# controller code behind each call — all included in M15 on day one of your engagement.