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?

Online Demo

https://yushulx.me/web-twain-document-scan-management/examples/rest_api/

Prerequisites

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 timeout parameter 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>

scan documents via RESTful API in web

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/Scanners should 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:18623 once and accept the certificate, or configure a trusted certificate for the Dynamsoft Service.
  • Scanner returns empty images or hangs: Ensure IfFeederEnabled is set correctly — set it to true only when using an automatic document feeder (ADF). If the scanner has no paper in the feeder and IfFeederEnabled is true, 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