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)
Toutes les listes acceptent ?updated_since=<ISO 8601> pour ne récupérer que ce qui a changé depuis cet instant (polling incrémental pour la BI et les ERP).
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.
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’abonnement de l’entreprise est suspendu ou absent. |
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.
Limites de débit et quota
Chaque réponse (2xx et 4xx) inclut :
| 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. |
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.