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);downloadTicketis 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
| Code | Meaning |
|---|---|
PERMISSION_DENIED | scanner.read / scan.run were not requested |
SCAN_DISABLED | Scanning is switched off in settings |
SCAN_CONSENT_DENIED / SCAN_CONSENT_TIMEOUT | The user refused, or confirmation timed out |
SCANNER_BUSY | The device is in use; retry shortly |
UNSUPPORTED_SCAN_OPTION | The parameters exceed the device capabilities |
SCAN_SOURCE_EMPTY | The feeder is empty |
SCAN_TOO_LARGE / SCAN_TIMEOUT | Page count, size or duration limit reached |
CONTENT_EXPIRED | The pages expired or were cleaned up |