Core features

Scan

List scanners, read capabilities, start a scan, confirm on the desktop and fetch the pages.

Last updated

Scanning is a capability domain of its own: a failing or busy scanner never affects printing.

Permissions and confirmation

Scanning permissions are not granted by default; request scanner.read (see devices) and scan.run (start a scan) explicitly. Every scan asks for confirmation on the desktop before it starts, and headless services refuse by default.

The whole flow

await client.ensureAuthorized(['scanner.read', 'scan.run']);

const { scanners } = await client.scannerList();
const scanner = scanners[0];
if (!scanner) throw new Error('No scanner available');

// Optional: confirm how the device will handle the parameters
const check = await client.scanValidate(scanner.scannerId, {
  source: 'platen',          // platen / feeder
  format: 'image/jpeg',
});

// Start and wait for the terminal state (including the user's confirmation)
const job = await client.scan({
  scannerId: scanner.scannerId,
  options: { source: 'platen', format: 'image/jpeg' },
  title: 'ID card front',
});

for (const page of job.pages) {
  const blob = await client.fetchScanPage(job.scanId, page.index, page.downloadTicket);
  // Use the Blob: URL.createObjectURL(blob) to display, or upload to your backend
}

scan() waits until the user confirms on the desktop, refuses, or the request times out (120 seconds by default). A refusal gives SCAN_CONSENT_DENIED, a timeout gives SCAN_CONSENT_TIMEOUT.

Capabilities and status

const caps = await client.scannerCapabilities(scannerId);
// input source × resolution × colour mode × output format × size

const status = await client.scannerStatus(scannerId);

As with printing, offer only what the capability list contains. Parameters go through the same capability check; unsupported values are downgraded with warnings, and scanValidate returns the result before anything is scanned.

Fetching the pages

Page bytes travel over HTTP on the same port, not over the WebSocket:

const blob = await client.fetchScanPage(scanId, pageIndex, page.downloadTicket);
  • downloadTicket is a short-lived ticket in the page metadata, issued only to the site that started the scan. Without it, the current bearer token is used.
  • Other origins cannot fetch these files; they get SCAN_NOT_FOUND, which does not reveal whether the job exists.
  • Pages are kept in a local temporary store only (10 minutes and 512 MiB by default) and are wiped when PrintLink restarts. After expiry or cleanup you get CONTENT_EXPIRED, so download promptly.

Console → Settings → Cache → Scan cache lets you inspect, save-as, delete or clear finished scans.

Cancelling and listing

await client.scanCancel(scanId);         // cancel an in-flight scan
const list = await client.scanList(20);  // scans from this site
const job = await client.scanStatus(scanId);

Common errors

CodeMeaning
PERMISSION_DENIEDscanner.read / scan.run were not requested
SCAN_DISABLEDScanning is switched off in settings
SCAN_CONSENT_DENIED / SCAN_CONSENT_TIMEOUTThe user refused, or confirmation timed out
SCANNER_BUSYThe device is in use; retry shortly
UNSUPPORTED_SCAN_OPTIONThe parameters exceed the device capabilities
SCAN_SOURCE_EMPTYThe feeder is empty
SCAN_TOO_LARGE / SCAN_TIMEOUTPage count, size or duration limit reached
CONTENT_EXPIREDThe pages expired or were cleaned up

See also

On this page