Documentation API Manifst

Bienvenue dans la documentation de l'API REST Manifst v1. Elle est incluse à partir du plan Portefeuille, et s'évalue sur l'abonnement du compte propriétaire de la clé. Les comptes souscrits avant la grille actuelle conservent leur accès.

Collection Postman
36 requêtes prêtes à l'emploi. Renseignez baseUrl et apiKey dans les variables de collection.
Télécharger

Introduction

URL de base :

https://manifst.net/api/v1/

Toutes les réponses sont au format JSON avec la structure suivante :

// Succès
{
  "data": { ... },        // objet ou tableau
  "meta": { "total": 42 } // présent sur les listes
}

// Erreur
{
  "error": {
    "code": "INSUFFICIENT_SCOPE",
    "message": "This API key does not have the required scope: 'projects:write'."
  }
}

Authentification

Toutes les requêtes doivent inclure votre clé API dans le header Authorization :

Authorization: Bearer mfst_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Ne jamais exposer votre clé API côté client (JS frontend, application mobile). Utilisez-la uniquement côté serveur.

Générer une clé

Rendez-vous dans Paramètres → Clés API de votre dashboard Manifst. Les clés sont affichées une seule fois à la création.

Exemple de requête

curl https://manifst.net/api/v1/projects \
  -H "Authorization: Bearer mfst_live_xxxxx"

Rate limiting

FenêtreLimite
Par heure1 000 requêtes
Par mois10 000 requêtes

Chaque réponse inclut ces headers :

X-RateLimit-Limit-Hour: 1000
X-RateLimit-Remaining-Hour: 847
X-RateLimit-Limit-Month: 10000
X-RateLimit-Remaining-Month: 9231
X-RateLimit-Reset-Hour: 1711234567

En cas de dépassement, l'API retourne HTTP 429 avec le header Retry-After (secondes avant le prochain reset).

Pagination & rafraîchissement incrémental

Les listes volumineuses : /tickets et /time-entries : acceptent une pagination optionnelle et un filtre incrémental. Sans ces paramètres, la réponse reste identique aux versions précédentes (rétro-compatible).

ParamètreDescription
pageNuméro de page (défaut 1). Active la pagination.
per_pageTaille de page (défaut 100, max 500). Active la pagination.
updated_sinceNe renvoie que les éléments modifiés depuis cette date (YYYY-MM-DD ou datetime). Idéal pour un refresh incrémental Power BI.

Le bloc meta renvoie toujours le total réel avant pagination :

GET /api/v1/tickets?project=abc-123&page=2&per_page=50&updated_since=2026-07-01

{
  "data": [ ... ],
  "meta": { "total": 320, "count": 50, "page": 2, "per_page": 50 }
}

Codes d'erreur

CodeHTTPDescription
MISSING_API_KEY401Header Authorization absent
INVALID_API_KEY401Clé introuvable
REVOKED_API_KEY401Clé révoquée
EXPIRED_API_KEY401Clé expirée
PLAN_REQUIRED403Plan Portefeuille requis
ACCOUNT_INACTIVE403Compte inactif
IP_NOT_ALLOWED403IP non autorisée (whitelist)
INSUFFICIENT_SCOPE403Scope manquant sur la clé
RATE_LIMIT_EXCEEDED429Quota dépassé
NOT_FOUND404Ressource introuvable
EXTERNAL_ACCESS_DENIED403Projet dont vous n’êtes ni owner ni admin, et dont le owner n’a pas ouvert l’accès par assistant IA ou API à votre rôle (permission use_ai_api, Paramètres → Accès → Assistants IA et API ; aucun rôle par défaut). Ces projets n’apparaissent pas non plus dans GET /projects, le portefeuille, les métriques ni les notifications.
FORBIDDEN403Accès interdit : projet dont vous n’êtes pas membre, ou action que votre rôle dans le projet n’autorise pas (la clé a les droits de son titulaire, pas davantage)
VALIDATION_ERROR422Paramètre manquant ou invalide
METHOD_NOT_ALLOWED405Méthode HTTP non supportée

Scopes

Chaque clé API dispose d'un ensemble de scopes qui définissent ses permissions. Les scopes disponibles :

ScopeDescription
projects:readLire les projets et leurs membres
projects:writeCréer, modifier, supprimer des projets
super-epics:readLire les super-épics
super-epics:writeCréer, modifier, supprimer les super-épics
epics:readLire les épics
epics:writeCréer, modifier, supprimer les épics
sprints:readLire les sprints
sprints:writeCréer et modifier les sprints
tickets:readLire les tickets
tickets:writeCréer, modifier, supprimer les tickets
comments:readLire les commentaires d'un ticket
comments:writeCréer, modifier, supprimer des commentaires
team:readLire les membres d'un projet
time:readLire les entrées de temps
time:writeEnregistrer des entrées de temps
statuses:readLire les statuts d'un projet
finance:readLire les indicateurs finance / EVM (projet & portefeuille)
roadmap:readLire les dépendances et jalons de roadmap
roadmap:writeCréer, modifier, supprimer les jalons et dépendances de roadmap
governance:readLire le registre des risques et la matrice RACI
governance:writeCréer, modifier, supprimer les risques et la matrice RACI
notifications:readLire les notifications du titulaire de la clé
notifications:writeMarquer lues, supprimer les notifications du titulaire de la clé
docs:readLire les espaces, pages et fichiers MyDoc
docs:writeCréer, modifier, supprimer des pages MyDoc

Le référentiel d'un projet suit les scopes existants : les tags relèvent de tickets:read / tickets:write, les types de ticket de projects:read / projects:write, les règles de transition de projects:read, l'historique d'un ticket de tickets:read. La recherche demande projects:read et ne cherche que les types dont la clé a le scope de lecture.

Projets

GET /api/v1/projects Lister les projets

Scope requis : projects:read

curl /api/v1/projects -H "Authorization: Bearer mfst_live_xxx"

// Réponse 200
{
  "data": [
    {
      "uuid": "abc-123",
      "name": "Mon projet",
      "description": "Description",
      "color": "#6366f1",
      "default_dod": ["Code review faite", "Tests passants"],
      "contingency_pct": 10.0,
      "budget": 350000.00,
      "user_role": "owner",
      "member_count": 5,
      "created_at": "2026-01-15 10:00:00",
      "updated_at": "2026-03-01 14:22:00"
    }
  ],
  "meta": { "total": 1 }
}
GET /api/v1/projects/{uuid} Détail d'un projet

Scope requis : projects:read

curl /api/v1/projects/abc-123 -H "Authorization: Bearer mfst_live_xxx"
GET /api/v1/projects/{uuid}/members Membres d'un projet

Scope requis : team:read

POST /api/v1/projects Créer un projet

Scope requis : projects:write

La création initialise automatiquement les 6 statuts par défaut (Backlog, À faire, En cours, En revue, Terminé, Bloqué) et les 2 types de tickets par défaut (Story, Bug).

Body

{
  "name": "Mon projet",                     // requis, 1 - 80 caractères
  "description": "...",                     // optionnel
  "color": "#6366f1",                       // optionnel, hex #rrggbb
  "default_dod": ["Tests OK", "Code review"], // optionnel : Definition of Done (tableau)
  "contingency_pct": 10.00,                 // optionnel : aléas finance, 0 - 100 % (défaut: 10)
  "budget": 350000.00                       // optionnel : BAC / budget à terminaison (≥ 0, null pour effacer)
}

Réponse 201

{
  "data": {
    "uuid": "...",
    "name": "Mon projet",
    "description": null,
    "color": "#6366f1",
    "default_dod": null,
    "contingency_pct": 10.0,
    "budget": 350000.00,
    "user_role": "owner",
    "created_at": "2026-03-24 20:00:00",
    "updated_at": "2026-03-24 20:00:00"
  }
}
PUT /api/v1/projects/{uuid} Modifier un projet

Scope requis : projects:write : owner uniquement

Body

