POS & punch clock
Clock employees in and out from POS systems, shared terminals and registered devices.
Partner POS systems, time clocks, and shared terminals can clock employees directly in and out of Curio Time without using the mobile app or JWT employee sessions.
Two supported flows:
| Flow | Best for | Auth |
|---|---|---|
| Option A — Shared terminal | Web punch page, generic hardware with password keypad | Anonymous + employee password per punch |
| Option B — Registered kiosk | Dedicated tablets, CurioStation-class devices | Device token + 4-digit PIN |
Both write to the same TimeEntry records used by payroll. After punch, HR integrations read data via Time entries.
Option A: Shared punch terminal (password)
Same API as Curio’s web punch clock (/punch-clock). No JWT.
1. List employees for the terminal UI
GET /api/v1/users/punch-list?companyId={companyId}
No Authorization header.
Response: ApiResponse<PunchListUserDto[]>
{
"success": true,
"data": [
{
"id": "…",
"name": "Jane Doe",
"departmentIds": ["…"],
"isClockedIn": false,
"openEntryStartAtUtc": null,
"requiresProjectSelection": false,
"requireGpsOnClockIn": false
}
],
"errors": null
}
| Field | Description |
|---|---|
isClockedIn |
Employee has an open time entry |
openEntryStartAtUtc |
Start of open punch (UTC) |
requiresProjectSelection |
Clock-in blocked until project/location selected (strict mode) |
requireGpsOnClockIn |
GPS should be sent on punch (optional; does not block) |
2. Optional — departments filter
GET /api/v1/departments?companyId={companyId}
3. Optional — who is clocked in now
GET /api/v1/users/active-status?companyId={companyId}
4. Clock in or out
POST /api/v1/time-entries/punch
Content-Type: application/json
Rate limited. No JWT.
Request:
{
"companyId": "…",
"userId": "…",
"password": "employee-web-password",
"action": "Start",
"lat": 64.1466,
"lng": -21.9426
}
| Field | Required | Description |
|---|---|---|
companyId |
Yes | Tenant GUID |
userId |
Yes | Employee GUID from punch list |
password |
Yes | Employee’s web login password (verified server-side) |
action |
Yes | "Start" = clock in, "Stop" = clock out |
lat, lng |
No | WGS84 GPS on punch |
userLat, userLon |
No | Alternative GPS fields (take precedence if both set) |
Response: ApiResponse<PunchResponse>
{
"success": true,
"data": {
"message": "Clocked in.",
"isClockedIn": true,
"timeEntryId": "…",
"punchedAtUtc": "2026-08-25T08:00:00Z",
"punchPhotoUploadGrant": null,
"punchPhotoUploadGrantExpiresAtUtc": null
},
"errors": null
}
| Field | Description |
|---|---|
isClockedIn |
State after this punch |
timeEntryId |
Created or closed entry |
punchedAtUtc |
UTC instant of the action |
punchPhotoUploadGrant |
Short-lived token if company requires punch photos (optional) |
Errors
| Situation | Typical result |
|---|---|
| Wrong password | 401 / error in envelope |
Already clocked in on Start |
409 |
Not clocked in on Stop |
409 |
| Project required but missing | 400 with message |
| IP not allowed (company rule) | 403 |
Option B: Registered kiosk device (PIN)
For hardware that stays logged in as a device and identifies employees with a 4-digit kiosk PIN (configured on each employee in Curio Time).
1. Register device (once per terminal)
Owner or manager credentials required once:
POST /api/v1/kiosk/register-device
Content-Type: application/json
{
"email": "admin@company.example",
"password": "…"
}
Response:
{
"success": true,
"data": {
"deviceToken": "…",
"companyId": "…",
"companyName": "Example Ltd"
},
"errors": null
}
Store deviceToken securely on the device. Send it on all subsequent kiosk calls:
Authorization: Bearer {deviceToken}
2. Authenticate employee with PIN
POST /api/v1/kiosk/auth-pin
Authorization: Bearer {deviceToken}
Content-Type: application/json
{
"employeeId": "…",
"pin": "1234"
}
(userId is accepted as alias for employeeId.)
Response:
{
"success": true,
"data": {
"userId": "…",
"displayName": "Jane Doe",
"isClockedIn": false,
"employeeToken": "…",
"expiresAtUtc": "2026-08-25T09:00:00Z",
"canEditOwnTimesheet": true,
"canEditClockInOut": true,
"enablePunchPhoto": false,
"requirePunchPhoto": false
},
"errors": null
}
Use employeeToken on punch requests:
X-Employee-Token: {employeeToken}
Session is short-lived — re-authenticate with PIN when expired.
3. Clock in or out
POST /api/v1/kiosk/punch
Authorization: Bearer {deviceToken}
X-Employee-Token: {employeeToken}
Content-Type: application/json
{
"employeeId": "…",
"action": "Start",
"lat": 64.1466,
"lng": -21.9426
}
| Field | Required | Description |
|---|---|---|
employeeId |
Yes | Must match signed-in employee |
action |
Yes | "Start" or "Stop" |
lat, lng |
No | Optional GPS |
Response: same PunchResponse shape as Option A.
4. Employee list (optional)
For PIN entry UI you can still use:
GET /api/v1/users/punch-list?companyId={companyId}
Or show numeric keypad only (employee selects themselves then enters PIN).
Choosing Option A vs B
| Option A (password) | Option B (device + PIN) | |
|---|---|---|
| Setup | Only need companyId |
One-time device registration |
| Employee auth | Web password each punch | 4-digit PIN |
| Best for | Browser terminal, existing password UX | Fixed tablet, faster repeat punches |
| Self-service after punch | No | Possible via /kiosk/me/* (CurioStation only — not required for punch-only partners) |
Partner checklist
- Obtain
companyIdfrom the customer (Curio settings or registration response). - Implement explicit
Start/Stop— no implicit toggle. - Show clear clocked-in state from
isClockedIn/ punch response. - Send GPS when terminal supports it and profile expects it.
- Handle
409conflicts gracefully (double tap, stale UI). - Do not store passwords or PINs in logs.
- Read worked time for payroll via JWT integration API — Time entries.
Not exposed to punch partners (v1)
| Endpoint | Reason |
|---|---|
/kiosk/me/time-entries/save |
Full timesheet edit — CurioStation self-service |
/kiosk/me/schedule/* |
Schedule apps on device |
JWT time-entries/save |
Mobile app path — different enforcement rules |
Contact Curio Time if you need expanded kiosk self-service beyond clock in/out.