---
type: API Endpoint
title: Attendance routes
description: POST /api/attendance/event, GET /api/attendance/state, GET /api/attendance/me — the employee's own clock in/out and stats.
resource: backend/src/routes/attendance.ts
tags: [api, attendance]
timestamp: 2026-08-12T00:00:00Z
---

# Attendance routes

Mounted at `/api/attendance`. Every route requires `requireAuth` +
`requireEmployee` — admins with no employee row get `403` here, by design
(the owner isn't necessarily clocking in themselves).

# Examples

```
POST /api/attendance/event
Body: { "type": "clock_in" | "clock_out" | "break_start" | "break_end" }
201 -> { "event": AttendanceEvent, "status": LiveStatus }
400 -> invalid type
409 -> invalid transition for the current status, e.g. break_start while
       already clocked_out — { "error": string, "status": LiveStatus }
```

```
GET /api/attendance/state
200 -> { "status": "clocked_out" | "working" | "on_break" }
```

```
GET /api/attendance/me?month=YYYY-MM   (or ?from=YYYY-MM-DD&to=YYYY-MM-DD)
Defaults to the current calendar month if no query params are given
— see parseRange in backend/src/util/dateRange.ts.
200 -> {
  "range": { "from": string, "to": string },
  "sessions": Session[],
  "totalWorkedMs": number,
  "totalBreakMs": number,
  "shiftCount": number
}
```

`Session` and the state machine behind these responses are documented at
[attendance_events](/docs/data-model/attendance-events.md).

# Related

- [attendance_events](/docs/data-model/attendance-events.md)
- [Admin routes](./admin-routes.md) — same stats shape, but for any employee
- [Frontend stores](/docs/frontend/stores.md) — `attendanceStore` polls
  `GET /state` + `GET /me` every 60s and ticks a local clock every second
  between polls