{
  "name": "Nouveau nom",                  // optionnel
  "description": "...",                   // optionnel
  "color": "#f59e0b",                     // optionnel
  "default_dod": ["item 1", "item 2"],     // optionnel : null ou [] pour vider
  "contingency_pct": 15.00,                // optionnel : 0 - 100 %
  "budget": 420000.00                      // optionnel : BAC (≥ 0, null pour effacer)
}

Réponse 200

{
  "data": {
    "uuid": "...",
    "name": "Nouveau nom",
    "description": null,
    "color": "#f59e0b",
    "default_dod": ["item 1", "item 2"],
    "contingency_pct": 15.0,
    "budget": 420000.00,
    "user_role": "owner",
    "created_at": "2026-03-24 20:00:00",
    "updated_at": "2026-03-24 20:00:00"
  }
}
DELETE /api/v1/projects/{uuid} Supprimer un projet

Scope requis : projects:write : owner uniquement

// Réponse 200
{ "data": { "deleted": true } }

Super-épics

GET /api/v1/super-epics?project={uuid} Lister les super-épics

Scope requis : super-epics:read

GET /api/v1/super-epics/{uuid} Détail

Scope requis : super-epics:read

POST /api/v1/super-epics Créer

Scope requis : super-epics:write

Body

{
  "project_uuid": "abc-123",
  "title": "Titre",
  "description": "...",  // optionnel
  "status": "backlog",   // optionnel : backlog | in_progress | done (défaut: backlog)
  "tags": ["tag-uuid-1"] // optionnel : array d'UUID de tags
}

Réponse 201

{
  "data": {
    "uuid": "...",
    "project_id": 1,
    "title": "Titre",
    "description": null,
    "status": "backlog",
    "created_at": "2026-03-24 20:00:00",
    "updated_at": "2026-03-24 20:00:00",
    "tags": [{ "uuid": "tag-uuid", "name": "bug" }]
  }
}
PUT /api/v1/super-epics/{uuid} Modifier

Scope requis : super-epics:write

Body

{
  "title": "Nouveau titre",  // optionnel
  "description": "...",      // optionnel
  "status": "in_progress",   // optionnel : backlog | in_progress | done
  "tags": ["tag-uuid-1"]     // optionnel : remplace l'ensemble des tags
}

Réponse 200

{
  "data": {
    "uuid": "...",
    "project_id": 1,
    "title": "Nouveau titre",
    "description": null,
    "status": "in_progress",
    "created_at": "2026-03-24 20:00:00",
    "updated_at": "2026-03-24 20:00:00",
    "tags": [{ "uuid": "tag-uuid", "name": "bug" }]
  }
}
DELETE /api/v1/super-epics/{uuid} Supprimer

Scope requis : super-epics:write : les épics liés sont détachés, pas supprimés.

Épics

GET /api/v1/epics?project={uuid} Lister les épics

Scope requis : epics:read

GET /api/v1/epics/{uuid} Détail

Scope requis : epics:read

POST /api/v1/epics Créer

Scope requis : epics:write

Body

{
  "project_uuid": "abc-123",
  "title": "Titre",
  "description": "...",            // optionnel
  "super_epic_uuid": "se-uuid",    // optionnel
  "status": "backlog",             // optionnel : backlog | todo | in_progress | in_review | done | blocked (défaut: backlog)
  "tags": ["tag-uuid-1"]           // optionnel : array d'UUID de tags
}

Réponse 201

{
  "data": {
    "uuid": "...",
    "project_id": 1,
    "super_epic_id": null,
    "super_epic_uuid": null,
    "title": "Titre",
    "description": null,
    "status": "backlog",
    "created_at": "2026-03-24 20:00:00",
    "updated_at": "2026-03-24 20:00:00",
    "tags": [{ "uuid": "tag-uuid", "name": "bug" }]
  }
}
PUT /api/v1/epics/{uuid} Modifier

Scope requis : epics:write

Body

{
  "title": "...",           // optionnel
  "description": "...",     // optionnel
  "super_epic_uuid": null,  // optionnel : uuid ou null pour détacher
  "status": "done",         // optionnel : backlog | todo | in_progress | in_review | done | blocked
  "tags": ["tag-uuid-1"]    // optionnel : remplace l'ensemble des tags
}

Réponse 200

{
  "data": {
    "uuid": "...",
    "project_id": 1,
    "super_epic_id": null,
    "super_epic_uuid": null,
    "title": "...",
    "description": null,
    "status": "done",
    "created_at": "2026-03-24 20:00:00",
    "updated_at": "2026-03-24 20:00:00",
    "tags": [{ "uuid": "tag-uuid", "name": "bug" }]
  }
}
DELETE /api/v1/epics/{uuid} Supprimer

Scope requis : epics:write : les tickets liés sont détachées.

Sprints

GET /api/v1/sprints?project={uuid} Lister les sprints

Scope requis : sprints:read

GET /api/v1/sprints/{uuid} Détail

Scope requis : sprints:read

POST /api/v1/sprints Créer

Scope requis : sprints:write

Body

{
  "project_uuid": "abc-123",
  "title": "Sprint 1",
  "goal": "...",               // optionnel
  "status": "planning",        // optionnel : planning | active | completed (défaut: planning)
  "start_date": "2026-04-01",  // optionnel
  "end_date": "2026-04-14",    // optionnel
  "dod": ["item 1", "item 2"]   // optionnel : Definition of Done du sprint (tableau)
}

Réponse 201

{
  "data": {
    "uuid": "...",
    "project_id": 1,
    "title": "Sprint 1",
    "goal": null,
    "status": "planning",
    "start_date": "2026-04-01",
    "end_date": "2026-04-14",
    "dod": null,
    "created_at": "2026-03-24 20:00:00"
  }
}
PUT /api/v1/sprints/{uuid} Modifier

Scope requis : sprints:write

Body

{
  "title": "Sprint 1",         // optionnel
  "status": "active",          // optionnel : planning | active | completed
  "goal": "...",               // optionnel
  "start_date": "2026-04-01",  // optionnel
  "end_date": "2026-04-14",    // optionnel
  "dod": ["item 1"]            // optionnel : null ou [] pour vider
}

Réponse 200

{
  "data": {
    "uuid": "...",
    "project_id": 1,
    "title": "Sprint 1",
    "goal": null,
    "status": "active",
    "start_date": "2026-04-01",
    "end_date": "2026-04-14",
    "dod": ["item 1"],
    "created_at": "2026-03-24 20:00:00"
  }
}

Tickets

GET /api/v1/tickets?project={uuid} Lister les stories

Scope requis : tickets:read

Filtres optionnels : ?sprint={uuid} et ?epic={uuid}

GET /api/v1/tickets/{uuid} Détail (avec AC, dépendances et sous-tâches)

Scope requis : tickets:read

{
  "data": {
    "uuid": "...", "title": "...", "description": "...",
    "priority": "haute", "status": "todo", "story_points": 5,
    "epic_id": 1, "sprint_id": 2, "assignee": "Alice Martin",
    "project_uuid": "abc-123",    // UUID du projet : clé de jointure
    "epic_uuid": "epic-uuid",     // null si la story n'a pas d'épic
    "sprint_uuid": "sprint-uuid", // null si la story n'est dans aucun sprint
    "acceptance_criteria": ["Critère 1", "Critère 2"],
    "dependencies": [{ "uuid": "...", "type": "story", "title": "Autre story" }], // tickets à terminer AVANT celui-ci (bloqueurs)
    "blocked_by": [{ "uuid": "...", "number": 12, "key": "US-12", "title": "Autre story", "status": "todo" }],
    "blocks": [], "duplicates": [], "duplicated_by": [], "relates": [], // cf. Liens entre tickets
    "tags": [{ "uuid": "tag-uuid", "name": "bug" }],
    "subtasks": [
      { "uuid": "...", "title": "Tâche technique", "status": "todo",
        "sort_order": 1, "assignee_id": 3, "assignee_name": "Alice Martin",
        "created_at": "2026-03-25 10:00:00", "updated_at": "2026-03-25 10:00:00" }
    ],
    "subtasks_total": 1,
    "subtasks_done": 0
  }
}
POST /api/v1/tickets Créer

