Publieke REST API

Tikklok REST API v1

Een stateless, met API-sleutel geauthenticeerde REST API voor integraties van derden. Lees de medewerkers, vestigingen en prestatiegegevens van je bedrijf. Elke respons is JSON.

Basis-URL

https://api.tikklok.appproductie
http://localhost:8084lokale ontwikkeling

Alle endpoints staan onder versie /v1. Elke aanvraag moet in productie via HTTPS gebeuren.

Authenticatie

HTTP Basic Auth via HTTPS. Stuur een Authorization-header met je sleutel-ID en secret, base64-gecodeerd:

Authorization: Basic base64(KEY_ID:SECRET)
CredentialFormaatOpmerkingen
KEY_IDTK_PUB_LIVE_… (28)Publieke identificator, hoofdletters, 16-hex staart. Veilig om te loggen.
SECRETTK_SEC_LIVE_… (60)Privé. Wordt exact één keer getoond bij aanmaak; alleen een SHA-256-hash wordt bewaard — onherstelbaar als je hem kwijt bent.

Bij een mislukte authenticatie volgt 401 unauthorized. De API maakt nooit onderscheid tussen een foute sleutel en een foute secret, om enumeratie te voorkomen.

Voorbeeld

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

Rate limits

BereikLimiet
Per sleutel60 aanvragen / minuut · 1000 / uur (vaste vensters)
Per IP, vóór auth20 aanvragen / minuut (voordat Basic Auth wordt gecontroleerd)
Per IP, mislukte auth10 mislukkingen / minuut → tijdelijke blokkade

Bij overschrijding volgt 429 rate_limited. Wacht een minuut en probeer opnieuw.

Responses & fouten

Elke respons is JSON met een success-boolean. Fouten dragen ook een korte machineleesbare error-string. Reads geven nooit success: false bij ontbrekende data — een 404-body betekent dat de resource zelf ontbrak. Elke respons draagt Cache-Control: no-store.

StatuserrorBetekenis
400invalid_roleOnbekende role-filterwaarde.
400invalid_dateOntbrekende/ongeldige datum, of min_date > max_date.
400range_too_largePrestatieperiode langer dan 366 dagen.
401unauthorizedOntbrekende of ongeldige gegevens.
402subscription_requiredBedrijf heeft geen actief (betaald) abonnement — de gratis proefperiode geeft geen API-toegang (elk endpoint behalve /v1/me).
404not_foundResource ontbreekt — of hoort bij een ander bedrijf (niet te onderscheiden, met opzet).
413payload_too_largeAanvraagbody groter dan 1 MB.
429rate_limitedRate limit overschreden.

Scope & ID's

Elke aanvraag is beperkt tot het bedrijf dat de API-sleutel bezit. Resources worden geadresseerd met hun globale ID's:

IDBetekenis
company_user_idEen persoon in je bedrijf (de „medewerker”-identiteit). Stabiel, globaal uniek.
location_idEén van de vestigingen van je bedrijf. Stabiel, globaal uniek.
De locations[] van elke medewerker bevat de location_user_id — de interne toewijzingssleutel — waarmee je klokgebeurtenissen voor die medewerker op die vestiging aanstuurt. Padparameters adresseren een toewijzing nog steeds met het paar (company_user_id, location_id). Een ID in een pad dat niet bij je bedrijf hoort, geeft 404 not_found — identiek aan een onbestaand ID, zodat vreemde ID's niet afgetast kunnen worden. Aanvraagbodies moeten ≤ 1 MB zijn (anders 413 payload_too_large).

Endpoints

De meeste endpoints zijn GET-reads. Er is één write-endpoint (POST /v1/pos/clock-events) dat uitsluitend voor Onlinefact bedoeld is. Elk endpoint behalve /v1/health vereist authenticatie.

GET/v1/healthgeen auth

Uptime-check. Geen authenticatie.

Respons 200

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

Bevestigt dat je gegevens werken en toont bij welk bedrijf ze horen.

Respons 200

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

