Skip to Content
Conventions

Conventions de l’API

Règles transversales à toutes les ressources de la v1.

SujetRègle
FormatJSON UTF-8. Les requêtes avec un corps portent Content-Type: application/json.
IDsUUID publics. Les IDs numériques internes ne sont jamais exposés.
DatesISO 8601. Instants en UTC (2026-07-08T09:30:00Z) ; dates calendaires YYYY-MM-DD.
Base URLVersionné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=false ou next_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.

ListeFiltre par updated_since ?
GET /v1/employeesOui
GET /v1/check-insOui
GET /v1/absencesOui
GET /v1/locationsOui
GET /v1/unitsOui
GET /v1/webhook-endpointsOui
GET /v1/vacation-balancesNon — accepté mais ignoré ; obsolète, retrait le 27/08/2027
GET /v1/work-summariesNon — 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

codeHTTPQuand
unauthenticated401Clé absente, invalide, révoquée ou expirée.
invalid_scope403La clé n’a pas le scope requis (errors.required_scope).
subscription_inactive403L’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_disabled403L’addon Public API n’est pas actif pour l’entreprise.
validation_failed422Corps ou paramètres invalides (errors avec le détail par champ).
not_found404La ressource n’existe pas ou appartient à une autre entreprise.
conflict409Conflit d’état (p. ex. décider une absence déjà décidée).
idempotency_conflict409L’Idempotency-Key a été réutilisée avec un corps différent.
rate_limited429Limite par minute dépassée (voir Retry-After).
quota_exceeded429Quota mensuel épuisé.
billing_required402Le plan ne permet pas l’action (p. ex. création d’un salarié sans siège disponible).
internal_error500Erreur 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 :

HeaderSignification
X-RateLimit-LimitLimite par minute de la clé.
X-RateLimit-RemainingRequêtes restantes dans la fenêtre courante.
X-RateLimit-ResetTimestamp Unix de réinitialisation de la fenêtre.
X-Kinmu-Quota-RemainingRequê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 resp

Surveillez 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érent409 idempotency_conflict.

La création de webhook est la seule exception : son secret copy-once n’est jamais mis en cache.

Last updated on