Scope requis : tickets:write

Body

{
  "project_uuid": "abc-123",
  "title": "En tant qu'utilisateur…",
  "description": "...",
  "priority": "haute",         // critique | haute | normale | faible
  "status": "backlog",         // backlog | todo | in_progress | in_review | done | blocked
  "story_points": 5,           // optionnel
  "epic_uuid": "epic-uuid",    // optionnel
  "sprint_uuid": "sprint-uuid",// optionnel
  "assignee_id": 3,            // optionnel
  "acceptance_criteria": ["Critère 1"], // optionnel
  "dependencies": ["us-uuid-1"],        // optionnel
  "tags": ["tag-uuid-1"]                // optionnel : array d'UUID de tags
}

Réponse 201

{
  "data": {
    "uuid": "...",
    "project_id": 1,
    "epic_id": null,
    "sprint_id": null,
    "assignee_id": null,
    "assignee": null,
    "title": "En tant qu'utilisateur…",
    "description": null,
    "priority": "haute",
    "status": "backlog",
    "story_points": 5,
    "created_at": "2026-03-24 20:00:00",
    "updated_at": "2026-03-24 20:00:00",
    "acceptance_criteria": ["Critère 1"],
    "dependencies": [],
    "tags": [{ "uuid": "tag-uuid", "name": "bug" }]
  }
}
PUT /api/v1/tickets/{uuid} Modifier

Scope requis : tickets:write

Body

{
  "title": "...",               // optionnel
  "description": "...",         // optionnel
  "priority": "haute",          // optionnel : critique | haute | normale | faible
  "status": "in_progress",      // optionnel : backlog | todo | in_progress | in_review | done | blocked
  "story_points": 8,            // optionnel
  "epic_uuid": "epic-uuid",     // optionnel : uuid ou null pour détacher
  "sprint_uuid": "sprint-uuid", // optionnel : uuid ou null pour détacher
  "assignee_id": 3,             // optionnel : id ou null
  "acceptance_criteria": ["Critère 1"], // optionnel : remplace les critères existants
  "dependencies": ["us-uuid-1"],        // optionnel : remplace les tickets dont celui-ci dépend. Boucle refusée : 409
  "tags": ["tag-uuid-1"]                // optionnel : remplace l'ensemble des tags
}

Réponse 200

{
  "data": {
    "uuid": "...",
    "project_id": 1,
    "epic_id": null,
    "sprint_id": null,
    "assignee_id": null,
    "assignee": null,
    "title": "...",
    "description": null,
    "priority": "haute",
    "status": "in_progress",
    "story_points": 8,
    "created_at": "2026-03-24 20:00:00",
    "updated_at": "2026-03-24 20:00:00",
    "acceptance_criteria": ["Critère 1"],
    "dependencies": [],
    "tags": [{ "uuid": "tag-uuid", "name": "bug" }]
  }
}
DELETE /api/v1/tickets/{uuid} Supprimer

Scope requis : tickets:write

Cinq relations, lues depuis le ticket de l'URL : blocked_by (l'autre ticket doit être terminé avant), blocks (l'autre attend celui-ci), duplicates (celui-ci est un doublon de l'autre, qui est l'original), duplicated_by et relates (lien sans sens). Un lien s'enregistre immédiatement et apparaît des deux côtés.

Refus : un lien qui créerait une boucle de blocage (409 CONFLICT), deux tickets qui se dupliquent mutuellement (409), un ticket lié à lui-même ou une relation inconnue (422), un ticket d'un autre projet (404).

GET /api/v1/tickets/{uuid}/links Liens d'un ticket

Scope requis : tickets:read

{
  "data": {
    "blocked_by": [{ "uuid": "...", "type": "story", "number": 12, "key": "US-12", "title": "Prérequis", "status": "todo" }],
    "blocks": [],
    "duplicates": [],
    "duplicated_by": [],
    "relates": []
  }
}
POST /api/v1/tickets/{uuid}/links Lier deux tickets

Scope requis : tickets:write · permission edit_us dans le projet

Body

{
  "other_uuid": "us-uuid",   // requis : ticket du même projet
  "kind": "duplicates"       // requis : blocked_by | blocks | duplicates | duplicated_by | relates
}

Réponse 201 : les cinq listes à jour, comme le GET. Rejouer un lien existant ne fait rien.

DELETE /api/v1/tickets/{uuid}/links Retirer un lien

Scope requis : tickets:write · permission edit_us dans le projet

Même body que le POST (ou ?other_uuid=…&kind=… en query). Réponse 200 : les cinq listes à jour.

Historique d'un ticket

GET /api/v1/tickets/{uuid}/history Qui a changé quoi, et d'où

Scope requis : tickets:read

Changements du plus récent au plus ancien. Un changement regroupe les champs modifiés par une même action. source vaut app, api, mcp, import ou le tracker / fournisseur Git qui a poussé le changement. Pagination facultative (?page, ?per_page, 200 au plus).

{
  "data": [
    {
      "change_id": "c0a8…",
      "created_at": "2026-10-27 14:02:11",
      "source": "api",
      "actor": { "user_id": 42, "uuid": "user-uuid", "name": "Camille Martin" },
      "items": [
        { "field": "status", "old_value": "todo", "new_value": "in_progress",
          "old_label": "À faire", "new_label": "En cours" }
      ]
    }
  ],
  "meta": { "total": 1, "count": 1 }
}

Sous-tâches

Les sous-tâches ont des statuts fixes (todo, in_progress, done), indépendants des statuts personnalisés du projet. Elles représentent une liste de contrôle technique attaché à un ticket.

GET /api/v1/subtasks?ticket_uuid={uuid} Lister les sous-tâches d'un ticket

Scope requis : tickets:read

{
  "data": [
    {
      "uuid": "...",
      "title": "Tâche technique",
      "status": "todo",           // todo | in_progress | done
      "sort_order": 1,
      "assignee_id": 3,
      "assignee_name": "Alice Martin",
      "created_at": "2026-03-25 10:00:00",
      "updated_at": "2026-03-25 10:00:00"
    }
  ],
  "meta": { "total": 1 }
}
POST /api/v1/subtasks Créer une sous-tâche

Scope requis : tickets:write

Body

{
  "ticket_uuid": "us-uuid",        // requis
  "title": "Tâche technique",  // requis
  "status": "todo",            // optionnel : todo | in_progress | done (défaut: todo)
  "assignee_id": 3             // optionnel
}

Réponse 201

{
  "data": {
    "uuid": "...", "title": "Tâche technique", "status": "todo",
    "sort_order": 1, "assignee_id": null, "assignee_name": null,
    "created_at": "...", "updated_at": "..."
  }
}
PUT /api/v1/subtasks/{uuid} Modifier

Scope requis : tickets:write

Body

{
  "title": "...",          // optionnel
  "status": "in_progress", // optionnel : todo | in_progress | done
  "assignee_id": 3,        // optionnel : id ou null pour désassigner
  "sort_order": 2          // optionnel
}

Réponse 200

{
  "data": {
    "uuid": "...", "title": "...", "status": "in_progress",
    "sort_order": 2, "assignee_id": 3, "assignee_name": "Alice Martin",
    "created_at": "...", "updated_at": "..."
  }
}
DELETE /api/v1/subtasks/{uuid} Supprimer

Scope requis : tickets:write

Commentaires

Les commentaires sont attachés à un ticket. Seul l'auteur (ou un admin) peut modifier ou supprimer ses propres commentaires. Le contenu est du HTML sanitisé (scripts et handlers JS retirés).

GET /api/v1/comments?ticket_uuid={uuid} Lister les commentaires d'un ticket

Scope requis : comments:read

ParamètreTypeDescription
usstringUUID du ticket : requis

Réponse 200

{
  "data": [
    {
      "uuid": "...",
      "author_id": 3,
      "author_name": "Alice Martin",
      "author_initials": "AM",
      "content": "<p>Texte du commentaire</p>",
      "mentions": [4, 7],
      "ticket_uuid": "...",
      "created_at": "2026-04-02 10:00:00",
      "updated_at": "2026-04-02 10:00:00"
    }
  ],
  "meta": { "total": 1 }
}
GET /api/v1/comments/{uuid} Détail d'un commentaire

Scope requis : comments:read

POST /api/v1/comments Créer un commentaire

Scope requis : comments:write

Body

{
  "ticket_uuid":  "...",                     // requis : UUID du ticket
  "content":  "<p>Mon commentaire</p>",  // requis : HTML sanitisé
  "mentions": [4, 7]                     // optionnel : IDs utilisateurs mentionnés
}

Réponse 201

{
  "data": {
    "uuid": "...",
    "author_id": 3,
    "author_name": "Alice Martin",
    "author_initials": "AM",
    "content": "<p>Mon commentaire</p>",
    "mentions": [4],
    "ticket_uuid": "...",
    "created_at": "2026-04-02 10:00:00",
    "updated_at": "2026-04-02 10:00:00"
  }
}
PUT /api/v1/comments/{uuid} Modifier un commentaire

Scope requis : comments:write : auteur ou admin uniquement.

Body

{
  "content":  "<p>Contenu modifié</p>",  // optionnel
  "mentions": [4, 7]                       // optionnel : remplace la liste complète
}
DELETE /api/v1/comments/{uuid} Supprimer un commentaire

Scope requis : comments:write : auteur ou admin uniquement.

{ "data": { "deleted": true } }

Équipe

GET /api/v1/team?project={uuid} Membres du projet

Scope requis : team:read

{
  "data": [
    {
      "uuid": "...", "firstname": "Alice", "lastname": "Martin",
      "email": "alice@example.com", "scrum_role": "dev",
      "project_role": "member", "status": "active",
      "tjm": 550.00, "service": "Delivery"
    }
  ],
  "meta": { "total": 4 }
}

tjm = taux journalier moyen (coût/jour) du membre sur ce projet, base des indicateurs finance ; null si non renseigné.

Backlog en une transaction

POST /api/v1/backlog Créer Super Epic, epics et tickets en tout-ou-rien

Scopes requis : tickets:write, plus epics:write si l'appel crée des epics et super-epics:write s'il crée un Super Epic. Le rôle doit avoir les droits de création correspondants dans le projet.

Le moteur de Launchpad, ouvert à l'API : tout est créé dans une seule transaction. Au moindre problème (type de ticket inconnu, points hors de l'échelle du Planning Poker 0, 1, 2, 3, 5, 8, 13, 21, UUID absent du projet, titre vide…), rien n'est créé et la réponse 422 liste tous les problèmes. Les tickets arrivent au statut backlog, hors sprint. Limites : 40 epics, 300 tickets et 30 critères par ticket par appel.

