API

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

  1. Obtain companyId from the customer (Curio settings or registration response).
  2. Implement explicit Start / Stop — no implicit toggle.
  3. Show clear clocked-in state from isClockedIn / punch response.
  4. Send GPS when terminal supports it and profile expects it.
  5. Handle 409 conflicts gracefully (double tap, stale UI).
  6. Do not store passwords or PINs in logs.
  7. 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.