Biotime Reference Public

Research & Evidence

The evidence base behind the ERPat Biotime module: which BioTime API routes were verified against a live server, which files were read during design, and what remains documented but untested.

Guide version: r1 Module version: 1.2.0 Updated: 2026-09-02 Estimated time: 5 min 4 views 0% helpful

Research & evidence

What was actually verified, how, and — just as importantly — what was not.

Why this page exists

An integration guide that presents tested and untested behaviour in the same confident voice is misleading in the place it matters most. This page separates the two, so anyone deploying Biotime knows which parts rest on evidence and which rest on documentation.

What was tested against a live server

The module was designed against a real, running ZKBio TimeCloud tenant, not against vendor documentation. The captured contract ships with the module:

ArtifactContents
specs/openapi.json 113 documented paths across the personnel, attendance, device, access-control, GPS, workflow and report namespaces. Every path slash-terminated. Declares the auth scheme as “Use exact format: JWT <access token>. Do not use Bearer.”
specs/postman_collection.json The same routes as a runnable collection, organised into nine folders — including one explicitly named “Known Non-Working / Do Not Use”.
specs/verification_summary.json The live run: 110 routes attempted, 110 verified, on 2026-08-21. Records each route's HTTP status, vendor response code, and the field names actually returned.

The tenant carried 258,398 attendance transactions, 185 employees, 4 terminals, 15 departments and 5 areas. That scale is why the sync is incremental, cursor-driven and bounded rather than a simple loop — a full walk at 200 rows per page is roughly 1,300 requests.

Field names come from the live response, not from a guess

The stored columns mirror the field names the server actually returned. For attendance punches those were: id, emp_code, first_name, last_name, dept_code, dept_name, position_code, position_name, punch_date, punch_time, punch_state, verify_type, work_code, latitude, longitude, gps_location, terminal_sn, terminal_alias, area_alias, upload_time and capture_url.

Several fields that early design drafts assumed would exist — a source marker, an is_attendance flag, a checksum, GPS accuracy — are not in the feed and were removed rather than stored empty.

Known-broken paths

Confirmed to fail on this server generation. Biotime never calls them, and its profile-detection deliberately avoids replaying credentials against them:

PathResultUse instead
/api/jwt-api-token-auth/405/web/oauth/api/v2/user_sign_in/
/web/api/jwt-api-token-auth/Present but misconfigured/web/oauth/api/v2/user_sign_in/
/web/oauth/api/v2/sign_in/404/web/oauth/api/v2/user_sign_in/
/web/att/api/v2/employee_schedules/404/web/att/api/v2/schedules/
/web/att/api/v2/calculations/404POST …/calculations/calculate/
/web/device/api/v2/commandlogs/404/web/device/api/v2/commands/
/web/device/api/v2/terminallogs/404No replacement
/web/device/api/v2/employees/404/web/personnel/api/v2/employees/

What was NOT tested

The on-premises BioTime 9.5 profile has never run against a live server. Its endpoint map, its authentication scheme and its response envelope are derived from documentation only. A first on-premises deployment should treat the nine-step connection test as its acceptance gate and expect corrections. The cloud profile carries no such caveat.

Also untested at the time of writing: behaviour against a server under heavy sustained rate-limiting (the handling exists and is classified as BT-RATE, but the back-off has not been exercised at length), and any BioTime deployment that reports body temperature — cloud servers do not send that field at all.

ERPat-side evidence

The design also rests on facts checked directly in the ERPat codebase rather than assumed:

QuestionWhat the code saysConsequence
Are ERPat employee ID numbers unique? No. The employee model states outright that there is no uniqueness constraint, that duplicates already exist in real data, and that a collision must never block a save. A multi-candidate match is a conflict, never a guess. This single fact shapes the whole binding design.
Which HTTP client does ERPat use? Native cURL. The house client states plainly that the repository carries no first-party Guzzle. Biotime uses cURL and adds no new dependency.
How are secrets stored elsewhere? Packed AES-256-GCM with the ciphertext bound to its row, as used by the Webmail module. Biotime reuses that proven pattern rather than inventing one.
Who owns biometric enrollment records? The Kiosk module, whose shared table treats a NULL enrollment label as a wildcard matching every device. Biotime writes only under its own dedicated label — writing a wildcard would silently widen Kiosk's own matching.
Where would attendance import have to go? Through the Kiosk timelogs pipeline, which builds records from paired taps. The live clock-in path is unsuitable for imports. Import is deferred to its own phase rather than bolted on.

A defect this evidence caught

Worth recording, because it shows why the field-level testing mattered. Punch timestamps arrive as plain strings, and a date library will happily accept a zero date (0000-00-00 00:00:00) without complaint — silently producing a timestamp nearly two thousand years in the past. Such a row would have sorted to the very top of every listing and every export.

The module now rejects any timestamp before 1970 outright, and a test pins that behaviour.

Reading the source yourself

The full design record, including the reasoning behind every decision and the alternatives rejected, is in modules/Biotime/specs/biotime-implementation-plan.md. It carries a companion status document and a clickable UI prototype.

Was this guide helpful?

Report a content problem