Chaque epic est soit existant (epic_uuid : les tickets y sont ajoutés), soit nouveau (title) : il est alors rattaché au Super Epic créé par l'appel s'il y en a un, ou au Super Epic existant indiqué par super_epic_uuid.

Idempotence : envoyez un commit_token (UUID). Le même jeton renvoyé ne crée rien une seconde fois et répond 200 avec "idempotent": true. Aucune synchro vers Jira, Azure Boards ou Linear (comme Launchpad) : tracker_not_synced le signale quand le projet est relié à un tracker.

Body

{
  "project_uuid": "abc-123",                 // requis
  "commit_token": "3f2b…-uuid",              // optionnel, recommandé
  "super_epic": { "title": "Portail client", "description": "<p>…</p>" },   // optionnel
  "epics": [
    { "title": "Authentification",
      "tickets": [
        { "title": "Se connecter par e-mail", "type": "story", "priority": "haute",
          "story_points": 3, "description": "<p>…</p>",
          "acceptance_criteria": ["Lien magique envoyé", "Expire en 15 min"] }
      ] },
    { "epic_uuid": "epic-existant-uuid",
      "tickets": [ { "title": "Connexion SSO", "story_points": 8 } ] }
  ]
}

Réponse 201

{
  "data": {
    "super_epic_uuid": "…",
    "n_epics": 1,
    "n_tickets": 2,
    "n_epics_touched": 2,
    "created": {
      "super_epic": { "uuid": "…", "title": "Portail client" },
      "epics": [
        { "uuid": "…", "title": "Authentification", "tickets": [ { "uuid": "…", "title": "Se connecter par e-mail" } ] },
        { "uuid": "epic-existant-uuid", "existing": true, "tickets": [ { "uuid": "…", "title": "Connexion SSO" } ] }
      ]
    },
    "commit_token": "3f2b…-uuid"
  }
}

Réponse 422

{ "error": { "code": "VALIDATION_ERROR",
  "message": "2 problem(s), nothing was created: epics[0].tickets[1].type: unknown type 'task' (valid: story, bug); epics[1].epic_uuid: no epic with this UUID in the project." } }

Entrées de temps

GET /api/v1/time-entries?project={uuid} Lister les temps

Scope requis : time:read

Filtres optionnels : ?story={uuid} pour une story spécifique, ?user={user_id} pour les saisies d’un membre, ?from=YYYY-MM-DD et ?to=YYYY-MM-DD pour le jour du travail (bornes incluses).

POST /api/v1/time-entries Enregistrer du temps

Scope requis : time:write

Body

{
  "ticket_uuid": "story-uuid",      // requis
  "duration_minutes": 90,       // requis
  "logged_at": "2026-03-24",    // requis, format YYYY-MM-DD
  "comment": "Revue de code"    // optionnel
}

Réponse 201

{
  "data": {
    "uuid": "...",
    "ticket_id": 12,
    "user_id": 3,
    "user_name": "Alice Martin",
    "ticket_uuid": "...",
    "ticket_title": "En tant qu'utilisateur…",
    "duration_minutes": 90,
    "comment": "Revue de code",
    "logged_at": "2026-03-24 00:00:00",
    "created_at": "2026-03-24 20:00:00"
  }
}

Statuts

GET /api/v1/statuses?project={uuid} Lister les statuts d'un projet

Scope requis : statuses:read

Retourne tous les statuts du projet (par défaut et personnalisés), triés par sort_order.

Réponse 200

[
  {
    "key":        "backlog",
    "label":      "Backlog",
    "color":      "#64748b",
    "bg_color":   "#f1f5f9",
    "sort_order": 1,
    "is_done":    false,
    "is_default": true
  },
  {
    "key":        "in_validation",
    "label":      "En validation",
    "color":      "#7c3aed",
    "bg_color":   "#f5f3ff",
    "sort_order": 7,
    "is_done":    false,
    "is_default": false
  }
]

Tags

Étiquettes réutilisables d'un projet. Pour poser des tags sur un ticket, un épic ou un super-épic, envoyez leur liste d'UUID dans le champ tags de ces ressources. Écriture : owner / admin du projet, ou rôle disposant de la permission manage_tags (tous les rôles par défaut).

GET /api/v1/tags?project={uuid} Lister les tags

Scope requis : tickets:read · GET /api/v1/tags/{uuid} pour un seul tag

{
  "data": [ { "uuid": "tag-uuid", "name": "Front-end", "slug": "front-end", "usage_count": 12 } ],
  "meta": { "total": 1 }
}
POST /api/v1/tags Créer un tag

Scope requis : tickets:write

{ "project_uuid": "proj-uuid", "name": "Front-end" }   // 50 caractères au plus

Réponse 201 : le tag. 409 CONFLICT si un tag de même nom (casse et ponctuation ignorées) existe déjà.

PUT /api/v1/tags/{uuid} Renommer un tag

Scope requis : tickets:write · body { "name": "…" }

DELETE /api/v1/tags/{uuid} Supprimer un tag

Scope requis : tickets:write · le tag est retiré de tous les tickets, épics et super-épics qui le portaient.

Types de ticket

