Skip to content

Reference

Scanner protocol & API

How DUXAC.exe talks to the API (PIN claim, consent, signed uploads, verdict) and the public endpoints.

All scanner routes live under /v1/scanner. Requests and responses are JSON. Errors always have this shape:

json
{ "error": { "code": "pin_invalid", "message": "..." } }

Flow#

StepRouteAuth
Update checkGET /v1/scanner/latest returns { version, sha256, downloadUrl, mandatory, minimumVersion }public
ClaimPOST /v1/scanner/claim with { pin, scannerVersion, scannerSha256, os, isAdmin }the PIN
ConsentPOST /v1/scanner/scans/:id/consent with { accepted }, returns { status, config }signed
ProgressPOST /v1/scanner/scans/:id/progress with { checkId, phase, percent }signed
FindingsPOST /v1/scanner/scans/:id/findings with { findings: [...] }signed
CompletePOST /v1/scanner/scans/:id/complete with { systemInfo, checkResults, totalFindings, durationMs, integrity }signed
FailPOST /v1/scanner/scans/:id/fail with { reason }signed

Claim normalises the PIN (case, dashes and spaces are ignored), finds the pending scan, and returns a one-time scanToken, the consent text and the list of checks. A wrong, unknown or expired PIN always gives the same pin_invalid error, and claims are rate-limited per IP.

Consent with accepted: true starts the scan and returns the scan configuration: the effective checks with their options and severity caps, the signatures (global, plus the workspace's own when its plan allows), the trusted publishers whose signed software is never reported, the hwidKey and the scan deadline.

Findings are idempotent on findingId (a UUID generated by the scanner), so a retried upload never creates duplicates. The API clamps each finding's severity to its check's maximum.

Complete stores the results and computes the verdict (see Verdicts & review). The player only receives a neutral message.

Request signing#

Every request after claim is signed with the per-scan secret (key id = scan id, secret = scanToken):

http
X-Duxac-Key-Id: <scan id>
X-Duxac-Timestamp: <unix time in milliseconds>
X-Duxac-Nonce: <32 lowercase hex characters, unique per request>
X-Duxac-Signature: <lowercase hex HMAC-SHA256(secret, canonical)>

The canonical string is these six lines joined with \n:

text
v1
<METHOD in upper case>
<path with query exactly as sent, e.g. /v1/scanner/scans/<id>/findings>
<timestamp>
<nonce>
<sha256 hex of the raw request body; hash of "" when there is no body>

The API rejects requests outside the allowed clock skew, reused nonces, and signatures that do not match (compared in constant time). The scanner and the API are tested against the same signing vectors. Once a scan is completed, failed or expired, every signed call returns scan_not_running.

Limits#

LimitValue
PIN8 characters from 23456789ABCDEFGHJKMNPQRSTUVWXYZ, shown as XXXX-XXXX
PIN validity15 minutes
Time to answer the consent screen10 minutes
Time to complete an accepted scan30 minutes
Signature clock skew±120 seconds
Nonce memory300 seconds
Findings per upload batch200
Findings per scan2000
Evidence per finding4096 bytes of JSON
Signatures per scan config20000

Error codes#

pin_invalid, scan_expired, scan_not_running, scanner_outdated, license_inactive, quota_exceeded, signature_invalid, timestamp_skew, nonce_replayed, rate_limited, validation_failed, internal_error

Public endpoints#

No authentication; rate-limited.

RouteReturns
GET /v1/public/plansPublished plans
GET /v1/public/checksThe check catalog (id, name, category, description, data accessed, admin requirement)
GET /v1/public/releasesScanner releases (changelog)
GET /v1/public/download/latestThe latest stable DUXAC.exe (404 when none is uploaded)
GET /v1/public/status{ api, database, cache, scansLast24h, time }

The dashboard and admin APIs (/v1/auth, /v1/me, /v1/admin) use a session cookie and require the header X-Duxac-Csrf: 1 on every non-GET request. Personal API tokens are available on plans with API access.