# Guide des API FlowDeck — Référence d'implémentation v6.3.0 > **Statut** : ✅ **IMPLÉMENTÉ (v6.3.0, 2026-09-21)** — l'API publique `/api/v2` est livrée : > routeur `app/routers/api_v2.py`, helpers `app/services/api_v2_helpers.py`, migration 20, > OpenAPI généré (`/docs`, `/redoc`, `docs/openapi-v2.json`), 24 tests dédiés. > **v6.6.0 (2026-09-24)** — **Agent phase 5** : wrappers agent (`/api/v2/agents/*`) + > marketplace de skills (`/api/v2/skills/*`) dans `app/routers/api_v2_agent.py`, logique > partagée `app/services/skill_gallery.py`, OpenAPI régénéré (**427 chemins**), 15 tests dédiés > (`tests/test_v66_agent_api.py`). Voir §2.4. > **v6.7.0 (2026-09-24)** — **SSO / SAML + OIDC entreprise** : parcours navigateur > `/auth/saml/*` + `/auth/oidc/*` (hors scope `/api/v2`), API admin de configuration > `/api/v2/sso/*` (session + admin + CSRF), logique partagée `app/services/sso_provisioning.py`, > OpenAPI régénéré, 38 tests dédiés (`tests/test_v67_sso.py`). Voir §2.5. > Les sections ci-dessous décrivent les conventions cibles et restent la référence de conception. > **Dernière mise à jour** : 2026-09-24 > **Portée** : inventaire de l'API existante, conventions cibles, design CRUD par ressource, webhooks, sécurité, checklist d'implémentation. > > **Reste reporté (v6.4)** : webhooks HMAC `X-FlowDeck-Signature` + retry 2s/10s/60s + événements étendus ; > migration de `/api/v2/sync` vers Bearer ; `REST /api/v2/projects/*/file|commits|issues` complets via `ForgeAdapter`. --- ## 1. Vision & architecture FlowDeck est **API-first** (FastAPI). Deux couches d'API coexistent : | Couche | Préfixe | Auth | Usage | |--------|---------|------|-------| | **API interne** (existant) | `/api`, `/workspace`, `/db`, `/board`… | Session cookie `flowdeck_session` + CSRF | Le frontend HTMX/Alpine | | **API publique v1** (existant, lecture seule) | `/api/v1` | Bearer token | Intégrations externes minimales | | **API publique v2** (cible v6.0.0) | `/api/v2` | Bearer token + scopes | CRUD complet, usage externe (clone Notion API) | **Principe** : l'API v2 expose les mêmes opérations que l'API interne, mais avec une surcouche de contrôle (auth par token, scopes, pagination, filtres, erreurs normalisées). Elle est implémentée comme des wrappers sur les mêmes services/logique métier — jamais de duplication. ### Règles d'or 1. Un endpoint public = un wrapper autour de la logique existante (un seul chemin de code). 2. L'API publique ne renvoie **jamais** de HTML, de secrets, ni de colonnes internes (`password_hash`, tokens OAuth). 3. Toute mutation passe par les mêmes validations que l'UI (CSRF concerne l'UI seule ; les tokens remplacent CSRF). 4. Versionnage par préfixe d'URL (`/api/v1`, `/api/v2`) — jamais de breaking change sur une version publiée. --- ## 2. État actuel — inventaire de l'API ### 2.1 Schéma global des routeurs (`app/main.py`) | Routeur | Fichier | Préfixe | Rôle | |---------|---------|---------|------| | `auth` | `routers/auth.py` | `/auth` | Login OAuth/local, register, logout, sessions | | `dashboard` | `routers/dashboard.py` | — | Pages HTML (landing, workspace, settings…) | | `board` | `routers/board.py` | `/board` | Kanban Gitea (legacy, compatible) | | `notes` | `routers/notes.py` | `/notes` | Notes Markdown par projet | | `api` | `routers/api.py` | `/api` | Pages, workspaces, tags, trash, favoris, settings | | `webhooks` | `routers/webhooks.py` | `/api/webhook` | Réception webhooks Gitea (issues/PR/repo) | | `collections` | `routers/collections.py` | `/db` | Databases Notion-style (CRUD) | | `my_tasks` | `routers/my_tasks.py` | `/my-tasks` | Agrégation tâches | | `workspace` | `routers/workspace.py` | `/workspace` | Local workspace, arborescence, webhooks sortants | | `library` | `routers/library.py` | `/api/library` | Bibliothèque (sources local/Gitea/GitHub) | | `admin` | `routers/admin.py` | `/api/admin` | Admin utilisateurs + audit | | `gitea` | `routers/gitea.py` | `/api/gitea` | Projets, fichiers, commits, labels | | `github` | `routers/github_routes.py` | `/api/github` | Intégration GitHub | | `public_api` | `routers/public_api.py` | `/api/v1` | **API publique existante (lecture seule)** | | `sharing` | `routers/sharing.py` | `/api` | Share/publish pages | | `sidebar_config` | `routers/sidebar_config.py` | `/api/sidebar` | Customisation sidebar | | `export` | `routers/export.py` | `/api/export` | Export MD/PDF/HTML/CSV | | `notifications` | `routers/notifications.py` | `/api/notifications` | Notifications in-app | | `collaboration` | `routers/collaboration.py` | `/api` | Commentaires inline, mentions, historique | ### 2.2 Récapitulatif des endpoints existants (par domaine) > Liste non exhaustive côté pages HTML ; les endpoints API sont tous listés. **Auth & users** ``` GET /auth/login, /auth/register, /auth/callback, /auth/logout, /auth/user POST /auth/local-login, /auth/register, /auth/register/{owner}/{repo} GET /api/users, /api/users/me, /api/users/search PUT /api/users/me, /api/user/profile, /api/user/password POST /api/user/token, /api/settings/avatar, /api/settings/avatar-color, /api/settings/tags GET /api/settings/account, /api/settings/avatar/{filename} PUT /api/settings/tags/{tag_id}, /api/settings/tags/all (GET) DELETE /api/settings/tags/{tag_id}, /api/user/forge/{provider} ``` **Workspaces & membres** ``` GET /api/workspaces, POST /api/workspaces GET /api/workspace/{ws_id}/members, POST /api/workspace/{ws_id}/members PUT /api/workspace/{ws_id}/members/{user_id}, DELETE /api/workspace/{ws_id}/members/{user_id} PUT /api/workspaces/{ws_id}, DELETE /api/workspaces/{ws_id} POST /api/workspaces/{ws_id}/select ``` **Pages (éditeur blocs)** ``` GET /api/pages/{page_id}, PUT /api/pages/{page_id}, DELETE /api/pages/{page_id} POST /api/pages (création), /api/pages/{page_id}/blocks, /api/pages/{page_id}/content PUT /api/pages/{page_id}/rename, /api/pages/{page_id}/move POST /api/pages/{page_id}/trash, /api/trash/{page_id}/restore GET /api/trash, DELETE /api/trash/{page_id} POST /api/pages/{page_id:int}/convert-to-database GET /api/local-workspace/tree, /api/sidebar/workspace-tree, /api/nav/menu GET /api/local-workspace/page-content/{page_id}, /children/{page_id} ``` **Collections (databases)** ``` GET /db/{collection_id}/api (détail), DELETE /db/{collection_id} POST /db/inline/api (créer inline) POST /db/{id}/linked/api, /db/{id}/toggle-inline/api, PUT /db/{id}/toggle-task/api GET /db/{id}/sources/api, POST /db/{id}/sources/api, DELETE /db/{id}/sources/{sid}/api GET /db/{id}/properties/api, POST /db/{id}/properties/api PUT /properties/{prop_id}/api, DELETE /properties/{prop_id}/api POST /db/{id}/properties/relation, /db/{id}/properties/relation/link GET /db/{id}/views/api, POST /db/{id}/views/save-as, PUT /views/{view_id}/config GET /property-types/api ``` **Collection pages & tâches** ``` GET /db/{id}/pages/api, POST /db/{id}/pages/api, PUT /pages/{page_id}/api, DELETE /pages/{page_id}/api POST /db/{id}/pages/{pid}/sub-items, GET /db/{id}/pages/{pid}/sub-items GET /db/{id}/pages/{pid}/dependencies/api, POST /db/{id}/pages/{pid}/dependencies/api DELETE /db/{id}/pages/{pid}/dependencies/{dep_id}/api POST /db/{id}/pages/{pid}/auto-shift/api, /db/{id}/pages/{pid}/check-deps GET /db/{id}/pages/{pid}/status-aggregate POST /formula/evaluate, /rollup/compute ``` **Sprints, dashboards, templates** ``` GET/POST /workspace/collections/{id}/sprints, PUT/DELETE /workspace/collections/{id}/sprints/{sid} POST /workspace/collections/{id}/sprints/{sid}/assign, DELETE .../assign/{page_id} GET /workspace/collections/{id}/sprints/burndown/{sid} GET/POST/PUT/DELETE /workspace/collections/{id}/dashboards[/{did}] GET/POST /workspace/collections/{id}/templates/page, PUT/DELETE .../templates/page/{tid} POST .../templates/page/{tid}/apply GET/POST /templates/database, POST /templates/database/{tid}/apply ``` **Favoris, tags, recents** ``` GET/POST /api/favorites, DELETE /api/favorites/{page_id} GET/POST /db/{id}/pages/api… (tags par page via /api/local-workspace/items/{id}/tags) GET /api/local-workspace/tags, /api/local-workspace/tags/search POST /api/local-workspace/items/{id}/tags, DELETE .../tags/{tag_id} GET /api/recents, POST /api/recents/track ``` **Partage & publication** ``` POST /api/share/{page_id}, GET /api/pages/{page_id}/shares, DELETE /api/pages/{page_id}/share/{share_id} POST /pages/{page_id}/share, POST /pages/{page_id}/publish, DELETE /pages/{page_id}/publish GET /public/{collection_id}, /published, /shared, /private, /p/{slug} ``` **Commentaires & notifications (v4.9.0)** ``` GET/POST /pages/{page_id}/comments, PUT/DELETE /comments/{comment_id} POST /pages/{page_id}/mentions GET /api/notifications, /api/notifications/unread-count POST /api/notifications/read, /api/notifications/read-all ``` **Historique** ``` GET/POST /pages/{page_id}/history ``` **Import / Export** ``` GET /api/collections/{id}/export/csv, POST /api/collections/{id}/import/csv GET /api/export/markdown|pdf|html (via /export et /markdown/{page_id}, /pdf/{page_id}, /html/{page_id}) ``` **Forge Gitea / GitHub** ``` GET /api/workspace/projects, POST /api/workspace/projects GET /projects/{owner}/{repo}/tree|labels|commits|private-pages|file PUT /projects/{owner}/{repo}/file, DELETE /projects/{owner}/{repo}/file POST /projects/{owner}/{repo}/upload, /sync-labels, /private-pages GET/POST /issues/{owner}/{repo}, PATCH /issues/{owner}/{repo}/{issue_id} POST /api/sync/{owner}/{repo}, /api/ai-keywords/{owner}/{repo}/extract ``` **Admin** ``` GET /api/admin/users, PUT /api/admin/users/{user_id}, DELETE /api/admin/users/{user_id} GET /audit, /api/frontend-errors, POST /api/frontend-error ``` **Santé & divers** ``` GET /api/health, /api/stats, /api/config, PUT /api/config, /api/favorites… POST /api/move, /col-mapping (GET/DELETE/POST), /board-config/{owner}/{repo} (GET/POST) ``` ### 2.3 API publique v1 existante (`app/routers/public_api.py`) | Méthode | Route | Description | |---------|-------|-------------| | POST | `/api/v1/token` | Génère un token `fd_xxx` (inséré dans `user_tokens`) | | GET | `/api/v1/collections` | Liste des collections (id, name, description, icon, created_at) | | GET | `/api/v1/collections/{id}` | Collection + ses pages | | GET | `/api/v1/collections/{id}/pages` | Pages racine de la collection | | GET | `/api/v1/pages/{page_id}` | Page détaillée | | GET | `/api/v1/my-tasks` | Tâches (limit 50) | **Auth v1** : header `Authorization: Bearer `. Token par défaut : `fd-public-key` (fallback dev). Vérification dans la table `user_tokens` (colonne `gitea_token`). **Limites de v1** (ce que v2 doit corriger) : - Lecture seule — aucun POST/PUT/DELETE sur les ressources. - Un seul token par utilisateur (`UNIQUE(gitea_user_id)`) et stocké dans une table prévue pour les tokens Gitea. - Pas de scopes, pas d'expiration, pas de nom d'affichage, pas de révocation individuelle. - Pas de pagination, filtres, tri. - Pas de documentation OpenAPI en production (`docs_url` n'est actif qu'en DEBUG). ### 2.4 API publique v2 — Agent & Skill marketplace (`app/routers/api_v2_agent.py`, v6.6.0) > **Agent phase 5 « Plateforme »** : permettre à une intégration tierce de piloter > FlowDeck Agent et d'installer/partager des skills, sans session navigateur. **Agents, conversations & runs** | Méthode | Route | Scope | Description | |---------|-------|-------|-------------| | GET | `/api/v2/agents` | read | Liste les agents (pagination `limit`/`offset`) | | POST | `/api/v2/agents` | write | Crée un agent (`name`, `system_instructions`, `model`, `scope`, `trigger`) | | GET/PUT/DELETE | `/api/v2/agents/{id}` | read / write | Détail, mise à jour, suppression | | GET | `/api/v2/agents/conversations` | read | Conversations **de l'utilisateur du token** | | POST | `/api/v2/agents/conversations` | write | Crée une conversation (`agent_id` optionnel) | | GET/DELETE | `/api/v2/agents/conversations/{id}` | read / write | Conversation + messages / suppression | | POST | `/api/v2/agents/conversations/{id}/run` | write | **Run synchrone JSON** (le flux SSE reste interne) | | GET | `/api/v2/agents/conversations/{id}/actions` | read | Journal d'audit (`agent_actions`, snapshots) | | POST | `/api/v2/agents/actions/{id}/undo` | write | Rollback d'une action | | POST | `/api/v2/agents/{id}/trigger` | write | Déclenche un agent custom (JSON, synchrone) | Réponse de `run` : `{conversation_id, status: completed|failed, final, error, reasoning[], actions[], events[], duration_ms}` — HTTP 500 quand `status = failed`. Les événements `events[]` sont ceux du flux SSE (`reasoning`, `action`, `final`, `notice`, `error`), à plat : `{"type": "final", "content": ...}`. **Skill marketplace** | Méthode | Route | Scope | Description | |---------|-------|-------|-------------| | GET/POST | `/api/v2/skills` | read / write | Liste / création (`409` si nom déjà présent) | | GET/DELETE | `/api/v2/skills/{id}` | read / write | Détail / suppression | | GET | `/api/v2/skills/{id}/export` | read | **Document portable** `{format, version, skill{...}}` | | POST | `/api/v2/skills/import` | write | Import (identique → `409`, `overwrite: true` → maj) | | GET | `/api/v2/skills/gallery` | read | Presets embarqués (6) | | POST | `/api/v2/skills/gallery/{slug}/install` | write | Installe un preset dans le workspace | | POST | `/api/v2/skills/{id}/apply` | write | Ouvre une conversation préchargée du skill | Mêmes endpoints (session cookie) côté interne : `/api/agent/skills/gallery`, `/api/agent/skills/{id}/export`, `/api/agent/skills/import`, `DELETE /api/agent/skills/{id}` — **même implémentation** via `app/services/skill_gallery.py`. **Règles spécifiques agent** 1. La **propriété** des conversations est vérifiée (`user_id` du token) : un token ne voit jamais les conversations d'un autre utilisateur (`404`, pas `403`, pour ne pas fuiter). 2. Le run est **synchrone** : le moteur (`AgentEngine`), l'ACL (`PermissionManager`), le journal `agent_actions` et les webhooks sont exactement ceux de l'UI — aucun second chemin. 3. Cycle de vie webhook : `agent.run.started` → `agent.run.finished` | `agent.run.failed` (catalogue `app/services/webhook_outbound.py`, abonnement `agent.*` possible). 4. Chaque mutation écrit `api_audit_log` (`agent.create`, `agent.run`, `skill.import`, …). ### 2.5 SSO / Enterprise auth (`app/routers/sso.py`, v6.7.0) > **Authentification fédérée** : un IdP d'entreprise (SAML 2.0 ou OpenID Connect + PKCE) > ouvre une session FlowDeck ; les comptes sont créés au premier login et les groupes de > l'IdP deviennent des rôles workspace. Design : `docs/V6_SSO_SAML_Enterprise_Auth.md`. **Parcours navigateur (session cookie, CSRF exclu — POST IdP cross-site)** | Méthode | Route | Description | |---------|-------|-------------| | GET | `/auth/saml/login` | Redirection SP-initiée vers l'IdP (`RelayState` = next sûr) | | POST | `/auth/saml/callback` | Assertion Consumer Service — signature/aud/dest/InResponseTo validés | | GET | `/auth/saml/metadata` | Métadonnées SP XML (EntityID, ACS, SLO, certificat) | | GET | `/auth/saml/logout` | SLO — relaye `SAMLRequest` à l'IdP puis détruit la session (POST accepté pour l'IdP) | | GET | `/auth/oidc/login` | Redirection authorize (`state` + `code_verifier` en DB) | | GET | `/auth/oidc/callback` | Code → token → userinfo ; ID token vérifié (JWKS, aud/iss/nonce/exp) | | GET/POST | `/auth/oidc/logout` | Déconnexion OIDC (relaye à l'IdP si `end_session_endpoint`) | **API admin (session + rôle admin + header `X-CSRF-Token`)** | Méthode | Route | Description | |---------|-------|-------------| | GET | `/api/v2/sso/providers` | **Public** (page de login) : `{providers[{type,name,icon,login_url}], sso_only, base_url}` | | GET | `/api/v2/sso/config` | Config publique (`{configured, source: db\|env, client_secret_set, provisioned_users}` — **jamais** le secret) | | POST/PUT | `/api/v2/sso/config` | Crée/met à jour (`{configured:false}` = actif) ; champ secret vide = conserver | | DELETE | `/api/v2/sso/config` | Désactive le SSO (les comptes SSO existants restent) | | GET | `/api/v2/sso/workspaces` | Épingles pour le mapping (workspaces + `provisioned_users`) | | POST | `/api/v2/sso/sync` | Re-synchronise les groupes de tous les utilisateurs SSO | | GET | `/api/v2/sso/history` | Journal d'audit des tentatives (`limit` ≤ 200) | Règles : secrets chiffrés Fernet (`sso_config`), anti-replay `sso_requests` (TTL 15 min), historique `sso_login_history` (succès/échecs), mode SSO only (login local refusé sauf admins), bootstrap possible par variables `SSO_*` du `.env` (la config admin prime). --- ### 3.1 Auth & tokens **Nouvelle table `api_tokens`** (remplace l'usage détourné de `user_tokens`) : ```sql CREATE TABLE api_tokens ( id INTEGER PRIMARY KEY AUTOINCREMENT, user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE, name TEXT NOT NULL DEFAULT 'default', -- ex: "CI production" token_hash TEXT NOT NULL UNIQUE, -- sha256 du token, jamais en clair scopes TEXT NOT NULL DEFAULT 'read', -- 'read' | 'read,write' | 'read,write,admin' expires_at TIMESTAMP, -- NULL = jamais last_used_at TIMESTAMP, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, revoked INTEGER NOT NULL DEFAULT 0 ); ``` - **Format token** : `fd_{user_id}_{urlsafe(32)}` — l'utilisateur ne le voit qu'une fois. Stockage en `sha256`. - **Crud tokens** : `POST /api/v2/tokens`, `GET /api/v2/tokens`, `DELETE /api/v2/tokens/{id}` (révocation), `POST /api/v2/tokens/rotate`. - **Scopes** : `read` (GET), `write` (POST/PUT/PATCH), `admin` (gestion users/admin). Dépendance FastAPI `require_scope("write")`. - **Compat v1** : garder le fallback `fd-public-key` uniquement si `settings.public_api_insecure_ok=true` (défaut : **false** en prod). ### 3.2 Format des réponses - JSON brut (pas d'enveloppe `{data: …}`) — style Notion API. - Timestamps **ISO-8601 UTC** (`2026-09-04T14:30:00Z`). SQLite stocke `YYYY-MM-DD HH:MM:SS` → convertir dans le convertisseur de sortie. - IDs : entiers SQLite (`int`). Exposés tels quels (pas de UUID) — `ponytail: garder int, passer en UUID seulement si exposition publique nécessaire`. - Champs `*_json` stockés en SQLite (ex: `property_values_json`, `config_json`) → **déjà parsés en objets JSON** dans les réponses API. ### 3.3 Erreurs (RFC 7807 `application/problem+json`) ```json { "type": "https://flowdeck/api/errors/not-found", "title": "Resource not found", "status": 404, "detail": "Collection 42 n'existe pas", "instance": "/api/v2/collections/42" } ``` Codes : `400` validation (détail = liste des champs), `401` token manquant/invalide, `403` scope insuffisant / permissions, `404` inexistant, `409` conflit (nom dupliqué, dépendance bloquante), `422` erreur de schéma (FastAPI), `429` rate limit, `500` interne. Handler global : `@app.exception_handler(HTTPException)` + adaptation des erreurs internes → problème JSON pour tout préfixe `/api/v2`. ### 3.4 Pagination, filtres, tri **Pagination offset** (simple, SQLite) : ``` GET /api/v2/collections?limit=30&offset=0 → response headers: X-Total-Count: 142 ``` Requête : `limit` (défaut 30, max 100), `offset` (défaut 0). Utilise `SELECT COUNT(*)` + `LIMIT ? OFFSET ?` — `ponytail: offset OK jusqu'à ~10k lignes, passer à keyset (cursor) si besoin`. **Filtres** (par propriété de la ressource) : ``` GET /api/v2/collections/{id}/pages?filter[status]=Done&filter[assignee]=bruno GET /api/v2/pages?query=mot&workspace_id=3 ``` Convention : `filter[]=` (AND implicite), `sort=`, `sort=-` (desc), `fields=a,b,c` (projection). **Recherche FTS** : réserver `?query=` sur les listes → recherche plein texte (table FTS5 à créer, indexant pages + collection_pages). ### 3.5 Idempotence & mutations - `POST` de création accepte un header optionnel `Idempotency-Key` — sur conflit, renvoyer la ressource existante créée avec cette clé (table `idempotency_keys`). - Les `PATCH` sont partiels (seuls les champs présents sont modifiés). ### 3.6 Rate limiting par token Étendre `RateLimitMiddleware` (actuellement 100 req/min/IP sur `/api/`, `/board/api/`, `/auth/`) : - `/api/v2/*` : quota **par token** (défaut 300 req/min, configurable par scope), en plus du quota IP. - Remplacer le store `defaultdict` in-memory par une clé composite `(ip|token_id)` — `ponytail: in-memory OK mono-instance, Redis si multi-workers`. - Header de réponse : `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset`. - Exemptés : `/api/health`, `/api/v2/tokens` (auth). --- ## 4. Design des endpoints v2 — CRUD par ressource > Pour chaque ressource : table(s) SQLite, endpoints internes existants (réutilisables), endpoints publics v2 à créer. > **`GET /collections` → wrapper `public_list_collections` existant à enrichir** ; les mutations s'appuient sur les routeurs `collections.py` (préfixe `/db`). ### 4.1 Users | Table | `users` | |-------|---------| | Colonnes clés | id, login, full_name, email, avatar_url, avatar_color, is_admin, is_active, auth_method, sidebar_config, notification_prefs | | **Interne** | `GET/PUT /api/users/me`, `GET /api/users`, `PUT/DELETE /api/users/{id}` (admin) | Endpoints publics v2 : | Méthode | Route | Description | |---------|-------|-------------| | GET | `/api/v2/users/me` | Profil du user courant (sans `password_hash`) | | PATCH | `/api/v2/users/me` | Mettre à jour profil (full_name, email, avatar_color, notification_prefs) | | GET | `/api/v2/users` | **admin** — lister (pagination) | | GET | `/api/v2/users/search?q=` | Recherche par login/email (pour @mentions) | ### 4.2 Workspaces & membres | Tables | `workspaces`, `workspace_members` (role: owner/admin/editor/viewer) | |--------|-----| | **Interne** | `GET/POST /api/workspaces`, `PUT/DELETE /api/workspaces/{ws_id}`, `GET/POST /api/workspace/{ws_id}/members`, `PUT/DELETE …/members/{user_id}` | Endpoints publics v2 : | Méthode | Route | Description | |---------|-------|-------------| | GET | `/api/v2/workspaces` | Workspaces de l'utilisateur (avec rôle) | | POST | `/api/v2/workspaces` | Créer (body: name, settings_json) | | GET | `/api/v2/workspaces/{id}` | Détail + membres | | PATCH | `/api/v2/workspaces/{id}` | Renommer / settings | | DELETE | `/api/v2/workspaces/{id}` | Supprimer (owner only) | | GET | `/api/v2/workspaces/{id}/members` | Lister membres | | POST | `/api/v2/workspaces/{id}/members` | Inviter (user_id ou email) | | PATCH | `/api/v2/workspaces/{id}/members/{uid}` | Changer le rôle | | DELETE | `/api/v2/workspaces/{id}/members/{uid}` | Retirer un membre | > ⚠️ **Dépendance v6** : créer `app/services/permission_manager.py` (la roadmap y fait référence mais le fichier n'existe pas). Centraliser : `require_workspace_role(ws_id, "editor")`, `can_read(workspace_id|collection_id|page_id, user)`. ### 4.3 Collections (databases) | Tables | `collections` (+ `workspace_id`, `created_by`, `is_inline`, `parent_page_id`, `is_task`) | |--------|-----| | **Interne** | `POST /db/inline/api`, `GET/DELETE /db/{id}/api`, `POST /db/{id}/linked/api`, `POST /db/{id}/toggle-inline/api`, `PUT /db/{id}/toggle-task/api`, sources (`/db/{id}/sources/api`) | Endpoints publics v2 : | Méthode | Route | Description | |---------|-------|-------------| | GET | `/api/v2/collections?workspace_id=&query=` | Liste (paginée) — wrapper de v1 enrichi | | POST | `/api/v2/collections` | Créer (name, description, icon, workspace_id, schema_json) | | GET | `/api/v2/collections/{id}` | Détail + pages (existant v1) | | PATCH | `/api/v2/collections/{id}` | Renommer, icon, description, schema | | DELETE | `/api/v2/collections/{id}` | Supprimer (cascade pages/vues/props) | | POST | `/api/v2/collections/{id}/linked` | Créer une DB liée | | POST | `/api/v2/collections/{id}/task` | toggler `is_task` | | GET | `/api/v2/collections/{id}/sources` | Sources / DB liées — wrapper v1 | | POST | `/api/v2/collections/{id}/sources` | Ajouter une source | | DELETE | `/api/v2/collections/{id}/sources/{sid}` | Retirer une source | ### 4.4 Pages de collection (collection_pages) | Tables | `collection_pages` (property_values_json, parent_id, position, gitea_issue_*) | |--------|-----| | **Interne** | `GET/POST /db/{id}/pages/api`, `GET/PUT/DELETE /pages/{page_id}/api`, sub-items, dependencies, auto-shift, status-aggregate, move | Endpoints publics v2 : | Méthode | Route | Description | |---------|-------|-------------| | GET | `/api/v2/collections/{id}/pages?filter[status]=…&sort=…` | Lister (pagination, filtres, tri, fields) | | POST | `/api/v2/collections/{id}/pages` | Créer (title, icon, property_values, parent_id) | | GET | `/api/v2/pages/{id}` | Détail (propriétés parsées) — v6.5.0 : + `content_page_id` (page contenu de la ligne, `null` si absente, sans création lazy) | | PATCH | `/api/v2/pages/{id}` | Titre, icon, position, property_values (merge) | | DELETE | `/api/v2/pages/{id}` | Corbeille (soft delete via `deleted_at`) | | POST | `/api/v2/pages/{id}/restore` | Restaurer | | POST | `/api/v2/pages/{id}/move` | Reordonner (position / parent_id) | | GET | `/api/v2/pages/{id}/sub-items` | Sous-tâches | | POST | `/api/v2/pages/{id}/sub-items` | Créer sous-item | | GET/POST/DELETE | `/api/v2/pages/{id}/dependencies[/{dep_id}]` | Dépendances | > **Détail `property_values`** : objet JSON `{"": }` — utiliser `property_types.py` (validation/formatage) déjà en place. ### 4.5 Propriétés (`collection_properties`) | Colonnes clés | name, prop_type (21 types), options_json, number_format, related_collection_id, reverse_name, relation_property_id, target_property_id, rollup_function, formula_expression, required, visible_in_views | |-----|-----| | **Interne** | `GET/POST /db/{id}/properties/api`, `PUT/DELETE /properties/{prop_id}/api`, `POST …/properties/relation`, `…/relation/link`, `POST /formula/evaluate`, `POST /rollup/compute`, `GET /property-types/api` | Endpoints publics v2 : | Méthode | Route | Description | |---------|-------|-------------| | GET | `/api/v2/collections/{id}/properties` | Types + configuration (wrapper) | | POST | `/api/v2/collections/{id}/properties` | Créer (name, prop_type, options…) | | PATCH | `/api/v2/properties/{id}` | Modifier (options, required, formula, rollup…) | | DELETE | `/api/v2/properties/{id}` | Supprimer | | POST | `/api/v2/properties/{id}/relation` | Créer relation bidirectionnelle | | POST | `/api/v2/properties/evaluate-formula` | Moteur formula (debug) | | POST | `/api/v2/properties/compute-rollup` | Moteur rollup (debug) | ### 4.6 Vues (`collection_views`) & Dashboards (`collection_dashboards`) | **Interne** | `GET /db/{id}/views/api`, `POST /db/{id}/views/save-as`, `PUT /views/{view_id}/config`, `GET/POST/PUT/DELETE /workspace/collections/{id}/dashboards[/{did}]` | |-----|-----| | Méthode | Route | Description | |---------|-------|-------------| | GET | `/api/v2/collections/{id}/views` | Lister les vues (config_json parsé) | | POST | `/api/v2/collections/{id}/views` | Créer (name, view_type, config) | | PATCH | `/api/v2/views/{id}` | Modifier config (filtres, tri, group_by, layout) | | DELETE | `/api/v2/views/{id}` | Supprimer | | POST | `/api/v2/views/{id}/save-as` | Dupliquer sous un autre nom | | GET/POST/PUT/DELETE | `/api/v2/collections/{id}/dashboards[/{did}]` | Dashboards (layout_json) | ### 4.7 Commentaires (`comments`, v4.9.0) | Colonnes clés | target_type ('page'/'collection_page'), target_id, user_id, body, parent_id, resolved, anchor_block_id, anchor_start, anchor_end | |-----|-----| | **Interne** | `GET/POST /pages/{page_id}/comments`, `PUT/DELETE /comments/{comment_id}`, `POST /pages/{page_id}/mentions` | | Méthode | Route | Description | |---------|-------|-------------| | GET | `/api/v2/pages/{id}/comments` | Lister (avec tri chronologique) | | POST | `/api/v2/pages/{id}/comments` | Ajouter (body, anchor_block_id/start/end optionnels) | | PATCH | `/api/v2/comments/{id}` | Éditer / résoudre (`resolved: true`) | | DELETE | `/api/v2/comments/{id}` | Supprimer | | POST | `/api/v2/pages/{id}/mentions` | Mentionner des users (déclenche notification + email) | ### 4.8 Notifications (`notifications`) | ntype | 'mention' / 'comment' / 'page' — resource_type, resource_id, url, is_read | |-----|-----| | **Interne** | `GET /api/notifications`, `GET /unread-count`, `POST /read`, `POST /read-all` | | Méthode | Route | Description | |---------|-------|-------------| | GET | `/api/v2/notifications?unread=1` | Lister (pagination) | | POST | `/api/v2/notifications/{id}/read` | Marquer lue | | POST | `/api/v2/notifications/read-all` | Tout marquer lu | | GET | `/api/v2/notifications/unread-count` | Compteur (badge) | | PATCH | `/api/v2/users/me/preferences` | prefs email (comments/mentions) | ### 4.9 Favoris & Tags & Recents | Tables | `favorites`, `tags` (per-user), `page_tags`, `recents` | |-----|-----| | **Interne** | `GET/POST /api/favorites`, `DELETE /api/favorites/{page_id}`, `/api/settings/tags*`, `/api/local-workspace/items/{id}/tags*`, `/api/recents*` | | Méthode | Route | Description | |---------|-------|-------------| | GET | `/api/v2/favorites` | Lister | | POST | `/api/v2/favorites` | Ajouter `{page_id}` | | DELETE | `/api/v2/favorites/{page_id}` | Retirer | | GET | `/api/v2/tags?q=` | Tags de l'utilisateur | | POST | `/api/v2/tags` | Créer (name, color) | | PUT/DELETE | `/api/v2/tags/{id}` | Modifier / supprimer | | POST | `/api/v2/pages/{id}/tags` | Attacher `{tag_id}` | | DELETE | `/api/v2/pages/{id}/tags/{tag_id}` | Détacher | | GET | `/api/v2/recents?source_type=` | Récents (limit 20) | ### 4.10 Partage & publication (`page_shares`) | Colonnes clés | page_id, shared_with_user_id, shared_with_email, permission ('view'/'edit'), created_by + colonnes pages (is_published, publish_slug, is_shared, share_mode) | |-----|-----| | **Interne** | `POST /api/share/{page_id}`, `GET /api/pages/{id}/shares`, `DELETE /api/pages/{id}/share/{share_id}`, `POST /pages/{id}/publish`, `DELETE /pages/{id}/publish` | | Méthode | Route | Description | |---------|-------|-------------| | GET | `/api/v2/pages/{id}/shares` | Liste des accès | | POST | `/api/v2/pages/{id}/shares` | Partager `{email|user_id, permission}` | | PATCH | `/api/v2/shares/{share_id}` | Changer permission | | DELETE | `/api/v2/shares/{share_id}` | Révoquer | | POST | `/api/v2/pages/{id}/publish` | Publier (`{slug}`) → page publique `/p/{slug}` | | DELETE | `/api/v2/pages/{id}/publish` | Dé-publier | ### 4.11 Historique (`page_history`) | Colonnes clés | page_id, user_id, change_type, snapshot_json, created_at | |-----|-----| | **Interne** | `GET/POST /pages/{page_id}/history` | | Méthode | Route | Description | |---------|-------|-------------| | GET | `/api/v2/pages/{id}/history` | Liste des versions (change_type, created_at, user) | | POST | `/api/v2/pages/{id}/history/restore` | Restaurer `{history_id}` | ### 4.12 Sprints (`sprints`, `sprint_pages`) | **Interne** | `/workspace/collections/{id}/sprints[/{sid}]`, assign, burndown | |-----|-----| | Méthode | Route | Description | |---------|-------|-------------| | GET/POST | `/api/v2/collections/{id}/sprints` | Lister / créer | | PATCH/DELETE | `/api/v2/sprints/{sid}` | Mettre à jour / supprimer | | POST | `/api/v2/sprints/{sid}/assign` | Assigner `{page_id, velocity_points}` | | DELETE | `/api/v2/sprints/{sid}/assign/{page_id}` | Retirer | | GET | `/api/v2/sprints/{sid}/burndown` | Points (total/completed/remaining/ideal) | ### 4.13 Templates (`page_templates`, `database_templates`) | **Interne** | `/workspace/collections/{id}/templates/page*`, `/templates/database*`, apply | |-----|-----| | Méthode | Route | Description | |---------|-------|-------------| | GET/POST | `/api/v2/collections/{id}/templates` | Lister / créer un template de page | | PATCH/DELETE | `/api/v2/templates/{tid}` | Modifier / supprimer | | POST | `/api/v2/templates/{tid}/apply` | Instancier (crée une page depuis le template) | | GET | `/api/v2/templates/database` | Templates de DB (Project tracker, CRM…) | | POST | `/api/v2/templates/database/{tid}/apply` | Créer une collection depuis le template | ### 4.14 Export & Import | **Interne** | `GET /api/collections/{id}/export/csv`, `POST /api/collections/{id}/import/csv`, `/export`, `/markdown/{page_id}`, `/pdf/{page_id}`, `/html/{page_id}` (routers/export.py) | |-----|-----| | Méthode | Route | Description | |---------|-------|-------------| | GET | `/api/v2/pages/{id}/export` | `?format=markdown|pdf|html` → fichier | | GET | `/api/v2/collections/{id}/export/csv` | CSV | | POST | `/api/v2/collections/{id}/import/csv` | Import CSV (multipart) | > Les exports PDF/HTML/MD existent déjà (`export.py`) — v2 ne fait que les exposer avec auth token. Générer le fichier puis le renvoyer (`FileResponse`) ou une URL signée temporaire. ### 4.15 Forges (Gitea / GitHub) | **Interne** | `routers/gitea.py` + `services/gitea_client.py` (adapter), `routers/github_routes.py` + `services/github_adapter.py` | |-----|-----| | Méthode | Route | Description | |---------|-------|-------------| | GET | `/api/v2/projects` | Projets forges liés (workspace) | | GET | `/api/v2/projects/{owner}/{repo}/tree` | Arborescence | | GET | `/api/v2/projects/{owner}/{repo}/file?path=` | Lire un fichier | | PUT | `/api/v2/projects/{owner}/{repo}/file` | Écrire `{path, content, message}` (commit) | | DELETE | `/api/v2/projects/{owner}/{repo}/file` | Supprimer (commit) | | GET | `/api/v2/projects/{owner}/{repo}/commits?path=` | Historique | | GET | `/api/v2/projects/{owner}/{repo}/issues` | Issues (provider-agnostic via ForgeAdapter) | | PATCH | `/api/v2/issues/{provider}/{owner}/{repo}/{id}` | Mettre à jour | > Cible v6 (roadmap) : interface **`ForgeAdapter`** commune (Gitea + GitHub) — les endpoints v2 consomment l'adapter, pas les clients bruts. ### 4.16 Admin & Audit | **Interne** | `routers/admin.py` (`/api/admin`), `/audit` | |-----|-----| | Méthode | Route | Description | |---------|-------|-------------| | GET | `/api/v2/admin/users` | **scope admin** — lister (pagination) | | PATCH | `/api/v2/admin/users/{id}` | Activer/désactiver, rôle admin | | DELETE | `/api/v2/admin/users/{id}` | Supprimer | | GET | `/api/v2/admin/audit-logs` | Dernières actions (login, exports, changements rôle) | ### 4.17 Recherche | Métier | FTS5 sur `pages` + `collection_pages` (+ tags) — table d'index à créer à la migration v6 | |-----|-----| | Méthode | Route | Description | |---------|-------|-------------| | GET | `/api/v2/search?query=&workspace_id=&type=page|collection|all` | Résultats groupés par type, snippets | ### 4.18 Synchronisation offline (PWA) | **Interne** | `routers/sync.py` + `services/sync_engine.py` — auth session (`flowdeck_session`), CSRF-exempt (`/api/v2`) | |-----|-----| | Méthode | Route | Description | |---------|-------|-------------| | GET | `/api/v2/sync/delta?since=&workspace_id=` | Changements serveur depuis `since` (pages créées/màj/supprimées, collections) → `{changes, server_time, has_more}` | | POST | `/api/v2/sync/batch` | Rejoue un lot de mutations offline → `{results, conflicts, server_time}` | | GET | `/api/v2/sync/status?workspace_id=` | `{pending_count, last_sync, is_syncing, server_time}` | **Format batch** : `{device_id, mutations:[{id, type, payload, client_timestamp}]}` — `type` ∈ `page_create|page_update|page_delete|page_move|collection_create|collection_update|collection_delete`. `page_update` accepte `base_version` (colonne `sync_version`) pour la détection de conflit. **Conflits** : `edit_edit` (last-write-wins + rapport), `edit_delete` (page orpheline recréée), `create_create` (renommage `« … (copie offline) »`). Chaque mutation est tracée dans `offline_sync_queue`. --- ## 5. Webhooks sortants (à étendre en v2) **Mécanisme existant** (`app/services/webhook_outbound.py`) : - Table `webhook_subscriptions`: id, url, event, secret, active, created_at. - Événements actuels (8) : ```python EVENTS = [ "page.created", "page.updated", "page.deleted", "collection.created", "collection.updated", "collection.deleted", "comment.added", "page.moved", ] ``` - Dispatch `fire_event(event, payload)` → POST JSON avec headers `X-FlowDeck-Event` + `X-FlowDeck-Secret`. **Endpoints v2 webhooks** (wrapper du CRUD sortant existant + gestion des abonnements) : | Méthode | Route | Description | |---------|-------|-------------| | GET | `/api/v2/webhooks` | Abonnements de l'utilisateur | | POST | `/api/v2/webhooks` | Créer `{url, event, secret}` | | PATCH | `/api/v2/webhooks/{id}` | Activer/désactiver, changer url/secret | | DELETE | `/api/v2/webhooks/{id}` | Supprimer | | POST | `/api/v2/webhooks/{id}/test` | Événement de test `ping` | | GET | `/api/v2/webhooks/{id}/deliveries` | Journal des livraisons (à ajouter : table `webhook_deliveries`) | **Événements à ajouter** (parité Notion + besoins v6) : ```python "page.created", "page.updated", "page.deleted", "page.restored", "collection.created", "collection.updated", "collection.deleted", "collection_page.created", "collection_page.updated", "collection_page.deleted", "comment.added", "comment.resolved", "mention.created", "notification.created", "sprint.created", "sprint.updated", "sprint.completed", "share.created", "share.revoked", "page.published", "page.unpublished", "agent.run.started", "agent.run.finished", "agent.run.failed", ``` **Payload type** : ```json { "event": "page.updated", "data": { "id": 42, "workspace_id": 3, "updated_at": "2026-09-04T14:30:00Z" }, "actor": { "id": 1, "login": "bruno" }, "timestamp": "2026-09-04T14:30:00Z" } ``` - Signature : HMAC-SHA256 du body avec `secret`, header `X-FlowDeck-Signature`. - Retry : 3 tentatives (2s, 10s, 60s) + journal `webhook_deliveries` (status, http_code, error, duration_ms). --- ## 6. Schéma de données — résumé par ressource > Schéma complet dans `app/db.py` (`init_db()` + migrations). Tables principales : | Domaine | Tables | |---------|--------| | Auth | `users`, `login_history`, `user_tokens` (Gitea), `user_oauth_tokens`, **`api_tokens` (à créer)** | | Kanban legacy | `boards`, `cards`, `col_mapping`, `checklists`, `checklist_items`, `notes` | | Pages | `pages` (content_format='markdown'/'blocks', deleted_at, share_mode, is_published, publish_slug, is_shared, collection_id) | | Databases | `collections`, `collection_pages`, `collection_views`, `collection_properties`, `collection_data_sources`, `collection_dashboards` | | Multi-utilisateur | `workspaces`, `workspace_members`, `comments` (target_type/target_id/anchors), `page_history`, `favorites`, `recents`, `page_shares` | | Templates | `database_templates`, `page_templates` (is_recurring, recurrence_rule) | | Tags | `tags` (per-user), `page_tags` | | Tâches | `page_dependencies` (blocks/related, auto_shift), `sprints`, `sprint_pages` | | Notifications | `notifications` (+ users.notification_prefs) | | Forges | `gitea_private_pages`, (projets via adapters) | | Webhooks | `webhook_subscriptions`, **`webhook_deliveries` (à créer)** | | Agent | (v4.10.0 — à créer : `agents`, `agent_conversations`, `agent_messages`, `agent_actions`, `agent_skills`, `agent_triggers`) | **Règles** : - FKs avec `ON DELETE CASCADE` là où la suppression parent doit purger (pages, vues, props, membres, dépendances). - `property_values_json`, `schema_json`, `config_json`, `layout_json`, `options_json` : JSON textuel, **parse/validate côté service** (`property_types.py`). - WAL mode + `PRAGMA foreign_keys=ON` (déjà en place dans `get_conn()`). --- ## 7. Sécurité (v2) | Menace | Contre-mesure | |--------|---------------| | Token volé | Hash sha256 en DB, révocation, rotation, `expires_at`, scopes minimaux | | Rejeu CSRF | N/A côté token (Bearer) ; l'UI garde son CSRF middleware | | Brute force / abus | Rate limit par token + par IP (middleware existant étendu), 429 | | Injection SQL | SQL paramétré (`?` seul, jamais de f-string dans les requêtes) | | XSS (contenu) | Sanitisation à la sortie HTML (UI) ; l'API renvoie du JSON brut (aucun rendu) | | Exfiltration de secrets | Projection stricte : jamais `password_hash`, `*_token`, clés OAuth dans les réponses | | CORS | Configurer `allow_origins` pour les clients externes (settings v2) | | Fichiers | `validate_upload()` existant : extensions whitelist + 10 MB max | | Audit | Journaliser chaque mutation v2 (user, token_id, action, resource, ts) — table `api_audit_log` | --- ## 8. OpenAPI & documentation - Activer `docs_url="/docs"` et `redoc_url="/redoc"` **en production** sur le routeur v2 uniquement (ou protéger par token admin). - Générer `openapi.json` à la release et le versionner dans `docs/openapi-v2.json`. - Tags OpenAPI par domaine (workspaces, collections, pages, properties, views, comments…). - Exemples request/response réels dans les docstrings (FastAPI le génère automatiquement en OpenAPI). - Garder `docs_url=None` tant que v1 est seule (comportement actuel), l'activer lors du merge v2. --- ## 9. Checklist d'implémentation v6.3.0 — état livré 1. ✅ **Migration DB** (migration 20) : `api_tokens.scopes/expires_at`, `webhook_deliveries`, `api_audit_log`, `idempotency_keys` ; FTS5 déjà en migration 3. 2. ✅ **`PermissionManager`** (`app/services/permission_manager.py`) — héritage workspace/collection/page + groupes. 3. ✅ **Conventions** : handler RFC 7807 (sur `StarletteHTTPException`), ISO-8601, `parse_pagination()` + `X-Total-Count`, `require_scope()` hiérarchique. 4. ✅ **Wrappers v2 read** : collections, pages, my-tasks, search — pagination/filtres/tri/fields. 5. ✅ **Wrappers v2 write** : collections, pages, properties, views (+ dashboards, comments, notifications, favoris, tags, partage, historique, sprints, templates, export/import, workspaces, admin). 6. ⚠️ **Webhooks v2** : CRUD abonnements + `/test` + `/deliveries` livrés. **Reporté** : signature HMAC `X-FlowDeck-Signature`, retry 2s/10s/60s, +20 événements. 7. ✅ **Reste des ressources** : sprints, templates, dashboards, favoris, tags, partage, notifications, admin. 8. ✅ **Recherche FTS** (`/api/v2/search`, repli LIKE). 9. ✅ **OpenAPI** : `/docs` + `/redoc` activés, `docs/openapi-v2.json` régénéré à chaque bump (511 chemins, `info.version` = VERSION courante). 10. ✅ **Tests** (`tests/test_public_api_v2.py`) : **24 tests** — auth scopes, CRUD par ressource, pagination, RFC 7807, idempotence, webhooks, search, admin. 11. ✅ **Documentation** : `ROADMAP.md`, `CHANGELOG.md`, ce guide + `/help`. > **Reporté v6.4** : webhooks HMAC/retry/events étendus ; `/api/v2/sync` en Bearer ; endpoints forges `file/commits/issues` complets via `ForgeAdapter` ; rate limit distribué (Redis). ## 10. Références - Schéma DB : `app/db.py` - Routeurs existants : `app/routers/*.py` - Frontend : `app/templates/*.html`, `static/js/app.js` - Webhooks sortants : `app/services/webhook_outbound.py` - Types de propriétés : `app/services/property_types.py` - Roadmap : `ROADMAP.md` (v6.0.0 — API publique, realtime, synced blocks, web clipper, permissions granulaires) --- ## 11. Documents de conception détaillée v6.0.0 Chaque feature v6.0.0 dispose d'un document de conception détaillé dans `/docs/` : | Feature | Document | Description | |---------|----------|-------------| | **PWA** | [`V6_PWA_Progressive_Web_App.md`](/docs/V6_PWA_Progressive_Web_App.md) | Offline support, service worker, IndexedDB, sync batch | | **SSO/SAML** | [`V6_SSO_SAML_Enterprise_Auth.md`](/docs/V6_SSO_SAML_Enterprise_Auth.md) | SAML 2.0, OIDC, auto-provisioning, group mapping | | **Permissions granulaires** | [`V6_Granular_Permissions.md`](/docs/V6_Granular_Permissions.md) | Page-level, property-level, collection-level, groupes | | **Web Clipper** | [`V6_Web_Clipper.md`](/docs/V6_Web_Clipper.md) | Extension navigateur, capture d'articles, OAuth | > **Note** : Le présent guide couvre la couche API v2 commune à toutes les features. Chaque document de conception ci-dessus détaille les endpoints, tables, services et UI spécifiques à sa feature.