Le slug d'un type est la valeur du champ type d'un ticket ; l'abbreviation préfixe sa clé (US-42). Écriture : owner / admin du projet, ou rôle disposant de la permission manage_ticket_types.

GET /api/v1/ticket-types?project={uuid} Lister les types

Scope requis : projects:read

{
  "data": [
    { "uuid": "…", "name": "User Story", "slug": "story", "abbreviation": "US", "is_default": true, "sort_order": 1 }
  ],
  "meta": { "total": 1 }
}
POST /api/v1/ticket-types Créer un type

Scope requis : projects:write

{ "project_uuid": "proj-uuid", "name": "Spike", "abbreviation": "SPK" }

L'abréviation est mise en majuscules, 10 caractères au plus, unique dans le projet (409) ; SE, EP et SP sont réservées (422).

PUT /api/v1/ticket-types/{uuid} Modifier nom ou abréviation

Scope requis : projects:write · body partiel { "name": "…", "abbreviation": "…" }. Le slug ne change pas.

DELETE /api/v1/ticket-types/{uuid} Supprimer un type

Scope requis : projects:write · les tickets de ce type repassent en story. Un type par défaut ne se supprime pas (403).

Règles de transition

GET /api/v1/workflows?project={uuid} Règles du projet et passages qui vous sont interdits

Scope requis : projects:read · &entity_type=ticket|epic|super_epic pour un seul type d'entité

forbidden_transitions liste, pour le titulaire de la clé, les changements de statut que les règles lui refusent : { statut_départ: { statut_arrivée: motif } }. Un client sait ainsi d'avance ce qu'il ne pourra pas faire, au lieu de le découvrir par un 403 WORKFLOW_BLOCKED. Les validations qui dépendent d'un objet précis (sous-tâches terminées, champs requis, verdict de recette) n'y figurent pas : elles ne se vérifient qu'au moment du PUT. Les owner / admin du projet ne sont pas soumis aux conditions (bypasses_conditions). La configuration des règles se fait dans l'application.

{
  "data": {
    "rules": [
      { "uuid": "…", "entity_type": "ticket", "status_from": null, "status_to": "done",
        "rule_type": "condition", "action_type": "require_role",
        "config": { "roles": ["product_owner"] }, "sort_order": 1, "enabled": true }
    ],
    "forbidden_transitions": {
      "ticket": { "review": { "done": { "reason": "role", "roles": ["product_owner"] } } },
      "epic": {},
      "super_epic": {}
    },
    "bypasses_conditions": false
  },
  "meta": { "total": 1 }
}

Dépendances

Liens finish-to-start entre items de roadmap (épic ou super-épic). Chaque nœud est exposé par son UUID (joignable avec /epics et /super-epics) pour reconstruire le graphe et, côté consommateur, en dériver le chemin critique.

GET /api/v1/dependencies?project={uuid} Lister les dépendances

Scope requis : roadmap:read

{
  "data": [
    {
      "uuid": "dep-uuid",
      "type": "FS",               // finish-to-start
      "pred_type": "epic",        // epic | super_epic
      "pred_uuid": "epic-uuid-1",
      "succ_type": "epic",
      "succ_uuid": "epic-uuid-2"
    }
  ],
  "meta": { "total": 1 }
}
POST /api/v1/dependencies Créer une dépendance

Scope requis : roadmap:write · owner / admin du projet

{
  "project_uuid": "proj-uuid",
  "pred_type": "epic",          // epic | super_epic : doit finir avant…
  "pred_uuid": "epic-uuid-1",
  "succ_type": "epic",          // …que celui-ci commence
  "succ_uuid": "epic-uuid-2"
}

Réponse 201 : la dépendance. Refus : lien sur soi-même (422), doublon ou cycle (409 CONFLICT), épic d'un autre projet (404). Les liens « bloque / est bloqué par » entre tickets passent par /tickets/{uuid}/links.

DELETE /api/v1/dependencies/{uuid} Supprimer une dépendance

Scope requis : roadmap:write · owner / admin du projet

Jalons

GET /api/v1/milestones?project={uuid} Lister les jalons datés

Scope requis : roadmap:read

{
  "data": [
    { "uuid": "...", "name": "Go-live", "date": "2026-09-30", "color": "#e11d48" }
  ],
  "meta": { "total": 1 }
}
POST /api/v1/milestones Créer un jalon

Scope requis : roadmap:write · owner / admin du projet

{
  "project_uuid": "proj-uuid",
  "name": "Go-live",            // requis, 120 caractères au plus
  "date": "2026-12-01",         // requis, YYYY-MM-DD
  "color": "#e11d48"            // facultatif, #rgb ou #rrggbb
}

Réponse 201 : le jalon.

PUT /api/v1/milestones/{uuid} Modifier un jalon

Scope requis : roadmap:write · body partiel : seuls les champs envoyés (name, date, color) changent.

DELETE /api/v1/milestones/{uuid} Supprimer un jalon

Scope requis : roadmap:write · owner / admin du projet

Pièces jointes des tickets

Même barrière que le formulaire de l'interface : 10 Mo au plus par fichier, quota de stockage du projet, type réel détecté sur le contenu (jamais le nom ni l'en-tête envoyés). SVG, HTML, scripts et exécutables sont refusés. Écrire demande la permission edit_us ou create_us (owner / admin toujours).

GET /api/v1/tickets/{uuid}/attachments Lister les pièces jointes d'un ticket

Scope requis : tickets:read

{
  "data": [
    { "uuid": "...", "ticket_uuid": "...", "name": "export.csv", "size": 2048,
      "type": "text/csv", "downloadable": true, "created_at": "..." }
  ],
  "meta": { "total": 1 }
}

downloadable: false : pièce jointe restée chez le tracker connecté (Jira, Azure Boards), sans copie locale.

POST /api/v1/tickets/{uuid}/attachments Déposer un fichier

Scope requis : tickets:write · permission edit_us ou create_us

En multipart/form-data, champ file :

curl -X POST https://manifst.net/api/v1/tickets/{uuid}/attachments \
  -H "Authorization: Bearer mfst_live_..." \
  -F "file=@export.csv"

Ou en JSON, contenu en base64 (une data URL est acceptée) :

{ "name": "capture.png", "content_base64": "iVBORw0KGgo..." }

Réponse 201 : la pièce jointe. 413 si le fichier dépasse 10 Mo ou si le quota du projet est atteint (STORAGE_QUOTA_REACHED).

GET /api/v1/attachments/{uuid} Métadonnées d'une pièce jointe

Scope requis : tickets:read

GET /api/v1/attachments/{uuid}/download Télécharger le fichier

Scope requis : tickets:read · renvoie le fichier lui-même (Content-Disposition: attachment), pas du JSON.

DELETE /api/v1/attachments/{uuid} Supprimer une pièce jointe

Scope requis : tickets:write · le fichier est effacé du disque avec la ligne.

Documentation (MyDoc)

Espaces et pages MyDoc, avec les droits de l'interface : la section MyDoc doit être ouverte au rôle du titulaire de la clé dans le projet, puis sa permission sur l'espace s'applique. L'owner du projet est admin partout. Une permission explicite fait foi. Un espace public donne edit à tout membre, un espace privé rien. Lire demande read, écrire edit.

Le contenu s'envoie en HTML (content, nettoyé par le sanitiseur de l'appli) ou en Markdown (content_markdown). Il se lit en HTML, en Markdown ou les deux (?format=html|markdown|both).

GET /api/v1/docs/spaces?project={uuid} Espaces lisibles d'un projet

Scope requis : docs:read

{
  "data": [
    { "uuid": "...", "name": "Spécifications", "description": "", "icon": "bi-journal-text",
      "color": "#6366f1", "is_public": true, "page_count": 12, "my_permission": "edit", "updated_at": "..." }
  ],
  "meta": { "total": 1 }
}
GET /api/v1/docs/spaces/{uuid}/pages Pages d'un espace, sans leur contenu