Gepagineerde lijst van de mensen in je bedrijf (company_users), elk met een beknopte lijst van de vestigingen waaraan ze actief zijn toegewezen. Owners/admins verschijnen ook, met een lege locations-array als ze geen toewijzingen hebben.

Queryparameters

ParamStandaardOpmerkingen
limit100Begrensd tot 1–500.
offset0Voor paginering.
location_id—Alleen medewerkers die actief aan deze vestiging zijn toegewezen.
role—Bedrijfsrol: owner, admin, employee. Ongeldig → 400 invalid_role.
include_inactive01 om gedeactiveerde medewerkers mee te nemen.

Respons 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

Volledige details van één medewerker plus elke vestigingstoewijzing (actief en historisch — elk met zijn eigen is_active). Elke toewijzing bevat zijn location_user_id (de toewijzingssleutel, voor klokgebeurtenissen via de API). 404 not_found als de medewerker niet in je bedrijf zit.

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

De toewijzing van één medewerker op één vestiging. 404 not_found als de medewerker niet in je bedrijf zit, de vestiging niet in je bedrijf zit, of er geen toewijzing tussen beide bestaat.

Respons 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,
    "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

Dezelfde prestatiegegevens als het downloadbare rapport van het dashboard, als JSON, gegroepeerd per vestiging. Voor elke vestiging waaraan de medewerker is (of was) toegewezen, een opsplitsing per dag plus opgetelde totals. Alle cijfers zijn uren (2 decimalen); overtime_hours kan negatief zijn (te weinig gewerkt).

expected_hours = planned − closed − location_holiday − user_holiday − sick · overtime_hours = worked − expected

Queryparameters (beide verplicht)

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

Ongeldige/ontbrekende datum of min_date > max_date → 400 invalid_date. Een periode langer dan 366 dagen → 400 range_too_large. 404 not_found als de medewerker niet in je bedrijf zit. Een medewerker zonder data in de periode geeft 200 met een lege locations-array.

Respons 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

De vestigingen van je bedrijf. qr_base64 wordt bewust weggelaten (groot, en inklokken gebeurt aan de app-kant).

Queryparameters

ParamStandaardOpmerkingen
include_inactive01 om gedeactiveerde vestigingen mee te nemen.

Respons 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

Eén vestiging, dezelfde objectvorm als de lijst. 404 not_found als ze niet in je bedrijf zit.

GET/v1/locations/{location_id}/employeesauth

De actieve mensen die op één vestiging werken — elke actieve toewijzing (location_users-rij) gecombineerd met de naam van de persoon. Alleen rijen waar de toewijzing, de company_user én de user allemaal actief zijn, worden teruggegeven. 404 not_found als de vestiging niet bij je bedrijf hoort; een vestiging binnen je bedrijf zonder actief personeel geeft 200 met een lege data.

Respons 200

{
  "success": true,
  "location_id": 7,
  "data": [
    {
      "location_user_id": 15,
      "first_name": "Jane",
      "last_name": "Doe",
      "role": "staff",
      "function_id": 3,
      "function_name": "Sales"
    }
  ]
}
GET/v1/employees/{company_user_id}/locations/{location_id}/clock-statusauth

Huidige klokstatus van één medewerker op één vestiging, afgeleid van hun meest recente klokgebeurtenis daar. clocked_in is true als de laatste gebeurtenis een IN is, false bij een OUT of als de medewerker daar nog nooit geklokt heeft. 404 not_found als de medewerker of vestiging niet in je bedrijf zit, of als de medewerker geen toewijzing op die vestiging heeft. De last_event_*-velden zijn null als er daar nog nooit geklokt is.

Respons 200

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

Klokstatus van meerdere medewerkers op één vestiging in één aanvraag — bedoeld voor een admin-overzicht. Elke rij is de huidige status van één medewerker, afgeleid van hun laatste klokgebeurtenis op die vestiging. 404 not_found als de vestiging niet in je bedrijf zit; een vestiging zonder overeenkomende medewerkers geeft 200 met een lege data.

Queryparameters

