Enrollment and the desktop agent¶
Every enrollment path ends in services.store_template(). It validates finger rules, consent,
size and the maximum number of fingers, encrypts and versions the template, and fans it out
to every in-scope device.
| Path | How |
|---|---|
| Walk-up | An admin enrolls a PIN on the device menu. The template arrives in OPERLOG/BIODATA, is matched by PIN, stored, marked present on that device, and queued to the others. Unknown PINs, missing consent or disallowed fingers are rejected, logged, and (with DELETE_REJECTED_DEVICE_TEMPLATES) deleted from the device. |
| Remote | start_enrollment_session(enrollee, device=..., fingers=[6]) queues add_user, then ENROLL_FP/ENROLL_BIO per finger. The session completes when the device uploads every requested finger, and fails if an enroll command fails. |
| Desktop agent | A local app with a USB reader (e.g. ZK9500 plus the vendor SDK) uploads templates through the agent API below. Uploads are staged (encrypted) on the session and become templates only on completion, so a cancelled session never replaces a good template. |
Rules (all settings): ENROLLMENT_REQUIRE_CONSENT, ALLOWED_FINGER_INDEXES,
FINGERS_REQUIRED_MIN (checked when an agent session completes: existing plus new fingers),
FINGERS_ALLOWED_MAX, ENROLLMENT_SESSION_TTL, TEMPLATE_MAX_BYTES.
Agent contract¶
Create an agent (API POST enrollment-agents/, admin, or fpa_create_agent "HR desk"
--algorithm 10). The key is shown once; only its SHA-256 is stored.
All requests send Authorization: Agent <key> and are rate limited by AGENT_RATE_LIMIT.
Base URL: /api/fingerprint/v1/agent/.
| Method & path | Body | Response |
|---|---|---|
GET me/ |
agent info, allowed fingers, limits, server time | |
GET sessions/ |
open sessions for this agent | |
POST sessions/ |
{"enrollee": "<uuid or PIN>", "fingers": [5,6]} |
new session (only with AGENT_CAN_START_SESSIONS) |
GET sessions/{id}/ |
session | |
POST sessions/{id}/claim/ |
marks in_progress |
|
POST sessions/{id}/templates/ |
{"finger_index": 6, "algorithm_version": "10", "template": "<base64>", "quality": 80} |
{"captured": [...], "remaining": [...]} |
POST sessions/{id}/complete/ |
completed; templates stored and synced |
|
POST sessions/{id}/cancel/ |
{"reason": "..."} |
cancelled; staged data discarded |
Errors use the standard format: {"error": {"code", "message", "details"}}. Codes include
session_closed, wrong_agent, finger_not_requested, algorithm_incompatible,
consent_required, fingers_missing and too_few_fingers.
Algorithm compatibility: the upload must match the agent's registered algorithm, and at
least one in-scope device must accept it (ALGORITHM_COMPATIBILITY_MAP, e.g.
{"12": ["10"]} if your v12 devices accept v10 templates). Devices that cannot use it are
marked incompatible in sync status.
Example client (not part of the package)¶
"""Minimal desktop enrollment agent. Replace capture() with your reader SDK."""
import base64
import time
import requests
BASE = "https://attendance.example.com/api/fingerprint/v1/agent"
HEADERS = {"Authorization": "Agent fpa_XXXXXXXXXXXXXXXX"}
def capture(finger_index: int) -> tuple[bytes, int]:
"""Call the vendor SDK (e.g. ZKFinger for ZK9500): merge 3 presses into one
template and return (template_bytes, quality)."""
raise NotImplementedError
def run() -> None:
me = requests.get(f"{BASE}/me/", headers=HEADERS, timeout=10).json()
print("connected as", me["name"])
while True:
sessions = requests.get(f"{BASE}/sessions/", headers=HEADERS, timeout=10).json()
for session in sessions:
sid = session["id"]
requests.post(f"{BASE}/sessions/{sid}/claim/", headers=HEADERS, timeout=10)
print("enrolling", session["enrollee"]["display_name"])
try:
for finger in session["fingers_requested"]:
data, quality = capture(finger)
r = requests.post(f"{BASE}/sessions/{sid}/templates/", headers=HEADERS,
timeout=10, json={
"finger_index": finger,
"algorithm_version": me["algorithm_version"],
"template": base64.b64encode(data).decode(),
"quality": quality})
r.raise_for_status()
requests.post(f"{BASE}/sessions/{sid}/complete/", headers=HEADERS,
timeout=10).raise_for_status()
except Exception as exc: # noqa: BLE001
requests.post(f"{BASE}/sessions/{sid}/cancel/", headers=HEADERS, timeout=10,
json={"reason": str(exc)[:200]})
time.sleep(3)
if __name__ == "__main__":
run()
Admins start agent sessions with POST /api/fingerprint/v1/enrollment-sessions/
{"enrollee": "<uuid>", "agent": "<agent uuid>", "fingers": [6, 1]}.