41 KiB
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/v2est livrée : routeurapp/routers/api_v2.py, helpersapp/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/*) dansapp/routers/api_v2_agent.py, logique partagéeapp/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. 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/syncvers Bearer ;REST /api/v2/projects/*/file|commits|issuescomplets viaForgeAdapter.
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
- Un endpoint public = un wrapper autour de la logique existante (un seul chemin de code).
- L'API publique ne renvoie jamais de HTML, de secrets, ni de colonnes internes (
password_hash, tokens OAuth). - Toute mutation passe par les mêmes validations que l'UI (CSRF concerne l'UI seule ; les tokens remplacent CSRF).
- 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>. 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_urln'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
- La propriété des conversations est vérifiée (
user_iddu token) : un token ne voit jamais les conversations d'un autre utilisateur (404, pas403, pour ne pas fuiter). - Le run est synchrone : le moteur (
AgentEngine), l'ACL (PermissionManager), le journalagent_actionset les webhooks sont exactement ceux de l'UI — aucun second chemin. - Cycle de vie webhook :
agent.run.started→agent.run.finished|agent.run.failed(catalogueapp/services/webhook_outbound.py, abonnementagent.*possible). - Chaque mutation écrit
api_audit_log(agent.create,agent.run,skill.import, …).
3. Conventions cibles pour l'API v2
3.1 Auth & tokens
Nouvelle table api_tokens (remplace l'usage détourné de user_tokens) :
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 ensha256. - 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 FastAPIrequire_scope("write"). - Compat v1 : garder le fallback
fd-public-keyuniquement sisettings.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 stockeYYYY-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
*_jsonstocké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)
{
"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[<property>]=<value> (AND implicite), sort=<property>, sort=-<property> (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
POSTde création accepte un header optionnelIdempotency-Key— sur conflit, renvoyer la ressource existante créée avec cette clé (tableidempotency_keys).- Les
PATCHsont 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
defaultdictin-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→ wrapperpublic_list_collectionsexistant à enrichir ; les mutations s'appuient sur les routeurscollections.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{"<property_id>": <valeur typée>}— utiliserproperty_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 |
| 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 |
| 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
ForgeAdaptercommune (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 |
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=<epoch>&workspace_id=<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=<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) :
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 headersX-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) :
"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 :
{
"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, headerX-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 CASCADElà 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 dansget_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"etredoc_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 dansdocs/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=Nonetant que v1 est seule (comportement actuel), l'activer lors du merge v2.
9. Checklist d'implémentation v6.3.0 — état livré
- ✅ Migration DB (migration 20) :
api_tokens.scopes/expires_at,webhook_deliveries,api_audit_log,idempotency_keys; FTS5 déjà en migration 3. - ✅
PermissionManager(app/services/permission_manager.py) — héritage workspace/collection/page + groupes. - ✅ Conventions : handler RFC 7807 (sur
StarletteHTTPException), ISO-8601,parse_pagination()+X-Total-Count,require_scope()hiérarchique. - ✅ Wrappers v2 read : collections, pages, my-tasks, search — pagination/filtres/tri/fields.
- ✅ Wrappers v2 write : collections, pages, properties, views (+ dashboards, comments, notifications, favoris, tags, partage, historique, sprints, templates, export/import, workspaces, admin).
- ⚠️ Webhooks v2 : CRUD abonnements +
/test+/deliverieslivrés. Reporté : signature HMACX-FlowDeck-Signature, retry 2s/10s/60s, +20 événements. - ✅ Reste des ressources : sprints, templates, dashboards, favoris, tags, partage, notifications, admin.
- ✅ Recherche FTS (
/api/v2/search, repli LIKE). - ✅ OpenAPI :
/docs+/redocactivés,docs/openapi-v2.jsongénéré (402 chemins). - ✅ Tests (
tests/test_public_api_v2.py) : 24 tests — auth scopes, CRUD par ressource, pagination, RFC 7807, idempotence, webhooks, search, admin. - ✅ Documentation :
ROADMAP.md,CHANGELOG.md, ce guide +/help.
Reporté v6.4 : webhooks HMAC/retry/events étendus ;
/api/v2/syncen Bearer ; endpoints forgesfile/commits/issuescomplets viaForgeAdapter; 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 |
Offline support, service worker, IndexedDB, sync batch |
| SSO/SAML | V6_SSO_SAML_Enterprise_Auth.md |
SAML 2.0, OIDC, auto-provisioning, group mapping |
| Permissions granulaires | V6_Granular_Permissions.md |
Page-level, property-level, collection-level, groupes |
| Web Clipper | 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.