zkbiotime-sdk
v0.1.1
Published
Typed, isomorphic TypeScript client for the ZKBioTime / BioTime 8 REST API (personnel, iclock devices, attendance).
Downloads
50
Maintainers
Readme
zkbiotime-sdk
Typed, isomorphic TypeScript client for the ZKBioTime / BioTime 8 REST API — personnel, iClock devices, and attendance reports.
- ✅ Fully typed and runtime-validated — model types are inferred from Zod schemas, and every response is validated at runtime (opt out with
validate: false). - 🔁 Pagination built in —
list,listAll, and async-iterablepaginate. - 🔐 HTTP Basic auth — the scheme that passes BioTime's
IsNotOpenAPIlicense gate on trial and full licenses. - 🌐 Isomorphic — runs on Node 18+ and modern browsers (uses global
fetch); one dependency (zod). - 🧯 Helpful errors —
ZKBioTimeError(status + body + license-gate flag) andZKBioTimeValidationError(Zod issues + the raw data).
npm install zkbiotime-sdkQuick start
import { ZKBioTimeClient } from "zkbiotime-sdk";
const zk = new ZKBioTimeClient({
baseUrl: "http://10.10.10.218", // your BioTime server
username: "admin",
password: "••••••",
});
// List (one page) — fully typed
const { count, data } = await zk.employees.list({ page_size: 50, search: "somchai" });
// Create — only emp_code, department and area are required
const emp = await zk.employees.create({
emp_code: "1001",
department: 1,
area: 1,
first_name: "Somchai",
mobile: "0812345678",
});
// Update (PATCH by default), then delete
await zk.employees.update(emp.id, { position: 2 });
await zk.employees.remove(emp.id);
// Iterate every punch across all pages, no manual paging
for await (const punch of zk.transactions.paginate({ start_time: "2026-06-01 00:00:00" })) {
console.log(punch.emp_code, punch.punch_time);
}Resources
| Namespace | Endpoint | Operations |
|---|---|---|
| zk.employees | /personnel/api/employees/ | CRUD + adjustArea, adjustDepartment, adjustPosition, adjustResign, resyncToDevice, delBioTemplate |
| zk.departments | /personnel/api/departments/ | CRUD |
| zk.areas | /personnel/api/areas/ | CRUD |
| zk.positions | /personnel/api/positions/ | CRUD |
| zk.resigns | /personnel/api/resigns/ | CRUD |
| zk.terminals | /iclock/api/terminals/ | CRUD + reboot, uploadAll, clearCommand |
| zk.transactions | /iclock/api/transactions/ | list / get / delete (raw punches aren't writable) |
| zk.reports | /att/api/<report>/ | get(name, params), export(name, params) — name autocompletes |
Every namespace exposes list / listAll / paginate (where applicable):
const allDepts = await zk.departments.listAll(); // every page → array
const firstPage = await zk.departments.list({ page: 1 }); // raw { count, next, previous, data }Reports
zk.reports.get() autocompletes the ~40 known report names (and still accepts any string):
const report = await zk.reports.get("monthlyPunchReport", {
start_date: "2026-06-01",
end_date: "2026-06-30",
departments: "1,2",
});Errors
Any non-2xx response throws ZKBioTimeError:
import { ZKBioTimeError } from "zkbiotime-sdk";
try {
await zk.employees.list();
} catch (err) {
if (err instanceof ZKBioTimeError) {
console.error(err.status, err.body);
if (err.isLicenseGate) {
// 403 on /personnel/ or /iclock/: the IsNotOpenAPI gate.
// Use Basic auth (this SDK does) or enable the `api` license mod.
}
}
}Runtime validation
Types alone are a compile-time promise; this SDK also validates every response at runtime against Zod schemas, so the data you receive really matches its type. The schemas are the single source of truth — the exported model types are z.inferred from them.
import { ZKBioTimeValidationError } from "zkbiotime-sdk";
try {
const emp = await zk.employees.get(123); // parsed + validated
} catch (err) {
if (err instanceof ZKBioTimeValidationError) {
console.error(err.path, err.issues); // Zod issues + err.data (the raw response)
}
}Schemas are deliberately lenient — unknown/forward-compatible fields pass through (typed unknown), and nullable fields are tolerated — so a server adding a column won't break you. Need maximum throughput or hitting a schema edge case? Turn it off:
const zk = new ZKBioTimeClient({ baseUrl, username, password, validate: false });The schemas (EmployeeSchema, DepartmentSchema, …) and paginatedSchema() are exported if you want to validate elsewhere.
Authentication & the license gate
BioTime 8's iclock and personnel viewsets enforce an IsNotOpenAPI permission: requests carrying a JWT or DRF token get 403 unless the license includes the api mod (trial licenses don't). HTTP Basic auth leaves request.auth = None and passes the gate — so this SDK uses Basic auth by default. /att/ report endpoints have no gate.
Generic escape hatch
Anything not wrapped is one call away:
// Raw typed request
const data = await zk.request<{ count: number }>("GET", "/personnel/api/employees/", {
query: { page_size: 1 },
});
// Schema probe: POST {} returns the required-field list and writes nothing
await zk.request("POST", "/personnel/api/employees/", { body: {} }).catch((e) => e.body);Custom fetch
Pass your own fetch (e.g. with a self-signed-TLS agent on the LAN, or to route through a proxy):
const zk = new ZKBioTimeClient({ baseUrl, username, password, fetch: myFetch });License
MIT
