Conventions de l’API
Règles transversales à toutes les ressources de la v1.
| Sujet | Règle |
|---|---|
| Format | JSON UTF-8. Les requêtes avec un corps portent Content-Type: application/json. |
| IDs | UUID publics. Les IDs numériques internes ne sont jamais exposés. |
| Dates | ISO 8601. Instants en UTC (2026-07-08T09:30:00Z) ; dates calendaires YYYY-MM-DD. |
| Base URL | Versionnée dans le path : https://api.kinmu.app/v1 · Dev : https://api.dev.kinmu.app/v1. |
Pagination (curseur)
Toutes les listes paginent par curseur :
?limit=(défaut 25, max 100) ·?cursor=<opaque>.- Réponse :
{ "data": [...], "meta": { "next_cursor": "…"|null, "has_more": true|false } }. - Pour la page suivante, renvoyez
cursor=<meta.next_cursor>.has_more=falseounext_cursor=null→ fin.
curl "https://api.kinmu.app/v1/employees?limit=50&cursor=eyJpZCI6MTIzfQ" \
-H "Authorization: Bearer kinmu_sk_live_…"Le cursor est opaque : ne le construisez pas et ne le parsez pas, renvoyez-le tel quel.
Synchronisation incrémentale (updated_since)
Le paramètre ?updated_since=<ISO 8601> ne récupère que ce qui a changé depuis cet instant (polling incrémental pour la BI et les ERP). Il n’est pas universel : il ne filtre que sur ces listes.
| Liste | Filtre par updated_since ? |
|---|---|
GET /v1/employees | Oui |
GET /v1/check-ins | Oui |
GET /v1/absences | Oui |
GET /v1/locations | Oui |
GET /v1/units | Oui |
GET /v1/webhook-endpoints | Oui |
GET /v1/vacation-balances | Non — accepté mais ignoré ; obsolète, retrait le 27/08/2027 |
GET /v1/work-summaries | Non — accepté mais ignoré ; obsolète, retrait le 27/08/2027 |
curl "https://api.kinmu.app/v1/employees?updated_since=2026-07-01T00:00:00Z" \
-H "Authorization: Bearer kinmu_sk_live_…"Conservez l’instant de votre dernière synchronisation et utilisez-le comme updated_since à la suivante. Le pattern complet est décrit dans le guide BI.
vacation-balances et work-summaries ne filtrent pas par updated_since. Ce sont des agrégats : il n’y a pas de delta à demander. Redemandez la plage qui vous intéresse (year= pour les soldes, from/to pour les résumés) ou rechargez-la entièrement à chaque passe, puis faites un upsert par clé dans votre entrepôt. Le paramètre reste accepté sur ces deux endpoints, pour ne pas casser les SDK déjà générés, mais il ne filtre rien : il est marqué obsolète et sera retiré le 27/08/2027.
Erreurs
Toutes les erreurs suivent la 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" }
}Programmez contre le champ code (stable), pas contre title/detail.
Table des codes
code | HTTP | Quand |
|---|---|---|
unauthenticated | 401 | Clé absente, invalide, révoquée ou expirée. |
invalid_scope | 403 | La clé n’a pas le scope requis (errors.required_scope). |
subscription_inactive | 403 | L’entreprise n’a pas de service actif : suspendue ou archivée, sans abonnement, abonnement suspendu, essai expiré, ou abonnement résilié et arrivé à terme ou expiré. |
addon_disabled | 403 | L’addon Public API n’est pas actif pour l’entreprise. |
validation_failed | 422 | Corps ou paramètres invalides (errors avec le détail par champ). |
not_found | 404 | La ressource n’existe pas ou appartient à une autre entreprise. |
conflict | 409 | Conflit d’état (p. ex. décider une absence déjà décidée). |
idempotency_conflict | 409 | L’Idempotency-Key a été réutilisée avec un corps différent. |
rate_limited | 429 | Limite par minute dépassée (voir Retry-After). |
quota_exceeded | 429 | Quota mensuel épuisé. |
billing_required | 402 | Le plan ne permet pas l’action (p. ex. création d’un salarié sans siège disponible). |
internal_error | 500 | Erreur interne. |
Isolation multitenant. Demander une ressource d’une autre entreprise renvoie 404 (not_found), jamais un 403 qui révélerait son existence.
Un essai épuisé ou une résiliation coupent /v1 avec un 403 subscription_inactive, jamais avec un 402. Le 402 (billing_required) est autre chose : l’abonnement est bien actif, mais le plan ne couvre pas l’action demandée (p. ex. créer un salarié sans siège disponible). Lisez le 403 comme « plus de service, à réactiver dans le dashboard » et le 402 comme « plan à faire évoluer ».
Révoquer des clés API et des webhooks (DELETE) continue de fonctionner avec un abonnement inactif : c’est un contrôle de sécurité, pas de la consommation de service.
Limites de débit et quota
Chaque réponse (2xx et 4xx) inclut, à deux exceptions documentées plus bas :
| Header | Signification |
|---|---|
X-RateLimit-Limit | Limite par minute de la clé. |
X-RateLimit-Remaining | Requêtes restantes dans la fenêtre courante. |
X-RateLimit-Reset | Timestamp Unix de réinitialisation de la fenêtre. |
X-Kinmu-Quota-Remaining | Requêtes restantes sur le quota mensuel. |
Deux réponses ne portent pas ces en-têtes. Les 401 (unauthenticated), parce que l’authentification coupe avant le throttle et que la requête n’est jamais comptée ; et toutes les réponses de GET /v1/reports/{report}/download, parce que le téléchargement signé est servi hors du pipeline authentifié. Ne prenez pas leur absence pour une anomalie : dans ces deux cas, c’est le comportement attendu.
Limites : live 120/min, test 30/min. Quota mensuel : live 10.000 + 1.000×empleados_activos (max 100 000) ; test 5.000.
Au-delà de la limite par minute → 429 rate_limited avec Retry-After: <secondes>. Quota épuisé → 429 quota_exceeded. Réessayez avec un backoff en respectant 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 respSurveillez X-RateLimit-Remaining et X-Kinmu-Quota-Remaining pour espacer vos requêtes avant de recevoir un 429.
Idempotence
Sur les méthodes qui écrivent (POST / PATCH / DELETE), vous pouvez envoyer Idempotency-Key: <unique> :
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" }'La première requête est exécutée et sa réponse est mise en cache 24 h ; un renvoi avec le même corps retourne la réponse d’origine (sans ré-exécution). Réutiliser la même clé avec un corps différent → 409 idempotency_conflict.
La création de webhook est la seule exception : son secret copy-once n’est jamais mis en cache.