📚 Learning Project: This project was created with AI/Copilot assistance as a learning exercise.
Scan2Target is a web-based scan server for Linux systems, Raspberry Pi, and virtual machines. It centralizes scanner control for USB and network devices, offering a modern web interface and REST API to initiate scans and automatically route documents to configured destinations (SMB shares, email, cloud storage, webhooks, etc.).
- Platform: Linux servers, Raspberry Pi, VMs; efficient resource usage
- Backends: eSCL/AirScan and SANE for scanning
- Targets: SMB/CIFS, SFTP, Email (SMTP), Paperless-ngx, Webhooks, Cloud (Google Drive, Dropbox, OneDrive, Nextcloud/WebDAV)
- Security: Optional JWT authentication, encrypted credential storage (Fernet AES-128), reverse-proxy friendly HTTPS
- Extensibility: Pluggable targets, scan profiles, real-time WebSocket updates
- Analytics: Hourly scan distribution, scanner/target statistics, timeline tracking with proper timezone conversion
+--------------------------------------------------------------+
| Web UI (Svelte) |
+--------------------------------------------------------------+
| REST/HTTPS (FastAPI) | WebSocket events
+----------------------+ +---------------------------------+
| API Routers | | Auth layer |
+----------------------+ +---------------------------------+
| |
| v
| +-------------------+
| | Config & Secrets |
| | (SQLite + YAML) |
| +-------------------+
v |
+--------------------------------------------------------------+
| Application Core |
| Scanning | Printing | Targets | Jobs/History | Services |
| (SANE/ | (CUPS) | (SMB, | (SQLite) | Schedulers |
| eSCL) | | SFTP, | | Event bus) |
| | | Email, | | |
| | | Paperless, Webhook) |
+--------------------------------------------------------------+
|
v
Device/Backend layer (SANE/eSCL/AirScan, CUPS/IPP)
- API: Versioned REST endpoints, authentication middleware, request validation, event streaming for job updates.
- Core/Scanning: Discover scanners (mDNS/Avahi and SANE backends), normalize capabilities, start scans, manage temporary files, and hand off to Targets.
- Core/Printing: Enumerate printers via CUPS, submit jobs, query queues, print test pages.
- Core/Targets: Upload/route scan outputs to configured destinations; provide connectivity tests.
- Core/Config: Manage settings, credentials, profiles; encryption-at-rest for secrets; pluggable storage backends (SQLite primary, YAML for bootstrapping and offline edits).
- Core/Jobs: Track scan and print jobs, persist status, and expose history.
- Services: Background schedulers (e.g., retry failed targets), watcher for Paperless consume folder handoff, IP allowlist enforcement helper.
- Web UI: Dashboard, scan/print pages, targets management, history, and configuration.
- User Action: User selects scanner, profile (DPI, color, size, format), and target; optional filename prefix.
- API:
POST /api/v1/scan/startvalidates payload and enqueues a scan job. - Scanner Selection: Core/Scanning resolves the device via eSCL/AirScan or SANE backend based on discovered inventory.
- Execution: Start scan with requested parameters; stream to temporary file.
- Post-processing: Convert to PDF/JPEG as needed; apply filename template
{prefix}{profile}_{date}_{time}.{ext}. - Target Delivery: Core/Targets writes or uploads the file to the selected destination; optional webhook notification.
- Job Tracking: Core/Jobs records status transitions (queued → running → completed/failed/cancelled) and stores metadata (scanner, profile, target, file path/URL). All timestamps stored as UTC. Jobs can be cancelled via
POST /api/v1/history/{id}/cancelwhich stops the background task and updates status. - UI Feedback: Clients poll
GET /api/v1/scan/jobs/{id}or receive WebSocket events for progress. Frontend converts UTC timestamps to browser local time. Active scans show cancel button for immediate termination.
POST /api/v1/scan/sessionscreates a persistent SQLite session and a private directory below/data/scan-sessions.POST /api/v1/scan/sessions/{id}/capturestores one page in interactive mode or a complete ADF stack in automatic mode. Only authenticated thumbnail blobs are sent to the browser.- Page rotation, deletion and ordering are persisted immediately. An active
scanimageprocess is registered under the session ID and can be terminated by deleting the session. GET /api/v1/scan/sessionsrestores unfinished sessions after a browser or service restart. Sessions expire afterSCAN2TARGET_SCAN_SESSION_TTL_HOURS.POST /api/v1/scan/sessions/{id}/finalizeapplies optional contrast/margin optimization and blank-page removal, creates one PDF, and optionally runs OCRmyPDF for deskewed searchable PDF/A-2 output.- The resulting file enters the persistent delivery retry queue; temporary session pages are removed only after the delivery job has been created.
- User Action: Upload PDF/JPEG/PNG, choose printer and options.
- API:
POST /api/v1/printstores the file (temp) and submits to CUPS via Core/Printing. - CUPS Handling: Job ID returned; status polled via
GET /api/v1/printers/{id}/jobs. - Job Tracking: Core/Jobs records print job metadata and status.
- Local folder: Ensure directory path exists (
/data/scans/YYYY/MM/DD), write file, set permissions. - SMB/CIFS: Mount-on-demand using
smbclientorpysmb; unmount/cleanup after upload. - SFTP (optional): Use
paramikofor uploads. - Email: Send via SMTP with TLS; attach file and include metadata.
- Paperless-ngx: Write to consume folder OR POST to HTTP API with token.
- Webhook: POST JSON metadata plus signed URL/location of the file.
GET /api/v1/scan/devices— list discovered scanners and capabilities.GET /api/v1/scan/profiles— list predefined scan profiles.POST /api/v1/scan/start— start a scan with device/profile/target selection.GET /api/v1/scan/sessions— list resumable multi-page sessions.POST /api/v1/scan/sessions— create a persistent multi-page session.POST /api/v1/scan/sessions/{id}/capture— capture one page or one ADF stack.PUT /api/v1/scan/sessions/{id}/pages— persist page ordering.DELETE /api/v1/scan/sessions/{id}— cancel the session and active scanner process.POST /api/v1/scan/sessions/{id}/finalize— optimize, OCR, create and deliver the PDF.GET /api/v1/scan/jobs— list scan jobs.GET /api/v1/scan/jobs/{id}— job status & result link.GET /api/v1/printers— list printers & status.GET /api/v1/printers/{id}/jobs— list jobs for a printer.POST /api/v1/print— upload + submit print job.POST /api/v1/printers/{id}/test— print a test page.GET /api/v1/targets— list targets.POST /api/v1/targets— create target (SMB/SFTP/Email/Paperless/Webhook/Local folder).PUT /api/v1/targets/{id}— update target settings.DELETE /api/v1/targets/{id}— delete target.POST /api/v1/targets/{id}/test— connectivity test.GET /api/v1/history— unified scan/print history.DELETE /api/v1/history— clear completed jobs.DELETE /api/v1/history/{id}— delete single job.POST /api/v1/history/{id}/cancel— cancel running or queued job.POST /api/v1/history/{id}/retry-upload— retry failed upload.GET /api/v1/stats/overview— total scans, success rate, averages.GET /api/v1/stats/timeline— daily scan counts (last 30 days).GET /api/v1/stats/scanners— per-scanner usage statistics.GET /api/v1/stats/targets— per-target delivery statistics.DELETE /api/v1/stats/targets/{name}— delete all jobs for a target.POST /api/v1/auth/login— obtain session/token.POST /api/v1/auth/logout— revoke session.POST /api/v1/homeassistant/scan— Home Assistant scan trigger (supports favorites).GET /api/v1/homeassistant/status— Home Assistant status sensor.
Example payloads:
POST /api/v1/scan/start
{
"device_id": "escl:HP_Envy_6400",
"profile_id": "scan_a4_color_300",
"target_id": "smb:nas_scans",
"filename_prefix": "invoices_"
}POST /api/v1/print
{
"printer_id": "ipp://printer.local/ipp/print",
"options": {"sides": "two-sided-long-edge"},
"file_id": "upload-temp-uuid"
}- Storage Choice: SQLite for structured configuration, users, jobs, and targets; YAML for bootstrap defaults and easy manual edits. SQLite chosen for atomic updates, concurrent access, and simple backups on Pi. Secrets stored encrypted (e.g., Fernet key in
/etc/scan2target/secret.keyor~/.scan2target/encryption.key). - Entities:
User: username, password hash, roles, allowed IP subnets.Scanner: id, type (eSCL/SANE), capabilities, last_seen.Printer: id, uri, name, status, defaults.ScanProfile: id, dpi, color_mode, paper_size, format.Target: id, type (local, smb, sftp, email, paperless_folder, paperless_api, webhook), config blob (encrypted fields for credentials), enabled flag.Job: id, type (scan/print), device_id, target_id (scan), printer_id (print), file_path/url, status (queued/running/completed/failed/cancelled), timestamps, logs.ScanSession: id, scanner/profile/source, interactive or automatic capture mode, status, ordered server-side pages and timestamps.
- Auth: Session or JWT tokens; password hashing via
argon2orbcrypt; HTTPS recommended behind Caddy/nginx; optional IP allowlist enforced per request. - Secrets: Encrypted credential fields; filesystem isolation (
/etc/scan2target/for secrets,/data/scan2targetfor runtime data). - Network Exposure: Run FastAPI on port 80 (or behind reverse proxy for TLS); disable anonymous access if needed; CSRF protection for web UI.
- Native: Systemd units to start FastAPI (uvicorn) and optional workers; Avahi for mDNS/AirPrint; CUPS installed with IPP Everywhere.
- Container: Docker Compose defining API, frontend, CUPS, Avahi reflector, and a data volume for
/data/scan2target.
- Performance: Use asynchronous I/O for network transfers; scanning/printing operations offloaded to worker threads to avoid blocking event loop.
- Logging: Structured logging (JSON) with per-module loggers; include job_id correlation; log target upload failures clearly.
- Observability: Health endpoint, metrics hook (e.g., Prometheus exporter) optional.
- Cloud storage targets; hardware button support via GPIO; multi-tenant user roles.