---
seo:
  description: Time-tracking sessions, logs, and timestamp templates.
sidebar:
  label: Overview
title: Fuxam API v4
---
Time-tracking sessions, logs, and timestamp templates.

Version 4.3.0

Base URL: `https://fuxam.app/api/v4`, `https://dev.fuxam.app/api/v4`

## Time tracking

Institution Zeiterfassung for partner terminals (ananas.codes / Datafox) and HR integrations. **Auth:** HTTP Basic (`clientId:clientSecret`). **Feature gate:** **403** when time tracking is disabled for the institution.

**Identify a member** with exactly one of `userId`, `email`, or membership `code` (badge). Sending none or more than one is **400**. Whitespace-only `code` (for example `" "`) is **422**. An unknown code is **404**. This is punch identity only — there is no user provisioning on this API.

**Numeric keypad codes (Datafox and similar):** store the membership code in Fuxam with a fixed prefix, for example `Datafox-1234`. The terminal adds `Datafox-` when it sends `code` and strips that prefix when it displays the number. Lookup is the stored code after trim (the column collation is case-insensitive). This API does not add or strip a prefix.

The institution setting `user_code_format` (Dashboard → Settings → User management, `/{lang}/dashboard/settings/user-management`) can require that whole-code shape on **writes**. It is literal text plus at least four bracket tokens (`[0]` is one digit). `Datafox-[0][0][0][0]` matches `Datafox-1234`. Literals are case-sensitive. A prefix alone is not a valid format: `Datafox-` is rejected (`NO_DYNAMIC_TOKEN`) and `Datafox-[0]` is rejected (`MIN_BLOCKS_NOT_MET`, fewer than four tokens). The format describes the entire code, not "prefix plus anything". Time-tracking reads do not re-check the format.

**Typical punch sequence:**
1. `GET /time-tracking/timestamp-templates` — optional; cache category IDs
2. `POST /time-tracking/sessions/start` — clock in
3. Optional `POST /time-tracking/sessions/pause` then `POST /time-tracking/sessions/resume`
4. Optional `POST /time-tracking/timestamps` — category on today's session (`templateId` and/or `label`)
5. `POST /time-tracking/sessions/stop` — clock out

**Offline catch-up:** the supported way to replay an offline day is `POST /time-tracking/logs/bulk` with complete `startTime` + `endTime` pairs (optional pauses). `POST /time-tracking/sessions/start` and `stop` with `at` are for near-real-time punches. An open session that started on an earlier institution-local day than the punch is auto-closed at the end of that start day (23:59:59.999 in the institution timezone). Do not rebuild a past day with start/stop. Edit a stored log with `PATCH /time-tracking/logs/{id}`.

**Already running:** if any open session conflicts with the punch, `POST /time-tracking/sessions/start` returns **409** and writes nothing. Conflict means the open session started on the punch's local day or later. The body includes `activeSession.id` and `activeSession.startTime`. Stop that session, then retry. Start also returns **409** `conflictingLog` when the open interval `[punch, ∞)` overlaps another log (the same half-open rule as `POST /time-tracking/logs`). Stop returns **409** `conflictingLog` when `[sessionStart, stop)` overlaps another log; the session being stopped is excluded, and a stop that only touches the next log startTime is accepted. For `conflictingLog`, adjust or delete the existing log via the UI or PATCH, or resend non-overlapping times. Every non-conflicting leftover from an earlier local day is closed at its own end-of-day before the new session is created. `closedActiveSession` in the **201** body is the earliest of those closes (the web UI still closes only one `findFirst` session and can auto-close a same-day session).

**Idempotency:** a completed success is replayed when the same `Idempotency-Key` and body are sent again. A **409** or **503** does not store a response; the key is released. After **409**, stop the open session and retry start with the same key. After **503**, retry the same request.

**Overlap:** `POST /time-tracking/logs` and each item of `POST /time-tracking/logs/bulk` reject an interval that overlaps an existing log or open session for that member (**409**, `conflictingLog` with `id`, `startTime`, and `endTime`). Intervals are half-open `[start, end)` on absolute instants, so ending exactly when the next log starts — including a local midnight boundary — is not an overlap. An open session (`endTime` null) occupies `[start, ∞)` and conflicts with any interval that ends after that start, including later days. Close it before importing those days. Bulk rejection is per item, same as other v4 batches: HTTP **200**, failed items are listed, earlier items in the request stay created, and a later item that overlaps an earlier one in the same batch fails with that item as `conflictingLog`. `PATCH /time-tracking/logs/{id}` uses the same half-open overlap check, excludes the log being edited, and returns **409** `conflictingLog`. The Fuxam UI editor does not.

**Timezone:** day boundaries and auto-close use `time_tracking_timezone` (Dashboard → Settings → Time tracking, `/{lang}/dashboard/settings/time-tracking`). Set it, for example `Europe/Berlin`. When it is unset, these API routes do not pass a timezone and a terminal request has no browser timezone cookie, so resolution falls through to **UTC**. An auto-close at 23:59:59.999 UTC then displays as 01:59 in Berlin during CEST (UTC+2). That fallback is unchanged.

**Delete:** not supported on this API. Admins delete logs in the Fuxam UI.

Mutating session punches and log creates require `Idempotency-Key`. Optional `at` is ISO-8601 **with timezone offset** for near-real-time punches; omit it to use server now. `GET /time-tracking/sessions/active` returns `{ "data": null }` when the member is not clocked in. List logs is per member (not institution-wide).

- [`POST /time-tracking/sessions/start`](/docs/api/time-tracking/time-tracking/start-time-tracking-session) — Start time tracking.
- [`POST /time-tracking/sessions/stop`](/docs/api/time-tracking/time-tracking/stop-time-tracking-session) — Stop time tracking.
- [`POST /time-tracking/sessions/pause`](/docs/api/time-tracking/time-tracking/pause-time-tracking-session) — Pause time tracking.
- [`POST /time-tracking/sessions/resume`](/docs/api/time-tracking/time-tracking/resume-time-tracking-pause) — Resume time tracking.
- [`GET /time-tracking/sessions/active`](/docs/api/time-tracking/time-tracking/get-active-time-tracking-session) — Get the active time tracking session.
- [`POST /time-tracking/timestamps`](/docs/api/time-tracking/time-tracking/add-time-tracking-timestamp) — Add a category timestamp to today's session.
- [`GET /time-tracking/timestamp-templates`](/docs/api/time-tracking/time-tracking/list-time-tracking-timestamp-templates) — List timestamp templates.
- [`GET /time-tracking/logs`](/docs/api/time-tracking/time-tracking/list-time-tracking-logs) — List time tracking logs.
- [`POST /time-tracking/logs`](/docs/api/time-tracking/time-tracking/create-time-tracking-log) — Create a time tracking log.
- [`POST /time-tracking/logs/bulk`](/docs/api/time-tracking/time-tracking/bulk-create-time-tracking-logs) — Bulk import time tracking logs.
- [`GET /time-tracking/logs/{id}`](/docs/api/time-tracking/time-tracking/get-time-tracking-log) — Get a time tracking log.
- [`PATCH /time-tracking/logs/{id}`](/docs/api/time-tracking/time-tracking/update-time-tracking-log) — Edit a time tracking log.
