API publique KairoProject
Connectez vos outils à KairoProject via l'API publique REST. Authentification, endpoints, webhooks et bonnes pratiques pour les intégrateurs.
L'API publique KairoProject permet à vos outils et systèmes externes d'interagir directement avec vos données de pilotage : portefeuilles, projets, tâches, ressources, temps passé et bien plus. Ce guide couvre l'authentification, les endpoints disponibles, la gestion des webhooks sortants et les bonnes pratiques d'intégration.
API en version 1
L'API publique est actuellement en version v1. Les endpoints décrits dans ce guide sont stables et utilisables en production. Les nouvelles fonctionnalités sont ajoutées sans breaking change ; tout changement incompatible fera l'objet d'un préfixe /v2/ avec un préavis d'au moins 3 mois.
Vous préférez piloter via un agent conversationnel ?
Si vous souhaitez que Claude, ChatGPT ou un autre assistant IA pilote KairoProject directement plutôt que d'intégrer cette API REST vous-même, consultez Connecter votre agent IA à KairoProject. Le serveur MCP expose les mêmes capacités sous forme d'outils structurés, avec la même authentification (mêmes clients API, mêmes scopes) et des garde-fous CCPM intégrés.
Modèle conceptuel
L'API suit strictement la hiérarchie de données de KairoProject :
Organisation
├── API Clients ← vos intégrations (credentials)
├── Membres ← utilisateurs de l'org
├── Webhooks ← endpoints de livraison d'événements
└── Portfolio
├── Ressource ← partagée entre tous les projets du portfolio
├── Équipe ← groupe de ressources, partagée
└── Projet
├── Tâche Root ← tâche structurelle (non supprimable)
├── Tâche ... ← vos tâches opérationnelles
└── Tâche Finish ← tâche structurelle (non supprimable)
Points clés à retenir :
- Toutes les données sont strictement isolées par organisation — un token ne peut jamais accéder aux données d'une autre organisation.
- Les ressources et équipes sont au niveau portfolio, partagées entre tous les projets.
- Chaque projet contient deux tâches structurelles (
root,finish) qui ne peuvent pas être supprimées via l'API. - Le recalcul CCPM (chaîne critique, tampon) est déclenché via
POST /projects/{projectId}/recompute?portfolioId=...et retourne une réponse synchrone.
URL publique de l'API
https://app.kairoproject.com/api/public/v1
Toutes les dates sont en ISO 8601 UTC (2026-05-17T10:00:00.000Z).
Authentification
L'API propose deux flux OAuth 2.0 selon votre contexte d'intégration : client_credentials pour une intégration serveur-à-serveur (votre ERP détient directement le secret), et authorization_code + PKCE pour un agent qui doit obtenir le consentement explicite d'un utilisateur final sans jamais voir son mot de passe. Chaque organisation peut créer des clients API depuis la console d'administration.
Créer un client API
Un owner ou admin crée un client API depuis la console. À la création, un clientId et un clientSecret sont générés — le secret n'est affiché qu'une seule fois.
Chaque client API dispose de :
- un nom affiché dans la console ;
- des scopes définissant les permissions accordées ;
- un statut (
activeourevoked) ; - une durée de vie des tokens (
tokenTTLSeconds, par défaut 3 600 secondes, jamais dépassée quelle que soit la configuration).
Compte organisation ou compte solo
La création d'un client API nécessite un abonnement organisation actif (active ou trialing) en plus du rôle owner/admin — une organisation annulée ou jamais payée reçoit un 403. Sur un compte individuel, elle nécessite l'entitlement apiAccess (plan Solo Pro) ; sans cet entitlement, la création retourne également un 403. Ce contrôle ne s'applique qu'à la création : gérer ou révoquer un client déjà créé reste possible même si l'abonnement a expiré depuis.
Authentification serveur-à-serveur (client_credentials)
POST /api/public/oauth/token
Corps de la requête :
{
"client_id": "org_acme-api-1234",
"client_secret": "kairo_sk_live_...",
"scope": "projects.read tasks.write"
}
Réponse :
{
"access_token": "eyJhbGciOiJIUzI1NiJ9...",
"token_type": "Bearer",
"expires_in": 3600,
"scope": "projects.read tasks.write"
}
Utilisez ensuite le token dans toutes vos requêtes :
Authorization: Bearer <access_token>
Durée de vie du token
Les tokens expirent après 3 600 secondes par défaut. Gérez l'expiration côté client en comparant issued_at + expires_in avec l'heure actuelle. Inutile de regénérer un token avant chaque requête.
Authentification par consentement utilisateur (authorization_code + PKCE)
Ce flux s'utilise quand un agent (Claude.ai, ChatGPT) doit agir au nom d'un utilisateur précis, avec son consentement explicite, sans jamais détenir son mot de passe.
- Discovery — l'agent lit les endpoints disponibles :
curl https://app.kairoproject.com/.well-known/oauth-authorization-server - Générer le couple PKCE (
code_verifieraléatoire,code_challenge= SHA-256 encodé en base64url) :import { randomBytes, createHash } from "node:crypto"; const codeVerifier = randomBytes(32).toString("base64url"); const codeChallenge = createHash("sha256").update(codeVerifier).digest("base64url"); - Ouvrir le navigateur vers l'écran de consentement :
https://app.kairoproject.com/api/public/oauth/authorize ?response_type=code &client_id=org_demo-api-1234 &redirect_uri=https://integrateur.example.com/callback &code_challenge=<codeChallenge> &code_challenge_method=S256 &scope=portfolios.read+tasks.write &state=<valeur-aléatoire-anti-CSRF> - L'utilisateur se connecte, voit les permissions demandées et clique Autoriser. KairoProject redirige vers
redirect_uri?code=<code>&state=<state>(le code expire après 10 minutes). - Échanger le code contre un token, avec le
code_verifiergénéré à l'étape 2 :
Réponse identique au fluxcurl -X POST https://app.kairoproject.com/api/public/oauth/token \ -H "Content-Type: application/json" \ -d '{ "grant_type": "authorization_code", "client_id": "org_demo-api-1234", "client_secret": "kairo_sk_...", "code": "<code>", "redirect_uri": "https://integrateur.example.com/callback", "code_verifier": "<codeVerifier>" }'client_credentials, avec en plus unrefresh_tokensi le scopeoffline_accessa été demandé.
Redirect URI fixe pour Claude.ai
Les redirect URIs sont fixes pour Claude.ai (https://claude.ai/api/mcp/auth_callback). Claude Code utilise un token direct (client_credentials), pas ce flow.
Scopes disponibles
| Scope | Accès accordé |
|---|---|
portfolios.read | Lecture des portefeuilles |
portfolios.write | Création et modification des portefeuilles |
projects.read | Lecture des projets |
projects.write | Création, modification et suppression des projets |
tasks.read | Lecture des tâches |
tasks.write | Création, modification et suppression des tâches |
resources.read | Lecture des ressources et équipes |
resources.write | Création, modification et suppression des ressources et équipes |
planning.trigger | Déclenchement du recalcul planning |
timesheet.read | Lecture des entrées de temps passé |
timesheet.write | Création, correction et suppression des entrées de temps passé |
bufferConsumptionEvents.read | Lecture du journal des causes de retard qualifiées |
bufferConsumptionEvents.write | Qualification des causes de retard et gestion des catégories |
domains.read | Lecture des domaines et phases (référentiel de catégorisation des tâches) |
domains.write | Création, modification et suppression des domaines et phases |
clients.read | Lecture des clients (tiers facturables) |
clients.write | Création des clients et rattachement/détachement à un projet |
organization.read | Lecture des informations plan/sièges de l'organisation |
members.manage | Lecture, invitation et modification des membres |
webhooks.manage | Gestion des webhooks sortants |
Rotation et révocation
- Rotation : créez un nouveau secret depuis la console. Le nouveau secret devient actif immédiatement et l'ancien secret est désactivé lors de la rotation.
- Révocation : passez le client en statut révoqué depuis la console (section API). L'accès est coupé immédiatement : chaque requête revérifie le statut du client, y compris pour un token émis avant la révocation. Il n'y a aucun délai à attendre — un token compromis cesse de fonctionner dès la révocation, sans attendre son expiration naturelle. Après une réactivation, les tokens émis avant la révocation restent rejetés ; seuls ceux émis après la réactivation sont valides.
Rate limiting
Chaque requête authentifiée est comptabilisée dans une fenêtre glissante par client API. Les limites varient selon le scope le plus restrictif de la requête.
| Scope | Requêtes / minute |
|---|---|
tasks.read | 120 |
tasks.write | 60 |
projects.read | 60 |
projects.write | 30 |
planning.trigger | 20 |
webhooks.manage | 30 |
| Autres | 60 |
Chaque réponse de succès inclut les headers suivants :
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 47
X-RateLimit-Reset: 1747476120
En cas de dépassement, l'API retourne un 429 avec un corps JSON uniforme de la forme { "error": "..." }. Selon l'endpoint, des informations de délai de réessai peuvent aussi être exposées via les headers.
Endpoints disponibles
Tous les endpoints sont préfixés par /api/public/v1 et requièrent un Bearer token valide. portfolioId est toujours requis en query param pour les sous-ressources d'un portfolio.
Légende : ✅ Idempotent (répéter la requête produit le même résultat) — ❌ Crée un doublon
Portefeuilles
| Méthode | Route | Scope requis | Idempotent |
|---|---|---|---|
GET | /portfolios | portfolios.read | ✅ |
POST | /portfolios | portfolios.write | ❌ |
GET | /portfolios/:id | portfolios.read | ✅ |
PATCH | /portfolios/:id | portfolios.write | ✅ |
Champs modifiables (PATCH) : name, description, aiAssistEnabled, domainId (doit référencer un domaine existant de la bibliothèque Domaines & Phases, voir plus bas — 404 sinon).
| Méthode | Route | Scope requis | Idempotent |
|---|---|---|---|
GET | /portfolios/:id/decisions | portfolios.read + projects.read + tasks.read + resources.read | ✅ |
Décision de priorité CCPM du portefeuille et diagnostic de la ressource stratégique (goulot au sens de la théorie des contraintes) — calculé à la demande à chaque appel, jamais mis en cache. Indique quel projet protéger en priorité et quelle ressource, si on lui ajoutait de la capacité, ferait le plus progresser l'ensemble du portefeuille. Ne couvre que les projets released: true.
Projets
| Méthode | Route | Scope requis | Idempotent |
|---|---|---|---|
GET | /projects?portfolioId= | projects.read | ✅ |
POST | /projects | projects.write | ❌ |
GET | /projects/:id?portfolioId= | projects.read | ✅ |
PATCH | /projects/:id?portfolioId= | projects.write | ✅ |
DELETE | /projects/:id?portfolioId= | projects.write | ✅ |
POST | /projects/:id/recompute?portfolioId= | planning.trigger | ✅ |
Suppression irréversible
La suppression d'un projet efface le projet et toutes ses tâches. Cette opération est irréversible. Les champs progress et bufferConsumption sont calculés — ils sont en lecture seule.
Tâches
| Méthode | Route | Scope requis | Idempotent |
|---|---|---|---|
GET | /projects/:id/tasks?portfolioId= | tasks.read | ✅ |
POST | /projects/:id/tasks | tasks.write | ❌ |
GET | /projects/:id/tasks/:taskId?portfolioId= | tasks.read | ✅ |
PATCH | /projects/:id/tasks/:taskId?portfolioId= | tasks.write | ✅ |
DELETE | /projects/:id/tasks/:taskId?portfolioId= | tasks.write | ✅ |
La mise à jour de ettcHours (heures restantes estimées) est le mécanisme de saisie d'avancement. Les tâches root et finish ne peuvent pas être supprimées (retourne 422).
Ressources et équipes
| Méthode | Route | Scope requis | Idempotent |
|---|---|---|---|
GET / POST / PATCH / DELETE | /projects/:id/resources?portfolioId= | resources.read / resources.write | ✅ / ❌ |
GET | /portfolios/:id/resources/:resId/load-timeline | resources.read | ✅ |
GET / POST / PATCH / DELETE | /projects/:id/teams?portfolioId= | resources.read / resources.write | ✅ / ❌ |
Les ressources et équipes sont stockées au niveau portfolio (un chemin équivalent /portfolios/:id/resources[...] et /portfolios/:id/teams[...] existe et opère sur la même collection, sans déclencher de recalcul). Le projectId dans l'URL sert uniquement à déclencher un recalcul du projet concerné après mutation.
load-timeline retourne la courbe de charge hebdomadaire d'une ressource (heures planifiées vs capacité) — à titre indicatif : ne distingue pas les tâches co-assignées à plusieurs ressources ni les affectations via équipe.
Domaines & Phases
Référentiel partagé (indépendant d'un portfolio précis) utilisé pour catégoriser les tâches par phase et colorer le Gantt.
| Méthode | Route | Scope requis | Idempotent |
|---|---|---|---|
GET / POST | /domains | domains.read / domains.write | ✅ / ❌ |
GET / PATCH / DELETE | /domains/:id | domains.read / domains.write | ✅ |
POST | /domains/:id/phases | domains.write | ❌ |
GET / PATCH / DELETE | /domains/:id/phases/:phaseId | domains.read / domains.write | ✅ |
PATCH /domains/:id remplace name et/ou l'intégralité du tableau phases — pas un merge. Pour ajouter/modifier/retirer une seule phase sans resoumettre les autres, préférez les endpoints .../phases[/:phaseId] dédiés. DELETE /domains/:id est refusée (400) si un portfolio référence encore ce domaine. La couleur d'une phase doit appartenir à la palette autorisée (18 couleurs prédéfinies).
Clients
Un client est un tiers facturable (compte-level, jamais imbriqué sous un portfolio) — typiquement le nom/code ERP du client final d'un projet.
| Méthode | Route | Scope requis | Idempotent |
|---|---|---|---|
GET | /clients | clients.read | ✅ |
GET | /projects/:id/client?portfolioId= | clients.read | ✅ |
PATCH | /projects/:id/client?portfolioId= | clients.write | ✅ (par clientId) |
DELETE | /projects/:id/client?portfolioId= | clients.write | ✅ |
PATCH écrase systématiquement tout rattachement existant (l'ERP fait foi). Si clientId ne correspond à aucun client déjà créé, un nouveau client est automatiquement créé plutôt que rejeté.
Import de projet
| Méthode | Route | Scope requis | Idempotent |
|---|---|---|---|
POST | /projects/import | projects.write | ✅ (par externalId) |
Crée en un seul appel un projet complet (tâches, ressources, équipes, calendriers) depuis un objet kairo-project-export — pour migrer depuis un outil tiers (Jira, MS Project, Asana, Excel) ou générer un projet depuis un cahier des charges. Ré-importer avec le même externalId de projet met à jour la même ressource plutôt que d'en créer un doublon.
Références non validées à l'import
tasks[].phaseId, tasks[].pertDefinitionId et project.clientId sont acceptés tels quels par cet endpoint, sans validation ni création — n'utilisez que des identifiants déjà existants dans le compte/portfolio cible (obtenus via GET /domains, GET /pert-definitions, GET /clients).
Timesheet
| Méthode | Route | Scope requis | Idempotent |
|---|---|---|---|
GET / POST | /portfolios/:id/timeEntries | timesheet.read / timesheet.write | ✅ / ❌ |
PATCH / DELETE | /portfolios/:id/timeEntries/:entryId | timesheet.write | ✅ |
POST | /portfolios/:id/timeEntries/:entryId/adjustments | timesheet.write | ❌ |
Journal des heures passées, à des fins de reporting et de calcul de coûts. Deux origines : source: "auto" (générée automatiquement à chaque PATCH .../tasks/:taskId qui modifie ettcHours) et source: "manual". Créer ou corriger une entrée ne modifie pas ettcHours sur la tâche associée et ne déclenche aucun recalcul planning.
.../adjustments corrige une entrée déjà exportée (verrouillée) sans jamais réécrire l'original — logique de note de crédit comptable. Échoue en 400 si l'entrée référencée n'est pas encore exportée (utilisez alors un PATCH normal). durationHours est signé (négatif pour retrancher, positif pour ajouter).
Causes de retard qualifiées (buffer)
| Méthode | Route | Scope requis | Idempotent |
|---|---|---|---|
GET | /portfolios/:id/bufferConsumptionEvents | bufferConsumptionEvents.read | ✅ |
PATCH | /portfolios/:id/bufferConsumptionEvents/:eventId | bufferConsumptionEvents.write | ✅ |
GET / POST | /portfolios/:id/issueCategories | bufferConsumptionEvents.read / bufferConsumptionEvents.write | ✅ / ❌ |
GET / PATCH / DELETE | /portfolios/:id/issueCategories/:categoryId | bufferConsumptionEvents.read / bufferConsumptionEvents.write | ✅ |
Journal des causes de retard qualifiées — chaque événement correspond à une consommation de tampon CCPM imputée à une cause précise (données source du Pareto des causes de retard). categoryId: null déclasse l'événement en « Sans cause ». Renommer ou supprimer une catégorie n'affecte jamais categoryNameSnapshot sur les événements déjà qualifiés — l'historique du Pareto reste stable dans le temps.
Coûts
| Méthode | Route | Scope requis | Idempotent |
|---|---|---|---|
GET | /portfolios/:id/resources/:resId/cost | timesheet.read | ✅ |
GET | /projects/:id/cost?portfolioId= | timesheet.read | ✅ |
Calculés à partir des entrées timesheet (durationHours × hourlyRate) — jamais persistés, recalculés à chaque appel. Si aucune ressource n'a de hourlyRate défini, les champs de coût sont null plutôt que 0, pour distinguer « pas de tarif configuré » de « coût nul ».
Presets PERT et risque de retard (ML)
| Méthode | Route | Scope requis | Idempotent |
|---|---|---|---|
GET | /pert-definitions | projects.read | ✅ |
GET | /projects/:id/risk?portfolioId= | projects.read | ✅ |
Les presets PERT (vitesses optimiste/médiane/pessimiste) convertissent une quantité de travail en heures avant un import ou une création de tâche. /risk retourne une probabilité de retard prédite par un modèle ML entraîné sur l'historique de l'organisation (horizonDays, défaut 7, borné entre 7 et 21) ; source: "fallback" signifie qu'aucun modèle entraîné n'est disponible pour ce périmètre.
Organisation et membres
| Méthode | Route | Scope requis | Idempotent |
|---|---|---|---|
GET | /organization | organization.read | ✅ |
GET | /members | members.manage | ✅ |
POST | /members | members.manage | ❌ |
GET | /members/:id | members.manage | ✅ |
PATCH | /members/:id | members.manage | ✅ |
POST /members accepte deux modes : avec uid (ajoute un utilisateur existant, retourne 200) ou avec email sans uid (crée une invitation, retourne 201).
Pagination
Les listes utilisent un système de pagination par curseur.
pageSize: nombre d'éléments par page (défaut 25, max 100).pageToken: curseur retourné dansnextPageTokenpar la réponse précédente.
async function fetchAllProjects(token: string, portfolioId: string) {
const projects = [];
let pageToken: string | null = null;
do {
const url = new URL("https://app.kairoproject.com/api/public/v1/projects");
url.searchParams.set("portfolioId", portfolioId);
url.searchParams.set("pageSize", "100");
if (pageToken) url.searchParams.set("pageToken", pageToken);
const res = await fetch(url, { headers: { Authorization: `Bearer ${token}` } });
const data = await res.json();
projects.push(...data.items);
pageToken = data.nextPageToken;
} while (pageToken);
return projects;
}
Webhooks sortants
KairoProject peut notifier vos systèmes en temps réel via des webhooks HTTP.
Configurer un webhook
POST /api/public/v1/webhooks
Scope requis : webhooks.manage.
Chaque webhook associe :
- une URL HTTPS de destination ;
- une liste d'événements à écouter ;
- un secret de signature généré automatiquement.
Événements disponibles
| Événement | Déclencheur |
|---|---|
project.created | Création d'un projet |
project.updated | Modification d'un projet (inclut progress, bufferConsumption) |
project.deleted | Suppression d'un projet |
task.created | Création d'une tâche |
task.updated | Modification d'une tâche |
task.deleted | Suppression d'une tâche |
planning.recompute.completed | Recalcul planning terminé — inclut bufferConsumption, progress, status |
planning.buffer_overflow | bufferConsumption >= 1.0 — inclut threshold et exceededAt |
Vérifier la signature
Chaque livraison inclut le header :
X-Kairo-Signature: t=<unix>,v1=<digest>
Le digest est calculé ainsi : HMAC-SHA256(secret, "${t}.${body}").
import { createHmac, timingSafeEqual } from "node:crypto";
function verifyKairoSignature(
signingSecret: string,
signatureHeader: string,
rawBody: string
): boolean {
const match = signatureHeader.match(/t=([^,]+),v1=([a-f0-9]+)/);
if (!match) return false;
const [, timestamp, signature] = match;
const payload = `${timestamp}.${rawBody}`;
const expected = createHmac("sha256", signingSecret).update(payload).digest("hex");
return timingSafeEqual(Buffer.from(expected, "hex"), Buffer.from(signature, "hex"));
}
Ignorez les livraisons avec un timestamp antérieur de plus de 5 minutes pour vous protéger des attaques replay.
Délai de réponse et retry
Votre endpoint doit répondre en moins de 10 secondes avec un code 2xx. En cas d'échec, la plateforme retente automatiquement jusqu'à 5 fois : +2 min, +8 min, +30 min, +2h. Au-delà, l'événement est marqué failed définitivement. Utilisez event.id pour dédupliquer les livraisons multiples.
Rotation du secret webhook
Utilisez POST /api/public/v1/webhooks/{id}/rotate-secret pour renouveler le secret de signature d'un webhook. L'ancienne valeur est révoquée dès la rotation.
OpenAPI et génération de SDK
Le contrat technique complet de l'API est disponible au format OpenAPI 3.1. Deux usages principaux :
- Publier la spec pour vos équipes ou partenaires intégrateurs.
- Générer un SDK typé plutôt qu'écrire les appels HTTP à la main :
Le fichier généré expose les types (npx openapi-typescript docs/api/public-api-openapi.yaml --output sdk/public-api.tsPortfolio,Resource,Member, etc.) et peut être publié dans un package interne.
Pour d'autres langages, openapi-generator-cli peut partir de la même spec :
openapi-generator-cli generate -i docs/api/public-api-openapi.yaml -g go
Codes d'erreur
Toutes les erreurs retournent un corps JSON uniforme : { "error": "Description explicite du problème." }
| Code | Raison courante |
|---|---|
400 | Validation échouée — champ manquant, format invalide, portfolioId absent |
401 | Token absent, invalide, expiré ou client révoqué |
403 | Token valide mais scope insuffisant |
404 | Ressource introuvable dans votre organisation |
422 | Opération sémantiquement impossible (ex : supprimer une tâche root) |
429 | Rate limit dépassé |
500 | Erreur serveur inattendue — réessayez après quelques secondes |
2 minutes · résultat personnalisé
Newsletter
Envie d'aller plus loin ?
Recevez régulièrement nos analyses sur la priorisation multi-projets, la Chaîne Critique, les ressources partagées et les pratiques de pilotage.
À lire ensuite
Connecter votre agent IA à KairoProject
Donnez à Claude, ChatGPT ou tout assistant IA un accès direct à vos projets KairoProject. Configurez la connexion en quelques minutes, sans code.
CCPM : la méthode qui explique pourquoi vos projets dérivent (et comment l'éviter)
Qu'est-ce que la CCPM (Critical Chain Project Management) ? Guide complet sur la méthode de la Chaîne Critique : principes, mécanismes, buffers, ressource contrainte et pilotage multi-projets pour les PME et bureaux d'études.
Gestion de portefeuille de projets : méthodes, outils et erreurs à éviter
Qu'est-ce qu'un portefeuille de projets, comment le piloter efficacement, gérer les ressources partagées et éviter les erreurs classiques qui font déraper les équipes.