API-Konventionen
Regeln, die für alle Ressourcen von v1 gelten.
| Thema | Regel |
|---|---|
| Format | JSON UTF-8. Requests mit Body tragen Content-Type: application/json. |
| IDs | Öffentliche UUIDs. Interne numerische IDs werden nie exponiert. |
| Datumsangaben | ISO 8601. Zeitpunkte in UTC (2026-07-08T09:30:00Z); Kalenderdaten als YYYY-MM-DD. |
| Base URL | Versioniert im Pfad: https://api.kinmu.app/v1 · Dev: https://api.dev.kinmu.app/v1. |
Paginierung (Cursor)
Alle Listen paginieren per Cursor:
?limit=(Default 25, max. 100) ·?cursor=<opaco>.- Antwort:
{ "data": [...], "meta": { "next_cursor": "…"|null, "has_more": true|false } }. - Für die nächste Seite sendest du
cursor=<meta.next_cursor>.has_more=falseodernext_cursor=null→ Ende.
curl "https://api.kinmu.app/v1/employees?limit=50&cursor=eyJpZCI6MTIzfQ" \
-H "Authorization: Bearer kinmu_sk_live_…"Der cursor ist opak: Baue ihn nicht selbst zusammen und parse ihn nicht — sende ihn unverändert zurück.
Inkrementelle Synchronisierung (updated_since)
Alle Listen akzeptieren ?updated_since=<ISO 8601>, um nur die Änderungen seit diesem Zeitpunkt zu laden (inkrementelles Polling für BI und ERPs).
curl "https://api.kinmu.app/v1/employees?updated_since=2026-07-01T00:00:00Z" \
-H "Authorization: Bearer kinmu_sk_live_…"Speichere den Zeitpunkt deiner letzten Synchronisierung und verwende ihn beim nächsten Lauf als updated_since. Das vollständige Pattern findest du im BI-Guide.
Fehler
Alle Fehler folgen 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" }
}Programmiere gegen das Feld code (stabil), nicht gegen title/detail.
Fehlercodes
code | HTTP | Wann |
|---|---|---|
unauthenticated | 401 | Key fehlt, ist ungültig, widerrufen oder abgelaufen. |
invalid_scope | 403 | Der Key hat den erforderlichen Scope nicht (errors.required_scope). |
subscription_inactive | 403 | Das Abonnement des Unternehmens ist ausgesetzt oder fehlt. |
addon_disabled | 403 | Das Addon Public API ist für das Unternehmen nicht aktiv. |
validation_failed | 422 | Ungültiger Body/Parameter (errors mit Details pro Feld). |
not_found | 404 | Die Ressource existiert nicht oder gehört einem anderen Unternehmen. |
conflict | 409 | Zustandskonflikt (z. B. eine bereits entschiedene Abwesenheit erneut entscheiden). |
idempotency_conflict | 409 | Die Idempotency-Key wurde mit einem anderen Body wiederverwendet. |
rate_limited | 429 | Minutenlimit überschritten (siehe Retry-After). |
quota_exceeded | 429 | Monatskontingent aufgebraucht. |
billing_required | 402 | Der Plan erlaubt die Aktion nicht (z. B. Mitarbeitenden anlegen ohne freien Seat). |
internal_error | 500 | Interner Fehler. |
Multitenant-Isolation. Eine Ressource eines anderen Unternehmens anzufragen liefert 404 (not_found) — nie ein 403, das ihre Existenz verraten würde.
Rate Limits und Kontingent
Jede Antwort (2xx und 4xx) enthält:
| Header | Bedeutung |
|---|---|
X-RateLimit-Limit | Minutenlimit des Keys. |
X-RateLimit-Remaining | Verbleibende Requests im aktuellen Fenster. |
X-RateLimit-Reset | Unix-Timestamp, zu dem das Fenster zurückgesetzt wird. |
X-Kinmu-Quota-Remaining | Verbleibende Requests des Monatskontingents. |
Limits: live 120/min, test 30/min. Monatskontingent: live 10.000 + 1.000×empleados_activos (max. 100.000); test 5.000.
Beim Überschreiten des Minutenlimits → 429 rate_limited mit Retry-After: <segundos>. Bei aufgebrauchtem Kontingent → 429 quota_exceeded. Wiederhole mit Backoff und respektiere 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 respBehalte X-RateLimit-Remaining und X-Kinmu-Quota-Remaining im Blick, um deine Requests zu verteilen, bevor du ein 429 bekommst.
Idempotenz
Bei mutierenden Methoden (POST / PATCH / DELETE) kannst du Idempotency-Key: <único> senden:
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" }'Der erste Request wird ausgeführt und seine Antwort 24 h gecacht; ein erneutes Senden mit dem gleichen Body liefert die ursprüngliche Antwort (ohne erneute Ausführung). Denselben Key mit einem anderen Body wiederverwenden → 409 idempotency_conflict.
Das Anlegen eines Webhooks ist die einzige Ausnahme: Sein copy-once-Secret wird nie gecacht.