Scope requis : docs:read · liste à plat : les pages de premier niveau d'abord, puis les sous-pages (parent_uuid). Les pages archivées sont exclues, sauf avec ?include_archived=1.

{ "data": [ { "uuid": "...", "parent_uuid": null, "title": "Export comptable", "emoji": null,
              "status": "published", "sort_order": 0, "word_count": 240, "rev": 7,
              "updated_at": "...", "updated_by_name": "Jeanne Martin" } ] }
GET /api/v1/docs/pages/{uuid}?format=markdown Lire une page

Scope requis : docs:read

{
  "data": {
    "uuid": "...", "space_uuid": "...", "space_name": "Spécifications", "project_uuid": "...",
    "parent_uuid": null, "title": "Export comptable", "emoji": null, "status": "published",
    "rev": 7,
    "content_markdown": "### Objectif\n\nProduire un **CSV** mensuel…",
    "word_count": 240, "labels": ["finance"],
    "created_by": { "uuid": "...", "name": "Jeanne Martin" },
    "updated_by": { "uuid": "...", "name": "Bob Dev" },
    "created_at": "...", "updated_at": "...", "my_permission": "edit"
  }
}

Gardez rev : il est exigé pour réécrire le contenu.

POST /api/v1/docs/pages Créer une page

Scope requis : docs:write · permission edit sur l'espace

{
  "space_uuid": "space-uuid",
  "parent_uuid": "page-uuid",        // facultatif : sous-page (2 niveaux au plus)
  "title": "Export comptable",       // requis, 500 caractères au plus
  "emoji": "📄",                     // facultatif
  "status": "published",             // published (défaut), draft, archived
  "content_markdown": "## Objectif\n\nProduire un **CSV** mensuel."   // ou "content" en HTML
}

Réponse 201 : la page, avec son rev.

PUT /api/v1/docs/pages/{uuid} Modifier une page

Scope requis : docs:write · body partiel : title, emoji, status, content ou content_markdown.

{ "content_markdown": "…", "base_rev": 7 }