ParamOpmerkingen
company_user_idsOptioneel. Kommagescheiden (bijv. 42,43,44), max. 500. Laat weg om elke actieve medewerker op de vestiging te krijgen. Een id dat geen actieve medewerker op deze vestiging is, wordt stil overgeslagen (een fout id laat de aanvraag niet mislukken).
Aanbevolen: stuur enkel de medewerkers die je integratie echt opvolgt — bijv. de medewerkers die in het programma van de eindgebruiker zelf ingesteld zijn — in plaats van de filter weg te laten en de hele vestiging op te halen.

Respons 200

{
  "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"
    }
  ]
}
POST/v1/pos/clock-eventsauthOnlinefact
Uitsluitend voor Onlinefact. Dit endpoint is niet beschikbaar voor andere partners of gewone API-sleutels. Het gebruikt een admin-scoped API-sleutel en is in productie IP-beperkt tot de servers van Onlinefact — aanvragen van andere adressen worden geweigerd vóór ze de API bereiken.

Registreer een klok-IN of -OUT voor een medewerker van je bedrijf — gebruikt door de kassa-integratie van Onlinefact. De server is autoritair: hij bepaalt IN/OUT op basis van de laatste gebeurtenis van de medewerker en valideert de event_type die je stuurt tegen die status (409 bij een mismatch). Gebeurtenissen krijgen de servertijd in de tijdzone van de vestiging (geen terugdatering), met source: "pos". Bij OUT wordt de bijbehorende IN tot een sessie gekoppeld en de hashketen voor manipulatiedetectie uitgebreid — identiek aan de manager-klok van het dashboard.

Request-body

VeldTypeOpmerkingen
company_user_idintDe medewerker (company_user_id). Moet een actieve medewerker in je bedrijf zijn.
location_idintDe vestiging (location_id). Moet een actieve vestiging van je bedrijf zijn, en de medewerker moet eraan toegewezen zijn.
event_typestringIN of OUT. Wordt gevalideerd tegen de serverstatus.

Respons 200 · 201

{
  "success": true,
  "clock_event_id": 90210,
  "event_type": "IN"
}

Fouten

StatuserrorBetekenis
400invalid_requestBody is geen JSON, location_user_id ontbreekt/is nul, of event_type is niet IN/OUT.
402subscription_requiredBedrijf heeft geen actief (betaald) abonnement — de gratis proefperiode geeft geen API-toegang.
404not_foundDe company_user_id of location_id zit niet in je bedrijf (vreemde ID's zijn niet te onderscheiden van ontbrekende).
422not_assignedDe medewerker en de vestiging bestaan beide in je bedrijf, maar de medewerker heeft geen actieve toewijzing op die vestiging. Er hoort een leesbare message bij.
409conflictDe serverstatus vereist het andere gebeurtenistype, of de medewerker is nog ingeklokt op een andere vestiging. Er hoort een leesbare message bij.
422location_misconfiguredDe vestiging van de medewerker heeft geen tijdzone ingesteld.

Installatie (Onlinefact)

In de plugin-config van Onlinefact moet het volgende opgeslagen worden:

Opslaan in de plugin-configWaarde
Per kassanummerlocation_id
Per kassamedewerkercompany_user_id

De API-sleutel is admin-scoped: ze kan vrij inklokken voor eender welke gebruiker. Daarom is dit endpoint enkel toegankelijk voor de servers van Onlinefact — voor de zekerheid, en omdat zinvolle rate limiting anders niet haalbaar zou zijn.

De pincode van de medewerker in de kassa zorgt ervoor dat collega's niet in elkaars naam kunnen inklokken. Eventueel kunnen we de manager toelaten om medewerkers in te klokken via functies, of via een extra pincode binnen functies.

API-sleutelbeheer

API-sleutels worden aangemaakt, opgelijst en ingetrokken vanuit het Tikklok-dashboard (Instellingen → API-sleutels, alleen owner/admin). De secret wordt één keer getoond bij aanmaak — kopieer hem meteen. Intrekken gebeurt onmiddellijk.

OpenAPI-spec

Een machineleesbare OpenAPI 3.0-beschrijving is beschikbaar om te importeren in Postman, Insomnia of generators voor client-SDK's.

openapi.yaml downloaden