API-conventies
Regels die voor alle resources van v1 gelden.
| Onderwerp | Regel |
|---|---|
| Formaat | JSON UTF-8. Requests met een body sturen Content-Type: application/json mee. |
| IDs | Publieke UUID’s. Interne numerieke IDs worden nooit blootgesteld. |
| Datums | ISO 8601. Tijdstippen in UTC (2026-07-08T09:30:00Z); kalenderdatums als YYYY-MM-DD. |
| Base URL | Versie in het pad: https://api.kinmu.app/v1 · Dev: https://api.dev.kinmu.app/v1. |
Paginering (cursor)
Alle lijst-endpoints pagineren met een cursor:
?limit=(default 25, max 100) ·?cursor=<opaak>.- Response:
{ "data": [...], "meta": { "next_cursor": "…"|null, "has_more": true|false } }. - Stuur voor de volgende pagina
cursor=<meta.next_cursor>mee.has_more=falseofnext_cursor=null→ einde.
curl "https://api.kinmu.app/v1/employees?limit=50&cursor=eyJpZCI6MTIzfQ" \
-H "Authorization: Bearer kinmu_sk_live_…"De cursor is opaak: bouw of parse hem niet zelf, maar stuur hem ongewijzigd terug.
Incrementele synchronisatie (updated_since)
De parameter ?updated_since=<ISO 8601> haalt alleen op wat er sinds dat moment is gewijzigd (incrementeel pollen voor BI en ERP’s). Hij geldt niet overal: hij filtert alleen op deze lijst-endpoints.
| Lijst-endpoint | Filtert op updated_since? |
|---|---|
GET /v1/employees | Ja |
GET /v1/check-ins | Ja |
GET /v1/absences | Ja |
GET /v1/locations | Ja |
GET /v1/units | Ja |
GET /v1/webhook-endpoints | Ja |
GET /v1/vacation-balances | Nee — wordt geaccepteerd maar genegeerd; verouderd, verwijdering op 27-08-2027 |
GET /v1/work-summaries | Nee — wordt geaccepteerd maar genegeerd; verouderd, verwijdering op 27-08-2027 |
curl "https://api.kinmu.app/v1/employees?updated_since=2026-07-01T00:00:00Z" \
-H "Authorization: Bearer kinmu_sk_live_…"Bewaar het tijdstip van je laatste synchronisatie en gebruik het als updated_since bij de volgende. Zie het volledige patroon in de BI-gids.
vacation-balances en work-summaries filteren niet op updated_since. Het zijn aggregaties: er valt geen delta op te vragen. Vraag de periode die je nodig hebt opnieuw op (year= bij saldi, from/to bij summaries) of laad hem elke ronde volledig opnieuw, en doe een upsert op sleutel in je opslag. De parameter wordt op die twee endpoints nog steeds geaccepteerd, zodat al gegenereerde SDK’s niet breken, maar hij filtert niets: hij is als verouderd gemarkeerd en verdwijnt op 27-08-2027.
Fouten
Alle fouten volgen RFC 9457 (application/problem+json):
{
"type": "https://docs.kinmu.app/errors/invalid_scope",
"title": "Scope insuficiente",
"status": 403,
"code": "invalid_scope",
"detail": "La API key no tiene el scope requerido: org:empleados:read.",
"errors": { "required_scope": "org:empleados:read" }
}Programmeer tegen het veld code (stabiel), niet tegen title/detail.
Codetabel
code | HTTP | Wanneer |
|---|---|---|
unauthenticated | 401 | De key ontbreekt, is ongeldig, ingetrokken of verlopen. |
invalid_scope | 403 | De key mist de vereiste scope (errors.required_scope). |
subscription_inactive | 403 | Het bedrijf heeft geen actieve dienst: opgeschort of gearchiveerd, geen abonnement, opgeschort abonnement, verlopen proefperiode, of een opgezegd en verstreken of verlopen abonnement. |
addon_disabled | 403 | De addon Public API is niet actief voor het bedrijf. |
validation_failed | 422 | Ongeldige body/parameters (errors met details per veld). |
not_found | 404 | De resource bestaat niet of hoort bij een ander bedrijf. |
conflict | 409 | Statusconflict (bijv. beslissen over een al besliste afwezigheid). |
idempotency_conflict | 409 | De Idempotency-Key is hergebruikt met een andere body. |
rate_limited | 429 | Limiet per minuut overschreden (zie Retry-After). |
quota_exceeded | 429 | Maandelijkse quota opgebruikt. |
billing_required | 402 | Het plan staat de actie niet toe (bijv. medewerker aanmaken zonder seat). |
internal_error | 500 | Interne fout. |
Multitenant-isolatie. Een resource van een ander bedrijf opvragen geeft 404 (not_found), nooit een 403 die het bestaan ervan verraadt.
Een opgebruikte proefperiode of een opzegging sluiten /v1 af met 403 subscription_inactive, nooit met een 402. De 402 (billing_required) is iets anders: het abonnement leeft, maar het pakket dekt de specifieke actie niet (bijv. een medewerker aanmaken zonder vrije seat). Lees de 403 als “geen dienst, heractiveren in het dashboard” en de 402 als “pakket uitbreiden”.
API-keys en webhooks intrekken (DELETE) blijft werken bij een inactief abonnement: het is een beveiligingsmaatregel, geen dienstverbruik.
Rate limits en quota
Elke response (2xx en 4xx) bevat het volgende, met twee uitzonderingen die hieronder staan:
| Header | Betekenis |
|---|---|
X-RateLimit-Limit | Limiet per minuut van de key. |
X-RateLimit-Remaining | Resterende requests in het huidige venster. |
X-RateLimit-Reset | Unix-timestamp waarop het venster reset. |
X-Kinmu-Quota-Remaining | Resterende requests van de maandelijkse quota. |
Twee responses dragen deze headers niet. De 401 (unauthenticated), omdat de authenticatie vóór de throttle afkapt en de request nooit wordt geteld; en alle responses van GET /v1/reports/{report}/download, omdat de ondertekende download buiten de geauthenticeerde pipeline wordt geserveerd. Zie het ontbreken daar niet als een storing: in die twee gevallen is het zo bedoeld.
Limieten: live 120/min, test 30/min. Maandelijkse quota: live 10.000 + 1.000×actieve_medewerkers (max 100.000); test 5.000.
Overschrijd je de minuutlimiet → 429 rate_limited met Retry-After: <seconden>. Is de quota op → 429 quota_exceeded. Retry met backoff en respecteer Retry-After.
import time, requests
def call_with_retry(session, method, url, **kwargs):
for attempt in range(5):
resp = session.request(method, url, **kwargs)
if resp.status_code != 429:
return resp
wait = int(resp.headers.get("Retry-After", 2 ** attempt))
time.sleep(wait)
resp.raise_for_status()
return respHoud X-RateLimit-Remaining en X-Kinmu-Quota-Remaining in de gaten en spreid je requests voordat je een 429 krijgt.
Idempotentie
Bij muterende methodes (POST / PATCH / DELETE) kun je Idempotency-Key: <uniek> meesturen:
curl -s -X POST "https://api.kinmu.app/v1/check-ins" \
-H "Authorization: Bearer kinmu_sk_live_…" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 3f9a…-uuid" \
-d '{ "employee_id": "…", "type": "in", "timestamp": "2026-07-08T08:00:00Z" }'De eerste request wordt uitgevoerd en de response wordt 24 uur gecachet; een herhaalde request met dezelfde body krijgt de oorspronkelijke response terug (zonder opnieuw uit te voeren). Dezelfde key hergebruiken met een andere body → 409 idempotency_conflict.
Het aanmaken van een webhook is de enige uitzondering: het copy-once secret wordt nooit gecachet.