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 et ressources. 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.
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.
Base URL
https://kairoproject.app/api/public/v1
Toutes les dates sont en ISO 8601 UTC (2026-05-17T10:00:00.000Z).
Authentification
L'API utilise le flux OAuth 2.0 client_credentials. 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).
Obtenir un token d'accès
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.
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 |
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). Les nouvelles demandes de token sont refusées immédiatement, et les tokens existants peuvent aussi être invalidés dès la révocation.
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.
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 / POST / PATCH / DELETE | /projects/:id/teams?portfolioId= | resources.read / resources.write | ✅ / ❌ |
Les ressources et équipes sont stockées au niveau portfolio. Le projectId dans l'URL sert uniquement à déclencher un recalcul du projet concerné après mutation.
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://kairoproject.app/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.
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 |