Réécrire le contenu exige base_rev, le rev de la page lue (ou l'en-tête If-Match: 7). Sans lui : 428. Si la page a changé entre-temps (un humain en co-édition, un autre agent) : 409 CONFLICT et rien n'est écrit. Il faut alors relire la page, refusionner et renvoyer avec le nouveau rev. Une écriture acceptée archive la version précédente dans l'historique de la page, et les onglets ouverts dessus se rechargent.

Si quelqu'un co-édite la page et que ses dernières modifications ne sont pas encore enregistrées, la réponse est 409 PAGE_BEING_EDITED et rien n'est écrit : réessayer une minute plus tard. Ses frappes ne sont jamais écrasées.

Titre, emoji et statut seuls ne demandent pas de base_rev.

DELETE /api/v1/docs/pages/{uuid} Supprimer une page

Scope requis : docs:write · supprime aussi ses sous-pages et les fichiers joints. Pour la retirer sans la perdre, préférer "status": "archived".

GET /api/v1/docs/pages/{uuid}/attachments · /docs/attachments/{uuid}/download Fichiers joints d'une page

Scope requis : docs:read · liste (uuid, name, type, size, created_at, created_by_name), puis téléchargement. Le dépôt de fichiers dans une page reste réservé à l'interface.

Recette (QA)

Le cadre de recette des tickets, avec les règles de l'écran :

Écrire demande les permissions perform_qa et edit_us (owner / admin toujours). Le verdict ne déplace pas le ticket : changez ensuite son statut par PUT /tickets/{uuid}. C'est là que s'appliquent les règles de transition, dont « recette acceptée exigée ».

GET /api/v1/tickets/{uuid}/qa Passages de recette d'un ticket

Scope requis : tickets:read

{
  "data": {
    "ticket_uuid": "...", "status": "in_review", "column_type": "review",
    "can_perform": true, "accepted": false,
    "sessions": [
      { "uuid": "...", "round": 1, "verdict": "open", "tester_name": "Jeanne Martin",
        "version_label": "1.4.0", "environment": "recette",
        "counts": { "pending": 1, "passed": 1, "failed": 0, "untestable": 0 },
        "checks": [
          { "uuid": "...", "source": "ac", "label": "Les montants sont en euros",
            "result": "pending", "note": null, "orphan": false, "...": "..." }
        ] }
    ]
  }
}

result : pending, passed, failed, untestable. orphan : point dont le critère a été retiré du ticket depuis.

POST /api/v1/tickets/{uuid}/qa Démarrer un passage

Scope requis : tickets:write · body facultatif { "version_label": "1.4.0", "environment": "recette" }.

Idempotent : si un passage est déjà ouvert, il est rendu tel quel (200) ; sinon un nouveau tour est ouvert (201). Deux agents qui démarrent en même temps travaillent sur le même passage.

PUT /api/v1/qa/sessions/{uuid}/checks Enregistrer des résultats, par lot

Scope requis : tickets:write

{
  "checks": [
    { "uuid": "check-1", "result": "passed" },
    { "uuid": "check-2", "result": "failed", "note": "Montant en dollars, ligne 12" }
  ]
}

200 points au plus, tout ou rien. Un point seul : PUT /qa/checks/{uuid} avec { "result", "note" }.

POST /api/v1/qa/sessions/{uuid}/checks Ajouter un point libre

Scope requis : tickets:write · { "label": "Export de plus de 10 000 lignes" }. Seuls les points libres se suppriment (DELETE /qa/checks/{uuid}) ; un critère ou un point de DoD se marque untestable.

PUT /api/v1/qa/sessions/{uuid} Version, environnement, testeur

Scope requis : tickets:write · version_label, environment, tester_uuid (membre du projet).

POST /api/v1/qa/sessions/{uuid}/close Rendre le verdict

Scope requis : tickets:write · { "verdict": "accepted" } ou "rejected".

422 si la règle du verdict n'est pas remplie (le message donne le nombre de points à contrôler et refusés) ; 409 si le passage est déjà clos. Un passage clos ne se modifie plus : démarrer un nouveau tour.

Rituels du sprint

Les mêmes calculs que les écrans Sprint Review et Daily : aucun second calcul, les chiffres sont ceux que l'équipe voit.

GET /api/v1/sprints/{uuid}/review Sprint Review complète

Scope requis : sprints:read

Engagé / livré, vélocité des sprints précédents, burndown, périmètre ajouté ou retiré, tickets reportés, avancement des epics, jalons, risques liés, coût EVM du sprint. Les blocs finance et risks valent null si le titulaire de la clé n'y a pas accès (view_financial, section Risques), comme à l'écran ; access dit lesquels.

PUT /api/v1/sprints/{uuid}/review Notes et verdict sur le Sprint Goal

Scope requis : sprints:write · permission run_review (Product Owner et Scrum Master par défaut ; can_review dans la lecture dit si la clé l'a)

{
  "goal_met": "partial",                       // yes, partial, no, ou null (non évalué)
  "goal_comment": "Export livré sans les avoirs",
  "review_notes": "Démo validée par le client."
}

Partiel : enregistrer le verdict n'efface pas les notes.

GET /api/v1/sprints/{uuid}/standup Tableau du daily du jour

Scope requis : sprints:read · section Sprints ouverte au rôle

Avancement du sprint, points à régler (tickets bloqués, en cours depuis plus de 3 jours sans mouvement, file de recette, charge concentrée), tour de table par personne (ce qui a bougé depuis le dernier jour ouvré, ce qui est en cours). closed_today : le daily déjà clôturé aujourd'hui, ou null. La clôture (compte rendu envoyé à l'équipe) reste un geste de l'interface.

GET /api/v1/sprints/{uuid}/retro Éléments de la rétrospective

Scope requis : sprints:read · section Sprints + permission edit_sprint, comme l'écran de rétro

{
  "data": [ { "uuid": "...", "category": "action", "content": "Ajouter les avoirs", "done": false, "created_at": "..." } ],
  "meta": { "total": 1, "by_category": { "went_well": 0, "problem": 0, "action": 1 } }
}
POST /api/v1/sprints/{uuid}/retro · PUT/DELETE /retro-items/{uuid} Ajouter, modifier, supprimer un élément

Scope requis : sprints:write · permission edit_sprint

// POST /sprints/{uuid}/retro
{ "category": "problem", "content": "Recette tardive" }     // went_well, problem, action
// PUT /retro-items/{uuid}
{ "done": true }                                              // ou "content"

Indicateurs de flux

GET /api/v1/metrics/flow?project={uuid} Flux, qualité, périmètre

Scope requis : tickets:read

Le calcul des blocs Flux, Qualité et Périmètre du tableau de bord, sur 90 jours : débit par semaine, cycle time et lead time, travail en cours et son âge, taux de défauts, résultats de recette. available: false tant que l'historique des tickets n'existe pas.

ParamètreDescription
cfd=1Flux cumulé jour par jour
sprints=1Périmètre des derniers sprints (ajouté, retiré, livré)
forecast=1Prévision de fin par simulation Monte-Carlo (palier Portefeuille)
projects=uuid1,uuid2Consolidation de plusieurs projets (palier Portefeuille). Les projets dont le titulaire n'est pas membre sont ignorés. Ni cfd, ni sprints, ni forecast : ils sont propres à une équipe.

Risques

Registre des risques du projet. La sévérité est calculée (probabilité × impact, de 1 à 25) : low jusqu'à 4, medium jusqu'à 9, high jusqu'à 14, critical au-delà. Lecture : la section Risques doit être ouverte au rôle (la même règle que le bloc risques de la Sprint Review). Écriture : palier Team et plus, owner / admin du projet ou permission manage_risks. Une valeur hors domaine est refusée (422), jamais bornée en silence.

GET /api/v1/risks?project={uuid} Lister les risques, du plus grave au moins grave

Scope requis : governance:read

ParamètreDescription
statusopen, mitigating, monitored, closed, occurred (plusieurs séparés par des virgules)
categorytechnique, delai, budget, ressources, externe, securite, qualite
severitylow, medium, high, critical
entity_type + entity_uuidRisques liés à un ticket, une epic ou une super_epic
updated_sinceModifiés depuis cette date (YYYY-MM-DD ou YYYY-MM-DD HH:MM:SS)
{
  "data": [
    {
      "uuid": "...", "title": "Retard API Stripe", "description": "",
      "category": "externe", "probability": 4, "impact": 5,
      "severity_score": 20, "severity_level": "critical",
      "status": "open", "owner_uuid": "...", "owner_name": "Jeanne Martin", "owner_initials": "JM",
      "mitigation": "Plan B : bouchon", "contingency": "", "due_date": "2026-12-01",
      "links": [ { "entity_type": "ticket", "uuid": "...", "title": "Migration paiement" } ],
      "created_at": "...", "updated_at": "..."
    }
  ],
  "meta": { "total": 1, "categories": ["technique", "delai", "..."] }
}
GET /api/v1/risks/{uuid} Lire un risque

Scope requis : governance:read

POST /api/v1/risks Créer un risque

Scope requis : governance:write · permission manage_risks

{
  "project_uuid": "proj-uuid",
  "title": "Retard API Stripe",     // requis, 160 caractères au plus
  "category": "externe",            // requis
  "probability": 4,                 // entier 1 à 5, défaut 3
  "impact": 5,                      // entier 1 à 5, défaut 3
  "status": "open",                 // défaut open
  "owner_uuid": "user-uuid",        // membre du projet, facultatif
  "due_date": "2026-12-01",
  "description": "...",             // 5 000 caractères au plus
  "mitigation": "...",              // 2 000 caractères au plus
  "contingency": "...",             // 2 000 caractères au plus
  "links": [ { "entity_type": "ticket", "uuid": "ticket-uuid" } ]
}

Réponse 201 : le risque. Une cible de links introuvable dans le projet refuse tout l'appel.

PUT /api/v1/risks/{uuid} Modifier un risque

Scope requis : governance:write · body partiel. links remplace la liste entière ([] les retire) ; owner_uuid: null retire le porteur.

DELETE /api/v1/risks/{uuid} Supprimer un risque

Scope requis : governance:write · permission manage_risks

Matrice RACI

Phases → tâches en lignes, participants en colonnes (membres du projet ou personnes externes sans compte), un rôle par case : R, A, C, I ou C/I. Mêmes droits que les risques : section RACI ouverte au rôle, et permission manage_raci pour écrire.

GET /api/v1/raci?project={uuid} Lire la matrice complète

Scope requis : governance:read

{
  "data": {
    "participants": [
      { "participant_uuid": "p1", "user_uuid": "...", "is_external": false, "name": "Jeanne Martin", "initials": "JM", "...": "..." },
      { "participant_uuid": "p2", "user_uuid": null,  "is_external": true,  "name": "Client", "initials": "CL", "...": "..." }
    ],
    "phases": [
      { "uuid": "...", "name": "Cadrage", "color": "#6366f1", "position": 0,
        "tasks": [ { "uuid": "t1", "title": "Valider le budget", "position": 0,
                     "cells": { "p1": "A", "p2": "C/I" } } ] }
    ],
    "can_edit": true
  }
}

cells est indexé par participant_uuid ; une case vide n'y figure pas. can_edit dit si le titulaire de la clé peut écrire.

POST /api/v1/raci/phases · /raci/tasks · /raci/participants Ajouter une phase, une tâche, un participant

Scope requis : governance:write · permission manage_raci

// POST /raci/phases
{ "project_uuid": "proj-uuid", "name": "Cadrage", "color": "#6366f1", "position": 0 }
// POST /raci/tasks
{ "phase_uuid": "phase-uuid", "title": "Valider le budget", "position": 0 }
// POST /raci/participants : un membre…
{ "project_uuid": "proj-uuid", "user_uuid": "user-uuid" }
// …ou une personne externe
{ "project_uuid": "proj-uuid", "name": "Jeanne Client" }

Réponse 201 : l'élément créé. position (0 = en tête) est facultatif : par défaut, à la fin. Un membre déjà présent renvoie 409.

PUT /api/v1/raci/phases/{uuid} · /raci/tasks/{uuid} · /raci/participants/{uuid} Renommer, recolorer, déplacer

Scope requis : governance:write · phase : name, color, position ; tâche : title, position ; participant : name, pour une personne externe seulement (un membre garde le nom de son compte).

PUT /api/v1/raci/cells Remplir des cases, par lot

Scope requis : governance:write · permission manage_raci

{
  "cells": [
    { "task_uuid": "t1", "participant_uuid": "p1", "role": "A" },
    { "task_uuid": "t1", "participant_uuid": "p2", "role": "C/I" },
    { "task_uuid": "t2", "participant_uuid": "p1", "role": null }   // null ou "" vide la case
  ]
}

500 cases au plus, toutes du même projet. Tout ou rien : une case invalide refuse le lot entier. Réponse : la matrice à jour, et meta.updated. Une case seule peut aussi s'envoyer à plat : { "task_uuid", "participant_uuid", "role" }.

DELETE /api/v1/raci/phases/{uuid} · /raci/tasks/{uuid} · /raci/participants/{uuid} Supprimer

Scope requis : governance:write · supprimer une phase supprime ses tâches et leurs cases ; retirer un participant supprime sa colonne.

GET /api/v1/search?project={uuid}&q={terme} Chercher dans un projet

Scope requis : projects:read, plus le scope de lecture de chaque type cherché.

Le moteur de la recherche de l'interface : titres, descriptions, clés (US-12, EP-3, SE-1, y compris l'ancien numéro global d'un ticket), objectifs de sprint, pages MyDoc (dans les espaces lisibles), noms et e-mails des membres.

ParamètreDescription
q2 à 200 caractères
typessuper_epic (super-epics:read), epic (epics:read), sprint (sprints:read), ticket (tickets:read), doc (docs:read), user (team:read). Défaut : tous.
limitRésultats par type, 1 à 50 (défaut 10)
{
  "data": [
    { "type": "ticket", "uuid": "...", "key": "US-12", "title": "Migration paiement", "status": "in_progress", "matched_legacy_key": false },
    { "type": "user", "uuid": "...", "user_id": 42, "title": "Jeanne Martin", "email": "jeanne@exemple.fr" }
  ],
  "meta": { "total": 2, "types_searched": ["super_epic", "epic", "sprint", "ticket", "user"] }
}

Un type dont la clé n'a pas le scope de lecture n'est pas cherché : il figure dans meta.types_skipped. user_id est l'identifiant attendu par mentions dans les commentaires.

Notifications

La cloche du titulaire de la clé, tous projets confondus : mentions, assignations, désignations de responsable, changements de statut… Ce sont les notifications de l'interface, aux mêmes conditions (périmées et projets quittés exclus). Une clé ne voit jamais les notifications d'un autre compte.

GET /api/v1/notifications Lister ses notifications, les plus récentes d'abord

Scope requis : notifications:read

ParamètreDescription
unread1 : non lues seulement
typeUn ou plusieurs types séparés par des virgules : mention, doc_mention, story_assigned, lot_owner_assigned, ticket_updated, poker_started…
projectUUID d'un projet
sinceArrivées depuis cette date (heure du serveur)
langfr ou en pour message ; défaut : la langue du compte
page, per_pagePagination, 50 par défaut, 200 au plus
{
  "data": [
    {
      "uuid": "...", "type": "mention",
      "message": "Bob Dev vous a mentionné sur « Migration paiement »",
      "params": { "actor_name": "Bob Dev", "ticket_title": "Migration paiement" },
      "project_uuid": "...", "project_name": "Refonte",
      "subject_type": "ticket", "subject_uuid": "ticket-uuid",
      "is_read": false, "read_at": null,
      "created_at": "2026-10-27 09:12:00", "age_seconds": 340
    }
  ],
  "meta": { "total": 1, "count": 1, "unread": 3, "page": 1, "per_page": 50 }
}

Pour un agent qui surveille sa boîte : ?unread=1&per_page=1 donne le compteur dans meta.unread pour un seul appel. subject_uuid se lit avec l'endpoint du subject_type (par exemple GET /tickets/{uuid}).

PUT /api/v1/notifications/{uuid} Marquer lue ou non lue

Scope requis : notifications:write · body { "read": true } (défaut) ou { "read": false }.

POST /api/v1/notifications/mark-read Marquer plusieurs notifications comme lues

Scope requis : notifications:write

{ "uuids": ["notif-uuid-1", "notif-uuid-2"] }   // 500 au plus
{ "all": true }                                  // tout ce qui est arrivé

Réponse : { "marked": 2, "unread": 1 }. Préférer uuids : all marque aussi ce qui est arrivé depuis la dernière lecture de l'agent.

DELETE /api/v1/notifications/{uuid} Supprimer une notification

Scope requis : notifications:write

Finance / EVM

Indicateurs de gestion par la valeur acquise (Earned Value Management) d'un projet, calculés côté serveur à partir des story points, des temps enregistrés (× TJM) et du BAC déclaré (projects.budget).

Les indicateurs dépendant du budget (bac, pv, ev, cpi, cv, sv, eac, etc, vac, tcpi) valent null tant qu'aucun BAC n'est déclaré sur le projet. Les métriques d'avancement et de coût (ac, ac_total, no_sprint_cost, engaged_charge, spi, progress_pct, planned_pct) restent calculées.
Deux coûts réels, deux usages. ac est le coût du périmètre sprint : c'est la seule assiette comparable à l'ev, donc la seule admissible au dénominateur du cpi et dans le cv. ac_total = ac + no_sprint_cost est la dépense réelle totale : le temps pointé hors sprint ne reçoit aucune part de BAC (celui-ci est réparti au prorata des SP de sprint) donc n'acquiert aucune valeur, mais l'argent est bel et bien sorti. C'est donc ac_total qui sert aux questions d'atterrissage (eac, tcpi). Sur un projet sans pointage hors sprint, ac_total == ac et eac retombe exactement sur BAC ÷ CPI.
GET /api/v1/finance?project={uuid} Indicateurs EVM du projet

Scope requis : finance:read

Le titulaire de la clé doit aussi pouvoir lire les finances du projet (droit « Finance » de son rôle, ou owner/admin du projet) ; sinon 403 FORBIDDEN.

{
  "data": {
    "project_uuid": "abc-123",
    "bac": 350000.00,          // budget à terminaison (déclaré)
    "base_budget": 315000.00,  // BAC − réserve d'aléa
    "reserve": 35000.00,       // réserve d'aléa (incluse dans le BAC)
    "contingency_pct": 10.00,
    "pv": 326341.95,           // valeur planifiée = % SP planifié à date × BAC
    "ev": 257455.27,           // valeur acquise   = % SP réalisé × BAC
    "ac": 186573.75,           // coût du périmètre sprint = Σ (temps ÷ 480) × TJM en vigueur à la date du travail
    "no_sprint_cost": 8250.00, // temps pointé sur des tickets sans sprint
    "ac_total": 194823.75,     // dépense réelle totale = ac + no_sprint_cost
    "engaged_charge": 137253.75, // coût réel des épics non terminées
    "cpi": 1.38,               // EV ÷ ac         (> 1 = sous le budget)
    "spi": 0.79,               // EV ÷ PV         (> 1 = en avance)
    "tcpi": 0.60,              // (BAC − EV) ÷ (BAC − ac_total)
    "eac": 261889.45,          // coût final estimé = ac_total + etc
    "etc": 67065.70,           // reste à terminaison = (BAC − EV) ÷ CPI
    "vac": 88110.55,           // écart à terminaison = BAC − EAC
    "cv": 70881.52,            // écart de coût  = EV − ac
    "sv": -68886.68,           // écart de délai = EV − PV
    "total_sp": 503,
    "done_sp": 374,            // SP physiquement terminés (avancement brut)
    "earned_sp": 370,          // SP acquis = Σ min(faits, engagés) par sprint : base de l'EV
    "planned_sp": 469.10,
    "undated_sp": 12,          // SP engagés dans des sprints sans dates : ils comptent
                               // dans l'EV mais contribuent 0 au PV — ils gonflent le SPI
    "progress_pct": 73.56,     // earned_sp ÷ total_sp
    "planned_pct": 93.24       // % SP planifiés à date
  }
}

Portefeuille

Consolidation EVM sur tous les projets accessibles au porteur de la clé : une seule requête là où il fallait boucler projet par projet. Les indices consolidés sont calculés sur la somme des valeurs (EV, PV, AC), pas sur une moyenne des ratios.

GET /api/v1/portfolio/finance Roll-up EVM multi-projets

Scope requis : finance:read

Filtre optionnel : ?projects=uuid1,uuid2 pour cibler une équipe / un programme. Seuls les projets dont vous pouvez lire les finances sont consolidés ; les autres sont comptés dans totals.excluded_no_financial_access.

{
  "data": {
    "totals": {
      "projects": 12, "with_bac": 4, "without_bac": 8,
      "bac": 1250000.00, "pv": 910000.00, "ev": 742000.00, "ac": 690000.00,
      "ac_total": 712000.00,
      "engaged_charge": 305000.00,
      "cpi": 1.08, "spi": 0.82,
      "etc": 472398.92, "eac": 1184398.92, "vac": 65601.08,
      "cv": 52000.00, "sv": -168000.00,
      "ac_all": 812000.00,       // dépense réelle totale sur TOUS les projets (BAC ou non)
      "engaged_all": 410000.00   // charge engagée sur tous les projets
    },
    "projects": [
      { "project_uuid": "abc-123", "project_name": "CMA-CGM", "bac": 350000.00, "ev": 257455.27, "ac": 186573.75, "cpi": 1.38, "spi": 0.79, "vac": 96360.55, "engaged_charge": 137253.75, "...": "..." }
    ]
  },
  "meta": { "total": 12 }
}

Documentation Manifst API v1 · Retour au site