How to Scan Documents from a Web Page Using the Dynamic Web TWAIN REST API
Dynamic Web TWAIN offers two methods for scanning documents from a web page: the HTML5/JavaScript API and the RESTful API. The former is suitable for web applications that require a high level of customization, while the latter is ideal for enterprise-class web applications, such as Salesforce, Oracle APEX, and Microsoft Power Apps, that need to scan documents in a sandboxed or security-restricted environment. This article will focus on the RESTful API and demonstrate how to scan paper documents to web pages using JavaScript and HTTP requests.
What you’ll build: A web page that discovers connected document scanners, triggers a scan job, and displays the scanned images — all through pure JavaScript HTTP requests to the Dynamic Web TWAIN REST API.
Key Takeaways
- The Dynamic Web TWAIN REST API enables browser-based document scanning without plugins or ActiveX — only standard HTTP requests are needed.
- Scan jobs are created by POSTing a JSON payload (license key, device info, scan config) to
/DWTAPI/ScanJobs, and scanned images are retrieved as binary data from/DWTAPI/ScanJobs/{jobId}/NextDocument. - The REST API supports TWAIN, WIA, SANE, ICA, and eSCL scanner protocols across Windows, macOS, and Linux.
- This approach is ideal for sandboxed enterprise platforms like Salesforce, Oracle APEX, and Microsoft Power Apps where traditional JavaScript SDK integration is restricted.
Common Developer Questions
- How do I scan documents from a web page using a REST API in JavaScript?
- What scanner protocols does the Dynamic Web TWAIN REST API support on Linux and macOS?
- How do I retrieve scanned images as JPEG from a REST-based document scanning service?
This article is Part 6 in a 8-Part Series.
- Part 1 - How to Build a Document Scanning REST API in Node.js
- Part 2 - How to Scan Documents from TWAIN, WIA, and eSCL Scanners in a Flutter App
- Part 3 - How to Scan Documents in Java Using TWAIN, WIA, eSCL, and SANE via REST API
- Part 4 - Build a Cross-Platform Python Document Scanner with TWAIN, WIA, and SANE
- Part 5 - How to Build a Cross-Platform .NET C# Document Scanner with TWAIN, WIA, SANE, and eSCL Support
- Part 6 - How to Scan Documents from a Web Page Using the Dynamic Web TWAIN REST API
- Part 7 - Build a SwiftUI Remote Document Scanner for macOS and iOS Using the Dynamic Web TWAIN REST API
- Part 8 - Build a Web Document Scanner with JavaScript: File, Camera, and TWAIN Scanner Support
Online Demo
https://yushulx.me/web-twain-document-scan-management/examples/rest_api/
Prerequisites
- Install and run Dynamic Web TWAIN Service.
- Get a 30-day free trial license for Dynamic Web TWAIN.
-
Include axios.
Axios is a promise-based HTTP client for the browser and Node.js. Since we have used it in a Node.js example, migrating the RESTful API calls to a web page is straightforward.
<script src="https://unpkg.com/axios@1.6.7/dist/axios.min.js"></script>
Step 1: Connect to the Dynamic Web TWAIN REST API
The Dynamic Web TWAIN Service runs on default ports 18622 for HTTP and 18623 for HTTPS.
To construct the host URL for the RESTful API:
const port = window.location.protocol === 'https:' ? 18623 : 18622;
const host = window.location.protocol + '//' + "127.0.0.1:" + port;
Step 2: Enumerate Available Document Scanners
The endpoint /DWTAPI/Scanners returns all available scanners as an array of objects, each containing the scanner’s name and ID.
Fetch the Scanner List
async function getDevices(host, scannerType) {
devices = [];
let url = host + '/DWTAPI/Scanners'
if (scannerType != null) {
url += '?type=' + scannerType;
}
try {
let response = await axios.get(url)
.catch(error => {
console.log(error);
});
if (response.status == 200 && response.data.length > 0) {
console.log('\nAvailable scanners: ' + response.data.length);
return response.data;
}
} catch (error) {
console.log(error);
}
return [];
}
Supported Scanner Protocol Types
The supported scanner protocol types include:
- TWAIN
- SANE
- ICA
- WIA
- ESCL
List TWAIN Scanners in an HTML Select Element
JavaScript
let queryDevicesButton = document.getElementById("query-devices-button");
queryDevicesButton.onclick = async () => {
let scannerType = ScannerType.TWAINSCANNER | ScannerType.TWAINX64SCANNER;
let devices = await getDevices(host, scannerType);
let select = document.getElementById("sources");
select.innerHTML = '';
for (let i = 0; i < devices.length; i++) {
let device = devices[i];
let option = document.createElement("option");
option.text = device['name'];
option.value = JSON.stringify(device);
select.add(option);
};
}
HTML
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Dynamsoft RESTful API Example</title>
<link rel="stylesheet" href="main.css">
<script src="https://unpkg.com/axios@1.6.7/dist/axios.min.js"></script>
</head>
<body>
<button id="query-devices-button">Get Devices</button>
<select id="sources"></select>
<script src="main.js"></script>
</body>
</html>
Step 3: Send a Document Scan Request
To scan documents, specify the license key, scanner information, and other scan parameters in a JSON object, then POST the data to the /DWTAPI/ScanJobs endpoint.
Create a Scan Job
let parameters = {
license: license,
device: JSON.parse(device),
};
parameters.config = {
IfShowUI: false,
PixelType: 2,
//XferCount: 1,
//PageSize: 1,
Resolution: 200,
IfFeederEnabled: false, // Set to true if you want to scan multiple pages
IfDuplexEnabled: false,
};
let jobId = await scanDocument(host, parameters);
async function scanDocument(host, parameters, timeout = 30) {
let url = host + '/DWTAPI/ScanJobs?timeout=' + timeout;
try {
let response = await axios.post(url, parameters)
.catch(error => {
console.log('Error: ' + error);
});
let jobId = response.data;
if (response.status == 201) {
return jobId;
}
else {
console.log(response);
}
}
catch (error) {
console.log(error);
}
return '';
}
- The
timeoutparameter is optional. If the scanner does not respond within the specified time, the scan job will be canceled. - The job ID is returned if the scan job is created successfully and can be used to retrieve the scanned images.
Step 4: Retrieve and Display the Scanned Images
Use the /DWTAPI/ScanJobs/{jobId}/NextDocument endpoint to retrieve the scanned images.
let url = host + '/DWTAPI/ScanJobs/' + jobId + '/NextDocument';
const response = await axios({
method: 'GET',
url: url,
responseType: 'arraybuffer',
});
Note: Set the response type to stream in Node.js and arraybuffer in the browser.
To display the scanned document in a browser, create a Blob object from the array buffer and use the URL.createObjectURL method to generate a URL for an HTML image element.
const arrayBuffer = response.data;
const blob = new Blob([arrayBuffer], { type: 'image/jpeg' });
const imageUrl = URL.createObjectURL(blob);
Repeat the request until the status code is not 200. Store all scanned image URLs in an array and return them to the caller.
async function getImages(host, jobId) {
let images = [];
let url = host + '/DWTAPI/ScanJobs/' + jobId + '/NextDocument';
console.log('Start downloading images......');
while (true) {
try {
const response = await axios({
method: 'GET',
url: url,
responseType: 'arraybuffer',
});
if (response.status == 200) {
const arrayBuffer = response.data;
const blob = new Blob([arrayBuffer], { type: 'image/jpeg' });
const imageUrl = URL.createObjectURL(blob);
images.push(imageUrl);
}
else {
console.log(response);
}
} catch (error) {
// console.error("Error downloading image:", error);
console.error('No more images.');
break;
}
}
return images;
}
Finally, you can display the scanned images as follows:
<div class="row">
<div class="full-img">
<img id="document-image">
</div>
</div>
<div class="row">
<div class="thumb-bar" id="thumb-bar">
<div class="thumb-box" id="thumb-box">
</div>
</div>
</div>
<script>
let images = await getImages(host, jobId, 'images');
for (let i = 0; i < images.length; i++) {
let url = images[i];
let img = document.getElementById('document-image');
img.src = url;
data.push(url);
let option = document.createElement("option");
option.selected = true;
option.text = url;
option.value = url;
let thumbnails = document.getElementById("thumb-box");
let newImage = document.createElement('img');
newImage.setAttribute('src', url);
if (thumbnails != null) {
thumbnails.appendChild(newImage);
newImage.addEventListener('click', e => {
if (e != null && e.target != null) {
let target = e.target;
img.src = target.src;
}
});
}
}
</script>

Common Issues and Edge Cases
- Service not running or blocked by firewall: If the REST API calls fail with a connection error, verify that the Dynamsoft Service is running (
http://127.0.0.1:18622/DWTAPI/Scannersshould return a response) and that your firewall or antivirus is not blocking localhost traffic on ports 18622/18623. - HTTPS certificate warning in the browser: When accessing the API over HTTPS (port 18623), the browser may reject the self-signed certificate. Navigate to
https://127.0.0.1:18623once and accept the certificate, or configure a trusted certificate for the Dynamsoft Service. - Scanner returns empty images or hangs: Ensure
IfFeederEnabledis set correctly — set it totrueonly when using an automatic document feeder (ADF). If the scanner has no paper in the feeder andIfFeederEnabledistrue, the scan job may time out or return no images.
Source Code
https://github.com/yushulx/web-twain-document-scan-management/tree/main/examples/rest_api