REST API
Klokk ships a read-only public REST API under /api/v1/* (/status, /users, /clock/states, /time/entries, /time/leaves, /companies). Requests are authenticated with an API key and authorised per scope (e.g. read:users, read:time_entries), and rate-limited to 60 requests per minute per IP.and rate-limited per key or personal link (RATE_API_PER_MINUTE). Reading is the default; writing for people is switched on separately (below).
Approved absences — vacation, sickness, home office and every other kind — come from GET /api/v1/time/leaves?from=YYYY-MM-DD&to=YYYY-MM-DD (both required, at most 366 days) with the scope read:leave_entries: per absence the person, company, kind, period, days and comment.
Acting for people: clocking and absences
Section titled “Acting for people: clocking and absences”Extensions can clock people in and out, record breaks, and request, change and cancel absences — under the same rules as in Klokk (closed months, last day of employment, approval). Everything is off by default. Super admins switch it on under API & Extensions → Access for people: Extensions may clock and manage absences and Personal links. Switching off ends every personal link and removes the write scopes from every key.
An extension acts for people in one of two ways, set per extension:
- Personal link — a person links the app (for example the company app) to their own account on a consent page. The app then acts only for that person, only with the permissions the person agreed to and the extension’s key still holds:
read:time_entries,read:leave_entries,read:clock_states,write:clock,write:absences. - Act for everyone in the company — the key of an extension bound to one company writes for every person of that company, named by
user_id. Meant for trusted central systems. With Absences count as approved the extension is the leading system: what it creates is approved, and it may change and cancel approved absences.
| Endpoint | Scope | Description |
|---|---|---|
POST /api/v1/clock/in, /clock/out, /clock/break/start, /clock/break/end |
write:clock | Body {"user_id": 7} with a company key, empty with a personal link. Always at the server’s time. |
POST /api/v1/absences |
write:absences | type, start_date, end_date, optional half_day (morning/afternoon), comment, external_id. A request follows the person’s approval rules. |
PATCH / DELETE /api/v1/absences/{id} |
write:absences | Only absences the extension created. A waiting request is changed or withdrawn; for an approved absence DELETE asks to cancel it (202). |
GET /api/v1/me |
— | The person a personal link acts for, with the absence types their company offers (absence_types). |
GET /api/v1/time/leaves?updated_since=… |
read:leave_entries |
Absences created or changed since then, in every state; limit/cursor page, next_updated_since is the value for the next sync. |
GET /api/v1/time/leaves/removed?since=… |
read:leave_entries |
Absences deleted since then — also after a person’s data was deleted (kept 90 days). |
external_id is the extension’s own ID for an absence: sent again with the same data, nothing is created twice (200); with other data the answer is 409 with the existing id. Errors carry error (text) and code (for programs, e.g. feature_disabled, month_closed, already_clocked_in, token_expired). GET /api/v1/status lists the version and the features this installation offers.
Linking an app (OAuth 2.0 authorization code with PKCE, S256 only):
- The super admin ticks Let people link this extension, registers its redirect addresses (exact match; https, http only for localhost, or an app scheme such as
com.example.app:/callback) and the link lifetime (30–365 days). The page shows the client ID. - The app sends the person to
/oauth/authorize?response_type=code&client_id=…&redirect_uri=…&code_challenge=…&code_challenge_method=S256&state=…(optionallyscope). The person signs in, with the second factor, and allows it. - Klokk returns to the redirect address with
code(valid one minute, once) andstate. - The app’s server posts to
/oauth/token(HTTP Basic: client ID and the extension’s key) withgrant_type=authorization_code,code,redirect_uriandcode_verifier, and receives an access token (one hour) and a refresh token. grant_type=refresh_tokenissues new tokens. A refresh token works once; presenting a used one again ends the link.- The app ends a link with
/oauth/revoke; the person under Profile → Linked apps. After the lifetime the person consents again.
Every write is in the audit log with the extension, and the person sees entries an app made in their time overview. Call the API from the app’s server, not from a browser: tokens do not belong in web pages (CORS allows GET only). Requests are limited per key or link (RATE_API_PER_MINUTE), so a company server may stamp for many people; failed authentications are limited to 30 per minute per address.
Super-admins manage integrations under API & Extensions (/admin/extensions). Each integration bundles an API key, its scopes, and one connection mode:
- API only — the extension calls the REST API above using its key.
- Webhooks — Klokk sends outbound HTTP POSTs on clock-in, clock-out, and leave events.
- WebSocket — the extension opens a persistent inbound connection at
GET /api/v1/extension/connect.
Set EXTENSIONS_ENABLED=false to switch off the extension subsystem at the network level. The read-only REST API endpoints listed above remain available; only the webhook/WebSocket extension features below are disabled. Writing for people and personal links are not available then either.
| Variable | Default | Description |
|---|---|---|
EXTENSIONS_ENABLED |
true | Enable the extension API subsystem. When set to false: /admin/extensions and all sub-routes return 404 /api/v1/extension/connect WebSocket endpoint is not registered Outbound webhook dispatch is skipped entirely No ExtensionHub goroutines are started |
WEBHOOK_ALLOW_PRIVATE_TARGETS |
false | SSRF protection for outbound webhooks. By default, webhook URLs that resolve to loopback, private or link-local addresses are refused (including via redirects and DNS rebinding). Set to true only if you deliberately deliver webhooks to an endpoint on your internal network. |