Public REST API

Tikklok REST API v1

A stateless, API-key-authenticated REST API for third-party integrations. Read your company's employees, locations, and performance data. Every response is JSON.

Base URL

https://api.tikklok.appproduction
http://localhost:8084local development

All endpoints are versioned under /v1. Every request must be made over HTTPS in production.

Authentication

HTTP Basic Auth over HTTPS. Send an Authorization header with your key ID and secret, base64-encoded:

Authorization: Basic base64(KEY_ID:SECRET)
CredentialFormatNotes
KEY_IDTK_PUB_LIVE_… (28)Public identifier, uppercase, 16-hex tail. Safe to log.
SECRETTK_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.

Example

curl -u TK_PUB_LIVE_A1B2C3D4E5F60718:TK_SEC_LIVE_… \
     https://api.tikklok.app/v1/me

Rate limits

ScopeLimit
Per key60 requests / minute · 1000 / hour (fixed windows)
Per IP, pre-auth20 requests / minute (before Basic Auth is checked)
Per IP, auth failures10 failures / minute → temporary lockout

Exceeding a limit returns 429 rate_limited. Wait a minute and retry.

Responses & errors

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.

StatuserrorMeaning
400invalid_roleUnknown role filter value.
400invalid_dateMissing/malformed date, or min_date > max_date.
400range_too_largePerformance span exceeds 366 days.
401unauthorizedMissing or invalid credentials.
404not_foundResource missing — or belongs to another company (indistinguishable, by design).
413payload_too_largeRequest body over 1 MB.
429rate_limitedRate limit exceeded.

Scoping & IDs

Every request is scoped to the company that owns the API key. Resources are addressed by their global IDs:

IDMeaning
company_user_idA person in your company (the "employee" identity). Stable, globally unique.
location_idOne of your company's locations. Stable, globally unique.
Each employee's 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).

Endpoints

All endpoints are GET. Every endpoint except /v1/health requires authentication.

GET/v1/healthno auth

Uptime probe. No authentication.

Response 200

{
  "success": true,
  "status": "ok",
  "version": "v1",
  "time": "2026-08-08T14:23:00Z"
}
GET/v1/meauth

Confirms your credentials work and shows which company they map to.

Response 200

{
  "success": true,
  "company_id": 1,
  "company_name": "Acme Corp",
  "key_id": "TK_PUB_LIVE_A1B2C3D4E5F60718",
  "key_name": "My Slack integration"
}
GET/v1/employeesauth

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.

Query parameters

ParamDefaultNotes
limit100Clamped to 1–500.
offset0For paging.
location_idOnly employees actively assigned to this location.
roleCompany role: owner, admin, employee. Invalid → 400 invalid_role.
include_inactive01 to include deactivated employees.

Response 200

{
  "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" }
      ]
    }
  ]
}
GET/v1/employees/{company_user_id}auth

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.

Response 200

{
  "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",
    "contract": "fixed_schedule",
    "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,
        "overtime": true,
        "valid_from": null,
        "valid_to": null,
        "is_active": true
      }
    ]
  }
}
GET/v1/employees/{company_user_id}/locations/{location_id}auth

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.

Response 200

{
  "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,
    "overtime": true,
    "valid_from": null,
    "valid_to": null,
    "is_active": true,
    "created": "2025-01-15 10:30:00",
    "updated": "2025-08-27 14:22:15"
  }
}
GET/v1/employees/{company_user_id}/performanceauth

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

Query parameters (both required)

ParamNotes
min_dateYYYY-MM-DD
max_dateYYYY-MM-DD, ≥ min_date

Bad/missing date or min_date > max_date400 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.

Response 200

{
  "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
        }
      }
    ]
  }
}
GET/v1/locationsauth

Your company's locations. qr_base64 is intentionally omitted (large, and clocking is handled app-side).

Query parameters

ParamDefaultNotes
include_inactive01 to include deactivated locations.

Response 200

{
  "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"
    }
  ]
}
GET/v1/locations/{location_id}auth

One location, same object shape as the list. 404 not_found if it isn't in your company.

API key management

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.

OpenAPI spec

A machine-readable OpenAPI 3.0 description is available for import into Postman, Insomnia, or client-SDK generators.

Download openapi.yaml