Uptime probe. No authentication.
Response 200
{
"success": true,
"status": "ok",
"version": "v1",
"time": "2026-08-08T14:23:00Z"
}
Public REST API
A stateless, API-key-authenticated REST API for third-party integrations. Read your company's employees, locations, and performance data. Every response is JSON.
All endpoints are versioned under /v1. Every request must be made over HTTPS in production.
HTTP Basic Auth over HTTPS. Send an Authorization header with your key ID and secret, base64-encoded:
Authorization: Basic base64(KEY_ID:SECRET)
| Credential | Format | Notes |
|---|---|---|
KEY_ID | TK_PUB_LIVE_… (28) | Public identifier, uppercase, 16-hex tail. Safe to log. |
SECRET | TK_SEC_LIVE_… (60) | Private. Shown exactly once at creation; only a SHA-256 hash is stored — unrecoverable if lost. |
Auth failure returns 401 unauthorized. The API never distinguishes a bad key from a bad secret, to prevent enumeration.
curl -u TK_PUB_LIVE_A1B2C3D4E5F60718:TK_SEC_LIVE_… \
https://api.tikklok.app/v1/me
| Scope | Limit |
|---|---|
| Per key | 60 requests / minute · 1000 / hour (fixed windows) |
| Per IP, pre-auth | 20 requests / minute (before Basic Auth is checked) |
| Per IP, auth failures | 10 failures / minute → temporary lockout |
Exceeding a limit returns 429 rate_limited. Wait a minute and retry.
Every response is JSON with a success boolean. Errors also carry a short machine-readable error string. Reads never emit success: false for missing data — a 404 body means the resource itself was missing. Every response carries Cache-Control: no-store.
| Status | error | Meaning |
|---|---|---|
| 400 | invalid_role | Unknown role filter value. |
| 400 | invalid_date | Missing/malformed date, or min_date > max_date. |
| 400 | range_too_large | Performance span exceeds 366 days. |
| 401 | unauthorized | Missing or invalid credentials. |
| 402 | subscription_required | Company has no active (paid) subscription — the free trial does not include API access (every endpoint except /v1/me). |
| 404 | not_found | Resource missing — or belongs to another company (indistinguishable, by design). |
| 413 | payload_too_large | Request body over 1 MB. |
| 429 | rate_limited | Rate limit exceeded. |
Every request is scoped to the company that owns the API key. Resources are addressed by their global IDs:
| ID | Meaning |
|---|---|
company_user_id | A person in your company (the "employee" identity). Stable, globally unique. |
location_id | One of your company's locations. Stable, globally unique. |
locations[] includes its location_user_id — the internal per-assignment key — used to target clock events for that employee at that location. Path parameters still address an assignment by the (company_user_id, location_id) pair. Any ID in a path that doesn't belong to your company returns 404 not_found — identical to a non-existent ID, so foreign IDs can't be probed. Request bodies must be ≤ 1 MB (413 payload_too_large otherwise).All endpoints are GET. Every endpoint except /v1/health requires authentication.
Uptime probe. No authentication.
{
"success": true,
"status": "ok",
"version": "v1",
"time": "2026-08-08T14:23:00Z"
}
Confirms your credentials work and shows which company they map to.
{
"success": true,
"company_id": 1,
"company_name": "Acme Corp",
"key_id": "TK_PUB_LIVE_A1B2C3D4E5F60718",
"key_name": "My Slack integration"
}
Paginated roster of your company's people (company_users), each with a lean list of the locations they're actively assigned to. Owners/admins appear too, with an empty locations array if they have no assignments.
| Param | Default | Notes |
|---|---|---|
limit | 100 | Clamped to 1–500. |
offset | 0 | For paging. |
location_id | — | Only employees actively assigned to this location. |
role | — | Company role: owner, admin, employee. Invalid → 400 invalid_role. |
include_inactive | 0 | 1 to include deactivated employees. |
{
"success": true,
"total": 25,
"limit": 100,
"offset": 0,
"data": [
{
"company_user_id": 42,
"first_name": "Jane",
"last_name": "Doe",
"is_active": true,
"locations": [
{ "location_user_id": 15, "location_id": 7, "location_name": "Main Store" }
]
}
]
}
Full detail for one employee plus every location assignment (active and historical — each carries its own is_active). Each assignment includes its location_user_id (the per-assignment key, used for API clock events). 404 not_found if the employee isn't in your company.
{
"success": true,
"data": {
"company_user_id": 42,
"first_name": "Jane",
"last_name": "Doe",
"email": "jane@example.com",
"tel": "+32 470 00 00 00",
"role": "employee",
"status": "active",
"is_active": true,
"created": "2025-01-15 10:30:00",
"updated": "2025-08-27 14:22:15",
"locations": [
{
"location_user_id": 15,
"location_id": 7,
"location_name": "Main Store",
"role": "staff",
"function_id": 3,
"function_name": "Sales",
"is_user_primary": true,
"clocking_allowed": true,
"valid_from": null,
"valid_to": null,
"is_active": true
}
]
}
}
One employee's assignment at one location. 404 not_found if the employee isn't in your company, the location isn't in your company, or there's no assignment linking the two.
{
"success": true,
"data": {
"company_user_id": 42,
"location_user_id": 15,
"location_id": 7,
"location_name": "Main Store",
"role": "staff",
"function_id": 3,
"function_name": "Sales",
"is_user_primary": true,
"clocking_allowed": true,
"valid_from": null,
"valid_to": null,
"is_active": true,
"created": "2025-01-15 10:30:00",
"updated": "2025-08-27 14:22:15"
}
}
The same performance data as the dashboard's downloadable report, as JSON, grouped by location. For each location the employee is (or was) assigned to, a per-day breakdown plus summed totals. All figures are hours (2 decimals); overtime_hours can be negative (undertime).
expected_hours = planned − closed − location_holiday − user_holiday − sick · overtime_hours = worked − expected
| Param | Notes |
|---|---|
min_date | YYYY-MM-DD |
max_date | YYYY-MM-DD, ≥ min_date |
Bad/missing date or min_date > max_date → 400 invalid_date. A span over 366 days → 400 range_too_large. 404 not_found if the employee isn't in your company. An employee with no data in range returns 200 with an empty locations array.
{
"success": true,
"data": {
"company_user_id": 42,
"employee_name": "Jane Doe",
"period": { "min_date": "2025-01-01", "max_date": "2025-01-31" },
"locations": [
{
"location_id": 7,
"location_name": "Main Store",
"days": [
{
"date": "2025-01-06",
"planned_hours": 8,
"worked_hours": 7.75,
"closed_hours": 0,
"location_holiday_hours": 0,
"user_holiday_hours": 0,
"sick_hours": 0,
"expected_hours": 8,
"overtime_hours": -0.25
}
],
"totals": {
"planned_hours": 40, "worked_hours": 39, "closed_hours": 0,
"location_holiday_hours": 0, "user_holiday_hours": 0, "sick_hours": 0,
"expected_hours": 40, "overtime_hours": -1
}
}
]
}
}
Your company's locations. qr_base64 is intentionally omitted (large, and clocking is handled app-side).
| Param | Default | Notes |
|---|---|---|
include_inactive | 0 | 1 to include deactivated locations. |
{
"success": true,
"data": [
{
"location_id": 7,
"name": "Main Store",
"street": "Rue de la Paix",
"house_nr": "42",
"postal_code": "1000",
"city": "Brussels",
"country": "BE",
"timezone": "Europe/Brussels",
"is_company_primary": true,
"latitude": 50.8503,
"longitude": 4.3517,
"geo_radius_meters": 100,
"is_active": true,
"created": "2024-06-01 09:00:00",
"updated": "2025-08-27 14:22:15"
}
]
}
One location, same object shape as the list. 404 not_found if it isn't in your company.
The active people working at one location — each active assignment (location_users row) joined with the person's name. Only rows where the assignment, its company_user, and the user are all active are returned. 404 not_found if the location isn't in your company; an in-company location with no active staff returns 200 with an empty data.
{
"success": true,
"location_id": 7,
"data": [
{
"location_user_id": 15,
"first_name": "Jane",
"last_name": "Doe",
"role": "staff",
"function_id": 3,
"function_name": "Sales"
}
]
}
Current clock status for one employee at one location, resolved from their most recent clock event there. clocked_in is true when the latest event is an IN, false when it's an OUT or the employee has never clocked there. 404 not_found if the employee or location isn't in your company, or the employee has no assignment at that location. The last_event_* fields are null if they've never clocked at that location.
{
"success": true,
"data": {
"company_user_id": 42,
"location_id": 7,
"location_user_id": 15,
"clocked_in": true,
"last_event_type": "IN",
"last_event_time_utc": "2026-09-03 08:00:00",
"last_event_time_local": "2026-09-03 10:00:00"
}
}
Clock status for multiple employees at one location in a single call — built for an admin overview. Each row is one employee's current status, resolved from their latest clock event at that location. 404 not_found if the location isn't in your company; a location with no matching staff returns 200 with an empty data.
| Param | Notes |
|---|---|
company_user_ids | Optional. Comma-separated (e.g. 42,43,44), max 500. Omit to get every active employee at the location. Any id that isn't an active employee at this location is silently skipped (a bad id doesn't fail the request). |
{
"success": true,
"location_id": 7,
"data": [
{
"company_user_id": 42,
"location_user_id": 15,
"clocked_in": true,
"last_event_type": "IN",
"last_event_time_utc": "2026-09-03 08:00:00",
"last_event_time_local": "2026-09-03 10:00:00"
}
]
}
API keys are created, listed, and revoked from the Tikklok dashboard (Settings → API keys, owner/admin only). The secret is shown once at creation — copy it immediately. Revoking a key is immediate.
A machine-readable OpenAPI 3.0 description is available for import into Postman, Insomnia, or client-SDK generators.