{"openapi":"3.1.0","info":{"title":"Spunto CMS API","description":"API REST du CMS Spunto. REST uniquement — pas de GraphQL. Le seul chemin d'accès côté consommateur est le client généré `@spunto-cms/client`.","version":"0.1.0"},"servers":[{"url":"/api","description":"Derrière le proxy — l'adresse publique"},{"url":"/","description":"L'API directement, sur son port"}],"paths":{"/health":{"get":{"operationId":"getHealth","summary":"Health check","description":"État du process et joignabilité de Postgres.","tags":["Health"],"responses":{"200":{"description":"L'API répond","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Health"}}}}}}},"/auth/registration":{"get":{"operationId":"getRegistrationPolicy","summary":"Registration policy","description":"À quelles conditions cette instance accepte une inscription, et par quelles portes. Public, et c'est voulu : un formulaire qui ne peut pas le savoir fait saisir un mot de passe pour se prendre un 403, et une tentative d'inscription l'apprendrait de toute façon.","tags":["Auth"],"responses":{"200":{"description":"Le mode d'inscription de cette instance","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RegistrationPolicy"}}}}}}},"/auth/invitation":{"get":{"operationId":"previewInvitation","summary":"Preview an invitation","description":"Ce qu'un lien d'invitation annonce : quel workspace, quelle adresse, quel rôle. Accessible sans compte — il faut bien pouvoir lire l'invitation avant d'en avoir un — mais uniquement au porteur du jeton. Un lien mort rend 404 sans dire s'il est inconnu, révoqué ou expiré.","tags":["Auth"],"responses":{"200":{"description":"L'invitation que ce jeton ouvre","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InvitationPreview"}}}},"404":{"description":"Jeton inconnu, révoqué ou expiré","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"parameters":[{"in":"query","name":"token","schema":{"type":"string","minLength":1,"maxLength":200},"required":true}]}},"/auth/register":{"post":{"operationId":"register","summary":"Register","description":"Crée un compte et ouvre une session. Le jeton n'est rendu qu'ici : l'API n'en stocke que le SHA-256.\n\nL'inscription **libre est fermée par défaut** : sans `invitation`, la réponse est 403 tant que l'instance n'est pas en `REGISTRATION_MODE=open` (voir `GET /auth/registration`). Avec une invitation valide, le compte naît membre du workspace qui l'a émise, avec le rôle qu'elle porte.","tags":["Auth"],"responses":{"201":{"description":"Compte créé, session ouverte","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Session"}}}},"403":{"description":"Inscription fermée, ou invitation invalide, expirée, ou destinée à une autre adresse","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Email déjà utilisé","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Register"}}}}}},"/auth/login":{"post":{"operationId":"login","summary":"Log in","description":"Rend le même message et le même temps de réponse que l'email existe ou non — l'API ne doit pas servir d'oracle d'énumération de comptes.","tags":["Auth"],"responses":{"200":{"description":"Session ouverte","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Session"}}}},"401":{"description":"Identifiants invalides","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Login"}}}}}},"/auth/google":{"post":{"operationId":"signInWithGoogle","summary":"Sign in with Google","description":"Échange un `id_token` Google contre une session — **le même appel inscrit et connecte**.\n\nL'`id_token` est revérifié ici contre les JWKS de Google (`iss`, `aud` = `GOOGLE_CLIENT_ID`, `exp`, `email_verified`) : le dance OAuth vit côté front, la confiance non.\n\n**La politique d'inscription s'applique aussi ici.** En mode `invite`, une identité Google inconnue sans invitation valide rend 403, exactement comme `POST /auth/register` — sinon Google contournerait `REGISTRATION_MODE`.\n\n**Aucun liage automatique par l'adresse** : si un compte existe déjà pour l'adresse du compte Google sans porter cette identité, la réponse est 409. Le liage se fait dans l'autre sens, depuis un compte authentifié (`POST /auth/identities/google`).","tags":["Auth"],"responses":{"200":{"description":"Identité déjà connue : session ouverte","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Session"}}}},"201":{"description":"Identité inconnue et inscription autorisée : compte créé, session ouverte","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Session"}}}},"401":{"description":"`id_token` invalide, expiré, émis pour une autre application, ou adresse non vérifiée par Google","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Connexion Google désactivée sur cette instance, inscription fermée, ou invitation invalide, expirée, ou destinée à une autre adresse","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Un compte existe déjà pour cette adresse et ne porte pas cette identité Google","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GoogleSignIn"}}}}}},"/auth/identities":{"get":{"operationId":"listMyIdentities","summary":"My external identities","description":"Les fournisseurs par lesquels ce compte peut entrer, en plus de son mot de passe s'il en a un. Le `subject` du fournisseur n'y figure pas : c'est un identifiant stable de la personne chez un tiers, il n'a rien à faire dans une réponse.","tags":["Auth"],"responses":{"200":{"description":"Les identités externes du compte","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/UserIdentity"}}}}},"401":{"description":"Aucune session valide","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/auth/identities/google":{"post":{"operationId":"linkGoogleIdentity","summary":"Link Google to my account","description":"Rattache une identité Google au compte **déjà authentifié**. C'est le seul sens dans lequel le liage est sûr : rattacher sur la foi d'une adresse laisserait quelqu'un prendre le contrôle d'un compte en s'inscrivant d'abord avec l'adresse de sa victime (docs/inscription.md).\n\nL'adresse Google n'a pas à être celle du compte : la preuve, ici, c'est la session. Idempotent si l'identité est déjà celle de ce compte.","tags":["Auth"],"responses":{"200":{"description":"Identité rattachée","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserIdentity"}}}},"401":{"description":"Aucune session valide, ou `id_token` invalide","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Connexion Google désactivée, ou clé API / app plutôt qu'une session humaine","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Ce compte Google est déjà lié ailleurs, ou ce compte a déjà une identité Google","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LinkGoogle"}}}}}},"/auth/logout":{"post":{"operationId":"logout","summary":"Log out","description":"Révoque la session portée par le jeton. Idempotent.","tags":["Auth"],"responses":{"204":{"description":"Session révoquée"},"401":{"description":"Aucune session","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/auth/me":{"get":{"operationId":"getCurrentUser","summary":"Current user","tags":["Auth"],"responses":{"200":{"description":"L'utilisateur du jeton","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AuthUser"}}}},"401":{"description":"Aucune session valide","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/oauth/token":{"post":{"operationId":"oauthToken","summary":"Exchange an authorization code for an app token","description":"**Porte publique** : le porteur n'a pas encore de jeton, c'est ce qu'il vient chercher. Trois preuves sont exigées ensemble — le `clientSecret` (l'app est bien celle qu'elle dit), PKCE (le code n'a pas été intercepté en route), et une `redirectUri` identique à celle du consentement. Le code est à usage unique, consommé dans la transaction qui crée l'installation.","tags":["Apps"],"responses":{"200":{"description":"Le jeton d'installation. Il n'expire pas : la durée de vie, c'est l'installation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OAuthTokenResponse"}}}},"400":{"description":"Requête malformée","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Identifiants d'app, code, PKCE ou redirect_uri refusés","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OAuthTokenRequest"}}}}}},"/workspaces":{"get":{"operationId":"listWorkspaces","summary":"List my workspaces","description":"**Mes** workspaces, jamais « les » workspaces : la liste est bornée par `membership`, avec mon rôle dans chacun.","tags":["Workspaces"],"responses":{"200":{"description":"Les workspaces dont je suis membre","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/WorkspaceMembership"}}}}},"401":{"description":"Aucune session valide","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"operationId":"createWorkspace","summary":"Create workspace","description":"Crée le workspace, m'en fait le `owner`, et pose ses trois statuts par défaut — un item porte un `status_id` NOT NULL (RFC §6.3).","tags":["Workspaces"],"responses":{"201":{"description":"Workspace créé","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WorkspaceMembership"}}}},"403":{"description":"Porteur qui est une clé API — elle est scopée à son workspace","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Slug déjà pris","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateWorkspace"}}}}}},"/workspaces/{workspaceId}":{"get":{"operationId":"getWorkspace","summary":"Get workspace","description":"Le workspace **tel que je le vois** : mon rôle voyage avec lui.","tags":["Workspaces"],"responses":{"200":{"description":"Le workspace et mon rôle dedans","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WorkspaceMembership"}}}},"404":{"description":"Workspace inconnu — ou dont je ne suis pas membre, ce qui se répond pareil","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"parameters":[{"in":"path","name":"workspaceId","schema":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"required":true}]},"patch":{"operationId":"updateWorkspace","summary":"Update workspace","description":"Le **nom** et la mise en page des couvertures (`coverLayout`) — chacun optionnel, absent = inchangé. Pas le `slug` : il est unique, il a pu être noté par une intégration, et il n'entre dans aucune URL de l'app : le changer serait une rupture invisible pour un bénéfice nul.","tags":["Workspaces"],"responses":{"200":{"description":"Le workspace modifié","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WorkspaceMembership"}}}},"403":{"description":"Rôle insuffisant — `admin` au moins","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"parameters":[{"in":"path","name":"workspaceId","schema":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"required":true}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateWorkspace"}}}}},"delete":{"operationId":"deleteWorkspace","summary":"Delete workspace","description":"Supprime le workspace et tout ce qui lui appartient — cartes, listes, propriétés, statuts, vues, membres, clés API, invitations, apps installées, webhooks, fichiers (octets compris) et le journal d'événements. Irréversible. Réservé au rôle `owner`, refusé à une clé API (une session utilisateur est exigée), et confirmé par le `slug` du workspace dans le corps.","tags":["Workspaces"],"responses":{"204":{"description":"Workspace supprimé"},"400":{"description":"Le `slug` du corps ne correspond pas à celui du workspace","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Rôle insuffisant, ou porteur qui est une clé API","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Workspace inconnu — ou dont je ne suis pas membre","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"parameters":[{"in":"path","name":"workspaceId","schema":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"required":true}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeleteWorkspace"}}}}}},"/workspaces/{workspaceId}/overview":{"get":{"operationId":"workspaceOverview","summary":"Workspace overview","description":"Les compteurs de l'écran d'accueil : cartes par catégorie, par liste, activité sur quatorze jours, taille du catalogue. **Trois requêtes**, quel que soit le nombre de listes — et une décision de coût assumée (RFC 0001 §9).","tags":["Workspaces"],"responses":{"200":{"description":"Les compteurs du workspace","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WorkspaceOverview"}}}}},"parameters":[{"in":"path","name":"workspaceId","schema":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"required":true}]}},"/workspaces/{workspaceId}/members":{"get":{"operationId":"listMembers","summary":"List members","tags":["Workspaces"],"responses":{"200":{"description":"Les membres du workspace et leur rôle","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/Member"}}}}}},"parameters":[{"in":"path","name":"workspaceId","schema":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"required":true}]},"post":{"operationId":"inviteMember","summary":"Invite a member","description":"Rattache un compte **existant** au workspace, ou change son rôle. Il n'y a pas d'invitation par email dans cette version : elle demande un jeton à durée de vie, un envoi, une page d'acceptation — donc sa propre carte.","tags":["Workspaces"],"responses":{"201":{"description":"Membre ajouté ou rôle mis à jour","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Member"}}}},"403":{"description":"Rôle insuffisant","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Aucun compte pour cet email","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Rétrograderait le dernier propriétaire du workspace","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"parameters":[{"in":"path","name":"workspaceId","schema":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"required":true}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InviteMember"}}}}}},"/workspaces/{workspaceId}/members/me":{"patch":{"operationId":"updateMyMembership","summary":"Set my avatar","description":"Pose ou retire **sa propre** photo dans ce workspace. Le fichier est une pièce jointe image du même workspace : un visage n'a pas plus d'URL publique qu'une pièce jointe (`docs/fichiers.md`).","tags":["Workspaces"],"responses":{"200":{"description":"L'appartenance mise à jour","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Member"}}}},"400":{"description":"Fichier inconnu dans ce workspace, ou qui n'est pas une image","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Porteur sans visage : clé API ou app installée","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"parameters":[{"in":"path","name":"workspaceId","schema":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"required":true}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateMyMembership"}}}}}},"/workspaces/{workspaceId}/members/{userId}":{"delete":{"operationId":"removeMember","summary":"Remove a member","tags":["Workspaces"],"responses":{"204":{"description":"Membre retiré"},"403":{"description":"Rôle insuffisant","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Dernier propriétaire du workspace","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"parameters":[{"in":"path","name":"workspaceId","schema":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"required":true},{"in":"path","name":"userId","schema":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"required":true}]}},"/workspaces/{workspaceId}/fields":{"get":{"operationId":"listFields","summary":"List fields","description":"Le registre de propriétés du workspace (RFC §6.2). Une collection ne définit aucune propriété, elle choisit dans ce registre.","tags":["Workspaces"],"responses":{"200":{"description":"Les champs du registre","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/Field"}}}}}},"parameters":[{"in":"path","name":"workspaceId","schema":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"required":true}]},"post":{"operationId":"createField","summary":"Create field","tags":["Workspaces"],"responses":{"201":{"description":"Champ créé","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Field"}}}},"400":{"description":"Clé invalide ou déjà prise dans ce workspace","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Rôle insuffisant","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"parameters":[{"in":"path","name":"workspaceId","schema":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"required":true}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateField"}}}}}},"/workspaces/{workspaceId}/fields/{fieldId}":{"patch":{"operationId":"updateField","summary":"Update field","description":"Renomme une propriété ou change sa configuration. Ni `key` ni `type` ne sont modifiables : la clé porte les valeurs déjà écrites, le type porte le cast des index d'expression.","tags":["Workspaces"],"responses":{"200":{"description":"Champ mis à jour","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Field"}}}},"404":{"description":"Champ inconnu","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"parameters":[{"in":"path","name":"workspaceId","schema":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"required":true},{"in":"path","name":"fieldId","schema":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"required":true}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateField"}}}}},"delete":{"operationId":"deleteField","summary":"Delete field","description":"Retire la propriété du registre **et** sa clé de tous les items. Refusé (409) si une vue enregistrée s'en sert : sa définition deviendrait incompilable.","tags":["Workspaces"],"responses":{"204":{"description":"Champ supprimé"},"409":{"description":"Champ utilisé par une vue","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"parameters":[{"in":"path","name":"workspaceId","schema":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"required":true},{"in":"path","name":"fieldId","schema":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"required":true}]}},"/workspaces/{workspaceId}/fields/{fieldId}/options":{"post":{"operationId":"upsertFieldOption","summary":"Upsert field option","description":"Déclare une option d'un `select` ou d'un `multi_select` (en fin de liste) ou change sa couleur, sans renvoyer le reste de `config` : la fusion se fait côté serveur, sous verrou, donc deux ajouts simultanés s'additionnent au lieu de s'effacer. Idempotent — une option déjà déclarée avec la même couleur n'écrit rien.","tags":["Workspaces"],"responses":{"200":{"description":"La propriété, avec ses options à jour","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Field"}}}},"400":{"description":"Propriété sans options (ni `select` ni `multi_select`), ou 200 options atteintes","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Champ inconnu","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"parameters":[{"in":"path","name":"workspaceId","schema":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"required":true},{"in":"path","name":"fieldId","schema":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"required":true}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpsertFieldOption"}}}}}},"/workspaces/{workspaceId}/statuses":{"get":{"operationId":"listStatuses","summary":"List statuses","tags":["Workspaces"],"responses":{"200":{"description":"Les statuts du workspace, dans leur ordre","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/Status"}}}}}},"parameters":[{"in":"path","name":"workspaceId","schema":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"required":true}]},"post":{"operationId":"createStatus","summary":"Create status","tags":["Workspaces"],"responses":{"201":{"description":"Statut créé","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}}},"403":{"description":"Rôle insuffisant","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"parameters":[{"in":"path","name":"workspaceId","schema":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"required":true}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateStatus"}}}}}},"/workspaces/{workspaceId}/statuses/{statusId}":{"patch":{"operationId":"updateStatus","summary":"Update status","tags":["Workspaces"],"responses":{"200":{"description":"Statut mis à jour","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}}},"404":{"description":"Statut inconnu","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"parameters":[{"in":"path","name":"workspaceId","schema":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"required":true},{"in":"path","name":"statusId","schema":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"required":true}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateStatus"}}}}},"delete":{"operationId":"deleteStatus","summary":"Delete status","description":"Refusé (409) si des items le portent, ou si c'est le dernier : `item.status_id` est NOT NULL et sa FK est en RESTRICT (RFC §6.3).","tags":["Workspaces"],"responses":{"204":{"description":"Statut supprimé"},"409":{"description":"Statut encore utilisé, ou dernier du workspace","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"parameters":[{"in":"path","name":"workspaceId","schema":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"required":true},{"in":"path","name":"statusId","schema":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"required":true}]}},"/workspaces/{workspaceId}/collections":{"get":{"operationId":"listCollections","summary":"List collections","tags":["Collections"],"responses":{"200":{"description":"Les listes du workspace","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/Collection"}}}}}},"parameters":[{"in":"path","name":"workspaceId","schema":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"required":true}]},"post":{"operationId":"createCollection","summary":"Create collection","description":"Crée une liste. `fieldIds` ne définit pas de propriétés : il choisit celles du registre du workspace que cette liste affiche (RFC §6.2).","tags":["Collections"],"responses":{"201":{"description":"Collection créée","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Collection"}}}},"409":{"description":"Préfixe de clés déjà pris dans ce workspace","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"parameters":[{"in":"path","name":"workspaceId","schema":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"required":true}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateCollection"}}}}}},"/workspaces/{workspaceId}/collections/order":{"put":{"operationId":"reorderCollections","summary":"Reorder collections","description":"Réécrit le groupe et la position des listes citées, de 0 à n-1. Les listes non citées ne bougent pas. Une seule transaction, un événement par liste **réellement** déplacée.","tags":["Collections"],"responses":{"200":{"description":"Toutes les listes du workspace, dans leur nouvel ordre","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/Collection"}}}}},"409":{"description":"Une liste ou un groupe cité n'existe pas dans ce workspace","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"parameters":[{"in":"path","name":"workspaceId","schema":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"required":true}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReorderCollections"}}}}}},"/workspaces/{workspaceId}/collections/{collectionId}":{"get":{"operationId":"getCollection","summary":"Get collection","description":"La collection et tout ce qu'il faut pour l'afficher : champs, statuts, vues.","tags":["Collections"],"responses":{"200":{"description":"La collection et son contexte","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CollectionDetail"}}}},"404":{"description":"Collection inconnue","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"parameters":[{"in":"path","name":"workspaceId","schema":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"required":true},{"in":"path","name":"collectionId","schema":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"required":true}]},"patch":{"operationId":"updateCollection","summary":"Update collection","description":"Renomme la liste, ou remplace la sélection de colonnes affichées (RFC §6.2).","tags":["Collections"],"responses":{"200":{"description":"Collection mise à jour","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Collection"}}}},"404":{"description":"Collection inconnue","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Préfixe de clés déjà pris dans ce workspace","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"parameters":[{"in":"path","name":"workspaceId","schema":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"required":true},{"in":"path","name":"collectionId","schema":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"required":true}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateCollection"}}}}},"delete":{"operationId":"deleteCollection","summary":"Delete collection","description":"Supprime la liste **et les items dont elle est la liste d'origine**. Un item seulement rattaché en liste secondaire survit dans sa liste d'origine (RFC §6.1).","tags":["Collections"],"responses":{"204":{"description":"Collection supprimée"},"404":{"description":"Collection inconnue","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"parameters":[{"in":"path","name":"workspaceId","schema":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"required":true},{"in":"path","name":"collectionId","schema":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"required":true}]}},"/workspaces/{workspaceId}/collections/{collectionId}/views":{"post":{"operationId":"createView","summary":"Create view","tags":["Collections"],"responses":{"201":{"description":"Vue créée","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CollectionView"}}}}},"parameters":[{"in":"path","name":"workspaceId","schema":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"required":true},{"in":"path","name":"collectionId","schema":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"required":true}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateCollectionView"}}}}}},"/workspaces/{workspaceId}/collections/{collectionId}/views/{viewId}":{"patch":{"operationId":"updateView","summary":"Update view","description":"La `definition` et le `display` sont remplacés en bloc — retirer un filtre, ou revenir à l'ordre de colonnes par défaut, serait sinon inexprimable.","tags":["Collections"],"responses":{"200":{"description":"Vue mise à jour","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CollectionView"}}}},"404":{"description":"Vue inconnue","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"parameters":[{"in":"path","name":"workspaceId","schema":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"required":true},{"in":"path","name":"collectionId","schema":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"required":true},{"in":"path","name":"viewId","schema":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"required":true}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateCollectionView"}}}}},"delete":{"operationId":"deleteView","summary":"Delete view","tags":["Collections"],"responses":{"204":{"description":"Vue supprimée"},"404":{"description":"Vue inconnue","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"parameters":[{"in":"path","name":"workspaceId","schema":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"required":true},{"in":"path","name":"collectionId","schema":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"required":true},{"in":"path","name":"viewId","schema":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"required":true}]}},"/workspaces/{workspaceId}/collections/{collectionId}/items":{"post":{"operationId":"createItem","summary":"Create item","tags":["Items"],"responses":{"201":{"description":"Item créé, corps compris","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ItemDetail"}}}},"400":{"description":"Parent ou statut invalide, ou valeur de propriété refusée par le registre","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"parameters":[{"in":"path","name":"workspaceId","schema":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"required":true},{"in":"path","name":"collectionId","schema":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"required":true}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateItem"}}}}}},"/workspaces/{workspaceId}/collections/{collectionId}/query":{"post":{"operationId":"queryView","summary":"Query a view","description":"Compile une `ViewDefinition` en SQL et rend une page keyset. Aucun `OFFSET`, jamais (RFC §6.6 : 348 ms contre 3 ms à la page ~12 000).","tags":["Views"],"responses":{"200":{"description":"Une page d'items et le curseur suivant","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ItemPage"}}}},"400":{"description":"Définition de vue ou curseur invalide","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"parameters":[{"in":"path","name":"workspaceId","schema":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"required":true},{"in":"path","name":"collectionId","schema":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"required":true}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueryView"}}}}}},"/workspaces/{workspaceId}/collections/{collectionId}/group-counts":{"post":{"operationId":"groupCounts","summary":"Group counts for a view","description":"Requête **séparée** de la page : les compteurs sont ce qui casse en premier (RFC §9.2, ~82 ns par item), donc leur coût est une décision séparée.","tags":["Views"],"responses":{"200":{"description":"Le compte par valeur de groupement","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/GroupCount"}}}}},"400":{"description":"Définition sans `groupBy`, ou champ inconnu","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"parameters":[{"in":"path","name":"workspaceId","schema":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"required":true},{"in":"path","name":"collectionId","schema":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"required":true}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ViewRequest"}}}}}},"/workspaces/{workspaceId}/collections/{collectionId}/grouped-query":{"post":{"operationId":"groupedQueryView","summary":"Query a view, grouped","description":"Compile la même `ViewDefinition` en **une page keyset par groupe**. Les groupes demandés sont rendus même vides : une colonne de kanban sans carte doit exister pour qu'on puisse y déposer, et un jour sans carte pour qu'un calendrier ait une case.","tags":["Views"],"responses":{"200":{"description":"Un groupe par colonne, chacun avec son compte et son curseur","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GroupedPage"}}}},"400":{"description":"Définition sans `groupBy`, champ ingroupable, ou curseur invalide","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"parameters":[{"in":"path","name":"workspaceId","schema":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"required":true},{"in":"path","name":"collectionId","schema":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"required":true}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GroupedQuery"}}}}}},"/workspaces/{workspaceId}/collections/{collectionId}/bulk-preview":{"post":{"operationId":"bulkPreview","summary":"Count what a bulk update would touch","description":"Ce que le filtre sélectionne **maintenant**. C'est ce que l'UI affiche dans sa confirmation : l'écart entre ce que l'utilisateur a vu et ce qui s'exécutera ne peut pas être supprimé (le filtre est évalué au moment de l'`UPDATE`), donc on le rend visible.","tags":["Views"],"responses":{"200":{"description":"Le compte, et les deux seuils","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BulkPreview"}}}},"400":{"description":"Définition de vue invalide","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"parameters":[{"in":"path","name":"workspaceId","schema":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"required":true},{"in":"path","name":"collectionId","schema":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"required":true}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ViewRequest"}}}}}},"/workspaces/{workspaceId}/collections/{collectionId}/bulk-update":{"post":{"operationId":"bulkUpdate","summary":"Update every card a filter selects","description":"Un `UPDATE … WHERE`, une transaction — **pas** N requêtes. Le filtre est celui des vues, compilé au même endroit ; la valeur posée est validée une fois contre le registre, pas mille. Écrit **un événement par carte**, tous porteurs du même `batchId` (sinon rien n'est rejouable), et n'émet **qu'un** webhook pour le lot.","tags":["Views"],"responses":{"200":{"description":"Le lot et le nombre de cartes réellement modifiées","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BulkResult"}}}},"400":{"description":"Patch vide, définition invalide, ou valeur refusée par le registre","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Au-delà du plafond, ou `expectedCount` absent/différent du compte réel — le message dit les deux chiffres","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"parameters":[{"in":"path","name":"workspaceId","schema":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"required":true},{"in":"path","name":"collectionId","schema":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"required":true}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BulkUpdate"}}}}}},"/workspaces/{workspaceId}/collections/{collectionId}/index-hints":{"post":{"operationId":"indexHints","summary":"Index hints for a view","description":"L'index que cette vue voudrait — **décrit, pas créé** (RFC §5.4). C'est ce qui rend la stratégie « index d'expression à la demande » opérationnelle plutôt que manuelle.","tags":["Views"],"responses":{"200":{"description":"Les index souhaités par cette vue","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/IndexHint"}}}}},"400":{"description":"Champ inconnu dans la définition","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"parameters":[{"in":"path","name":"workspaceId","schema":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"required":true},{"in":"path","name":"collectionId","schema":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"required":true}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ViewRequest"}}}}}},"/workspaces/{workspaceId}/collection-groups":{"get":{"operationId":"listCollectionGroups","summary":"List collection groups","description":"Les groupes du workspace, **à plat** — `parentId` dit qui range qui. Deux niveaux au plus, donc la barre latérale se rend sans requête récursive ni fil d'Ariane.","tags":["Collections"],"responses":{"200":{"description":"Les groupes","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/CollectionGroup"}}}}}},"parameters":[{"in":"path","name":"workspaceId","schema":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"required":true}]},"post":{"operationId":"createCollectionGroup","summary":"Create collection group","tags":["Collections"],"responses":{"201":{"description":"Groupe créé","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CollectionGroup"}}}},"409":{"description":"Le parent n'existe pas dans ce workspace, ou est déjà un sous-groupe — le rangement s'arrête à deux niveaux","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"parameters":[{"in":"path","name":"workspaceId","schema":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"required":true}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateCollectionGroup"}}}}}},"/workspaces/{workspaceId}/collection-groups/order":{"put":{"operationId":"reorderCollectionGroups","summary":"Reorder collection groups","description":"« Ce parent contient ces groupes, dans cet ordre » — les positions sont réécrites de 0 à n-1, et `parentId` en fait aussi un déplacement. Un seul appel, une seule transaction. Deux niveaux au plus : un groupe qui contient des sous-groupes ne peut pas en devenir un.","tags":["Collections"],"responses":{"200":{"description":"Les groupes, dans leur nouvel ordre","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/CollectionGroup"}}}}},"409":{"description":"Un groupe cité n'existe pas dans ce workspace, ou le rangement dépasserait deux niveaux","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"parameters":[{"in":"path","name":"workspaceId","schema":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"required":true}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReorderGroups"}}}}}},"/workspaces/{workspaceId}/collection-groups/{groupId}":{"patch":{"operationId":"updateCollectionGroup","summary":"Update collection group","tags":["Collections"],"responses":{"200":{"description":"Groupe mis à jour","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CollectionGroup"}}}},"404":{"description":"Groupe inconnu","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Le rangement dépasserait deux niveaux : le parent visé est déjà un sous-groupe, ou ce groupe contient lui-même des sous-groupes","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"parameters":[{"in":"path","name":"workspaceId","schema":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"required":true},{"in":"path","name":"groupId","schema":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"required":true}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateCollectionGroup"}}}}},"delete":{"operationId":"deleteCollectionGroup","summary":"Delete collection group","description":"Supprime le groupe et **détache** ce qu'il contenait — ses listes repassent à la racine, ses sous-groupes aussi (avec leurs listes, qui ne bougent pas). Aucune liste, aucune carte n'est supprimée.","tags":["Collections"],"responses":{"204":{"description":"Groupe supprimé, son contenu détaché d'un cran"},"404":{"description":"Groupe inconnu","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"parameters":[{"in":"path","name":"workspaceId","schema":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"required":true},{"in":"path","name":"groupId","schema":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"required":true}]}},"/workspaces/{workspaceId}/search":{"get":{"operationId":"search","summary":"Search the workspace","description":"Cherche dans les **titres** des cartes et les noms des listes. Une clé lisible (`SPR-12`) remonte sa carte en tête, même si le titre ne correspond pas. Ne lit pas le corps des cartes, et ignore les archivées.","tags":["Workspaces"],"responses":{"200":{"description":"Ce qui correspond, cartes et listes séparées","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SearchResults"}}}}},"parameters":[{"in":"path","name":"workspaceId","schema":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"required":true},{"in":"query","name":"q","schema":{"type":"string","maxLength":200},"required":true,"description":"Ce qui a été tapé. Une clé (`SPR-12`) remonte la carte exacte."},{"in":"query","name":"limit","schema":{"type":"integer","minimum":1,"maximum":20}}]}},"/workspaces/{workspaceId}/items/{itemId}":{"get":{"operationId":"getItem","summary":"Get item","tags":["Items"],"responses":{"200":{"description":"L'item **et son corps** — les pages de vue, elles, rendent `Item`","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ItemDetail"}}}},"404":{"description":"Item inconnu","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"parameters":[{"in":"path","name":"workspaceId","schema":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"required":true},{"in":"path","name":"itemId","schema":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"required":true}]},"patch":{"operationId":"updateItem","summary":"Update item","description":"`properties` est **fusionné**, pas remplacé : un PATCH sur une propriété n'efface pas les autres.","tags":["Items"],"responses":{"200":{"description":"Item mis à jour","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ItemDetail"}}}},"400":{"description":"Valeur de propriété refusée par le registre, ou couverture qui n'est pas une image du workspace","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Item inconnu","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"parameters":[{"in":"path","name":"workspaceId","schema":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"required":true},{"in":"path","name":"itemId","schema":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"required":true}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateItem"}}}}},"delete":{"operationId":"deleteItem","summary":"Delete item","description":"Suppression **définitive**, sous-arbre compris. L'équivalent réversible est `PATCH { archived: true }`.","tags":["Items"],"responses":{"204":{"description":"Item et descendants supprimés"},"404":{"description":"Item inconnu","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"parameters":[{"in":"path","name":"workspaceId","schema":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"required":true},{"in":"path","name":"itemId","schema":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"required":true}]}},"/workspaces/{workspaceId}/items/{itemId}/duplicate":{"post":{"operationId":"duplicateItem","summary":"Duplicate item","description":"La copie naît **juste après** l'original dans l'ordre manuel, avec ses sous-tâches, ses rattachements et son corps — un numéro neuf, aucune trace d'import, et l'appelant pour auteur. Sans corps de requête : renommer la copie est un `PATCH`.","tags":["Items"],"responses":{"201":{"description":"Copie créée, corps compris","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ItemDetail"}}}},"400":{"description":"Sous-arbre trop large pour un seul geste","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Item inconnu","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"parameters":[{"in":"path","name":"workspaceId","schema":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"required":true},{"in":"path","name":"itemId","schema":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"required":true}]}},"/workspaces/{workspaceId}/items/{itemId}/move":{"post":{"operationId":"moveItem","summary":"Move item","description":"Réordonner = un `UPDATE` d'une ligne, grâce à l'indexation fractionnaire (RFC §6.5). Reparenter réécrit le chemin `ltree` du sous-arbre (§6.4).","tags":["Items"],"responses":{"200":{"description":"Item déplacé","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ItemDetail"}}}},"400":{"description":"Déplacement impossible (parent introuvable, cycle)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Item inconnu","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"parameters":[{"in":"path","name":"workspaceId","schema":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"required":true},{"in":"path","name":"itemId","schema":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"required":true}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MoveItem"}}}}}},"/workspaces/{workspaceId}/items/{itemId}/subtree":{"get":{"operationId":"getSubtree","summary":"Get item subtree","description":"L'item et tous ses descendants, via `path <@ …` — une plage d'index GiST (RFC §6.4 : 2,39 ms contre 9,98 ms en CTE récursive).","tags":["Items"],"responses":{"200":{"description":"Le sous-arbre, ordonné par chemin","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/Item"}}}}}},"parameters":[{"in":"path","name":"workspaceId","schema":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"required":true},{"in":"path","name":"itemId","schema":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"required":true}]}},"/workspaces/{workspaceId}/items/{itemId}/ancestors":{"get":{"operationId":"getAncestors","summary":"Get item ancestors","description":"Le fil complet au-dessus d'une carte : la racine d'abord, le parent en dernier, la carte elle-même exclue. Les ancêtres **archivés** sont inclus — un fil qui saute un maillon mentirait sur la hiérarchie.","tags":["Items"],"responses":{"200":{"description":"Les ancêtres, de la racine au parent (vide pour une carte racine)","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/Item"}}}}}},"parameters":[{"in":"path","name":"workspaceId","schema":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"required":true},{"in":"path","name":"itemId","schema":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"required":true}]}},"/workspaces/{workspaceId}/items/{itemId}/collections":{"post":{"operationId":"addItemToCollection","summary":"Add item to another list","description":"Le même item dans plusieurs listes — modèle ClickUp, pas Notion (RFC §6.1). La liste d'origine reste `homeCollectionId` et n'est jamais dupliquée ici.","tags":["Items"],"responses":{"204":{"description":"Rattaché"},"404":{"description":"Item inconnu","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"parameters":[{"in":"path","name":"workspaceId","schema":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"required":true},{"in":"path","name":"itemId","schema":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"required":true}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AddToCollection"}}}}}},"/workspaces/{workspaceId}/items/{itemId}/collections/{collectionId}":{"delete":{"operationId":"removeItemFromCollection","summary":"Remove item from a list","description":"Détache une liste **supplémentaire**. La liste d'origine n'est pas détachable : c'est elle qui donne sa clé à la carte, et une carte sans liste n'apparaîtrait plus nulle part.","tags":["Items"],"responses":{"204":{"description":"Détaché"},"404":{"description":"Item inconnu, ou carte non rattachée à cette liste","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"C'est la liste d'origine de la carte","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"parameters":[{"in":"path","name":"workspaceId","schema":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"required":true},{"in":"path","name":"itemId","schema":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"required":true},{"in":"path","name":"collectionId","schema":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"required":true}]}},"/workspaces/{workspaceId}/import":{"post":{"operationId":"importItems","summary":"Import external items","description":"Fait exister ici des objets venus d'ailleurs, **et rend l'opération rejouable** : la clé `(source, externalId)` décide de créer ou de mettre à jour, et une carte identique à sa source n'est pas réécrite (ni écriture, ni événement, ni webhook).\n\nLes événements produits portent tous le même `batchId` et la cause `import` — un import n'est pas de l'activité, et ne doit pas se lire comme telle. La livraison des webhooks se fait **par tranche et par type**, jamais par carte.\n\nAu-delà d'une tranche, rappeler la route avec le `batchId` rendu : un import de 3 000 cartes reste **une** action dans le journal.","tags":["Items"],"responses":{"200":{"description":"Ce qui a été créé, mis à jour, et laissé tel quel","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ImportResult"}}}},"400":{"description":"Charge utile refusée : propriété inconnue, document invalide, doublon d'identifiant","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Liste inconnue","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Tranche trop grosse, ou préfixe de clés déjà pris","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"parameters":[{"in":"path","name":"workspaceId","schema":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"required":true}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Import"}}}}}},"/workspaces/{workspaceId}/api-keys":{"get":{"operationId":"listApiKeys","summary":"List API keys","description":"Les clés du workspace, sans leur secret — il n'existe que dans la réponse de création. `lastUsedAt` est ce qui permet de révoquer sans crainte : une clé jamais servie ne casse rien.","tags":["API keys"],"responses":{"200":{"description":"Les clés du workspace, de la plus ancienne à la plus récente","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/ApiKey"}}}}},"403":{"description":"Rôle insuffisant, ou porteur qui est lui-même une clé API","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"parameters":[{"in":"path","name":"workspaceId","schema":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"required":true}]},"post":{"operationId":"createApiKey","summary":"Create an API key","description":"Émet une clé scopée à CE workspace, avec SON rôle — jamais au-dessus du sien : on ne délègue pas plus que ce qu'on a. Le jeton n'est rendu qu'ici ; l'API n'en stocke que le SHA-256.","tags":["API keys"],"responses":{"201":{"description":"Clé créée — c'est la seule réponse qui contient le secret","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreatedApiKey"}}}},"403":{"description":"Rôle insuffisant, rôle demandé au-dessus du sien, ou porteur qui est une clé API","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"parameters":[{"in":"path","name":"workspaceId","schema":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"required":true}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateApiKey"}}}}}},"/workspaces/{workspaceId}/api-keys/{apiKeyId}":{"delete":{"operationId":"revokeApiKey","summary":"Revoke an API key","description":"Immédiat et définitif : la clé disparaît, le prochain appel qui la porte reçoit 401. Rien à faire de plus — l'API est sans état, il n'y a pas de cache à purger.","tags":["API keys"],"responses":{"204":{"description":"Clé révoquée"},"403":{"description":"Rôle insuffisant, ou porteur qui est lui-même une clé API","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Clé inconnue dans ce workspace","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"parameters":[{"in":"path","name":"workspaceId","schema":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"required":true},{"in":"path","name":"apiKeyId","schema":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"required":true}]}},"/workspaces/{workspaceId}/invitations":{"get":{"operationId":"listInvitations","summary":"List invitations","description":"Les invitations en attente, sans leur jeton — il n'existe que dans la réponse d'émission. Une invitation acceptée n'est plus là : elle est devenue une appartenance.","tags":["Invitations"],"responses":{"200":{"description":"Les invitations du workspace, de la plus ancienne à la plus récente","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/Invitation"}}}}},"403":{"description":"Rôle insuffisant, ou porteur qui est une clé API","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"parameters":[{"in":"path","name":"workspaceId","schema":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"required":true}]},"post":{"operationId":"createInvitation","summary":"Invite someone who has no account yet","description":"Réserve un siège pour une adresse : elle pourra créer son compte et arrivera membre de ce workspace, avec le rôle fixé ici. Le jeton n'est rendu qu'une fois — l'API n'envoie aucun email, c'est à l'émetteur de le transmettre.","tags":["Invitations"],"responses":{"201":{"description":"Invitation émise — c'est la seule réponse qui contient le jeton","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreatedInvitation"}}}},"403":{"description":"Rôle insuffisant, rôle demandé au-dessus du sien, ou porteur qui est une clé API","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Cette adresse a déjà un compte — c'est `POST /members` qui la rattache","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"parameters":[{"in":"path","name":"workspaceId","schema":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"required":true}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateInvitation"}}}}}},"/workspaces/{workspaceId}/invitations/{invitationId}":{"delete":{"operationId":"revokeInvitation","summary":"Revoke an invitation","description":"Le lien cesse aussitôt d'ouvrir un compte. Comme pour une clé API, c'est une suppression et pas un drapeau : une invitation révoquée qui resterait listée n'entretiendrait que le doute.","tags":["Invitations"],"responses":{"204":{"description":"Invitation révoquée"},"403":{"description":"Rôle insuffisant, ou porteur qui est une clé API","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Invitation inconnue dans ce workspace","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"parameters":[{"in":"path","name":"workspaceId","schema":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"required":true},{"in":"path","name":"invitationId","schema":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"required":true}]}},"/workspaces/{workspaceId}/files":{"post":{"operationId":"uploadFile","summary":"Upload a file","description":"`multipart/form-data`, champ `file`. Le type servi n'est **pas** celui déclaré : tout ce qui n'est pas dans la liste blanche devient `application/octet-stream` et se télécharge (un SVG est un document scriptable, pas une image). La taille est mesurée, pas annoncée.\n\nAvec `externalSource` et `externalId`, le téléversement devient **idempotent** : si ce workspace a déjà le fichier de cette origine, l'API rend celui-là (200) au lieu d'en stocker une copie. C'est ce qui permet de rejouer un import sans dupliquer ses images — une URL de fichier Notion est signée et change à chaque lecture, elle ne peut pas servir de clé.","tags":["Files"],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"type":"object","required":["file"],"properties":{"file":{"type":"string","format":"binary"},"externalSource":{"type":"string","description":"L'outil d'origine, ex. `notion`"},"externalId":{"type":"string","description":"L'identifiant du fichier chez la source"}}}}}},"responses":{"200":{"description":"Ce fichier avait déjà été téléversé depuis cette origine — rendu tel quel","content":{"application/json":{"schema":{"$ref":"#/components/schemas/File"}}}},"201":{"description":"Fichier téléversé","content":{"application/json":{"schema":{"$ref":"#/components/schemas/File"}}}},"400":{"description":"Aucun fichier dans le corps","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"Fichier au-delà de `MAX_UPLOAD_BYTES`","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"parameters":[{"in":"path","name":"workspaceId","schema":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"required":true}]},"get":{"operationId":"listFiles","summary":"List files","description":"Sans `ids`, les derniers fichiers téléversés. Avec `ids`, exactement ceux-là — c'est ainsi qu'une page de vue résout les noms de ses propriétés `file` en **un** appel. Un id inconnu est absent de la réponse, pas une erreur.","tags":["Files"],"responses":{"200":{"description":"Les fichiers du workspace","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/File"}}}}}},"parameters":[{"in":"path","name":"workspaceId","schema":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"required":true},{"in":"query","name":"ids","schema":{"type":"string"},"description":"Ids séparés par des virgules — résout les noms d'une page de vue en un appel"},{"in":"query","name":"limit","schema":{"default":50,"type":"integer","minimum":1,"maximum":200}}]}},"/workspaces/{workspaceId}/files/{fileId}":{"get":{"operationId":"getFile","summary":"Get file metadata","tags":["Files"],"responses":{"200":{"description":"Les métadonnées du fichier","content":{"application/json":{"schema":{"$ref":"#/components/schemas/File"}}}},"404":{"description":"Fichier inconnu dans ce workspace","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"parameters":[{"in":"path","name":"workspaceId","schema":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"required":true},{"in":"path","name":"fileId","schema":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"required":true}]},"delete":{"operationId":"deleteFile","summary":"Delete a file","description":"Définitif. Les blocs et propriétés qui le citaient ne sont pas nettoyés : une référence morte se rend en dégradé, comme un type de bloc inconnu — les balayer coûterait un parcours de toutes les cartes du workspace.","tags":["Files"],"responses":{"204":{"description":"Fichier supprimé"},"404":{"description":"Fichier inconnu dans ce workspace","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"parameters":[{"in":"path","name":"workspaceId","schema":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"required":true},{"in":"path","name":"fileId","schema":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"required":true}]}},"/workspaces/{workspaceId}/files/{fileId}/content":{"get":{"operationId":"getFileContent","summary":"Download file content","description":"Les octets. Servis `inline` pour les images et les PDF, en `attachment` pour tout le reste, toujours avec `X-Content-Type-Options: nosniff` et une CSP qui n'autorise rien — front et API partagent une origine en production.","tags":["Files"],"responses":{"200":{"description":"Le contenu du fichier","content":{"application/octet-stream":{"schema":{"type":"string","format":"binary"}}}},"404":{"description":"Fichier inconnu dans ce workspace","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"parameters":[{"in":"path","name":"workspaceId","schema":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"required":true},{"in":"path","name":"fileId","schema":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"required":true}]}},"/workspaces/{workspaceId}/events":{"get":{"operationId":"listEvents","summary":"Read the event log","description":"Les événements du workspace, dans l'ordre, **strictement après** `since` (ou avant, en `order=desc`). Le curseur est l'`id` d'un événement, délibérément lisible : un consommateur qui vient de recevoir un webhook doit pouvoir reprendre à partir de lui (RFC 0003 §5.2).","tags":["Events"],"responses":{"200":{"description":"Une page du journal et le curseur suivant","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EventPage"}}}}},"parameters":[{"in":"path","name":"workspaceId","schema":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"required":true},{"in":"query","name":"since","schema":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"description":"L'`id` du dernier événement traité. Exclusif : la plage commence juste après."},{"in":"query","name":"limit","schema":{"type":"integer","minimum":1,"maximum":200}},{"in":"query","name":"type","schema":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}]},"description":"Un ou plusieurs types exacts. Répéter le paramètre en ajoute."},{"in":"query","name":"batchId","schema":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"description":"Les événements d'une seule action — édition en masse ou import. C'est ce que lit un webhook de lot."},{"in":"query","name":"cause","schema":{"anyOf":[{"type":"string","enum":["edit","import"]},{"type":"array","items":{"type":"string","enum":["edit","import"]}}]},"description":"`edit` = ce que le produit a fait faire, `import` = ce qui a été rejoué d'une source externe. `?cause=edit` est ce que demande un fil d'activité : sans lui, un import de 3 000 cartes enterrerait six mois de travail."},{"in":"query","name":"order","schema":{"type":"string","enum":["asc","desc"]},"description":"`asc` (défaut) = l'ordre du journal, le plus ancien d'abord — celui dont une intégration a besoin. `desc` = le plus récent d'abord, pour un fil d'activité. Le curseur veut dire « continue à partir d'ici » dans le sens demandé."}]}},"/workspaces/{workspaceId}/events/{eventId}":{"get":{"operationId":"getEvent","summary":"Get one event","description":"Utile pour vérifier une livraison reçue : un consommateur idempotent sur `event.id` peut confirmer qu'il tient bien ce que dit le journal.","tags":["Events"],"responses":{"200":{"description":"L'événement","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Event"}}}},"404":{"description":"Événement inconnu — ou purgé avec sa partition","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"parameters":[{"in":"path","name":"workspaceId","schema":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"required":true},{"in":"path","name":"eventId","schema":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"required":true}]}},"/workspaces/{workspaceId}/webhooks":{"get":{"operationId":"listWebhooks","summary":"List webhook endpoints","tags":["Webhooks"],"responses":{"200":{"description":"Les endpoints du workspace. Le secret n'y est jamais.","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/WebhookEndpoint"}}}}}},"parameters":[{"in":"path","name":"workspaceId","schema":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"required":true}]},"post":{"operationId":"createWebhook","summary":"Create a webhook endpoint","description":"Le `secret` de signature n'est rendu **qu'ici**. Il sert à vérifier `X-Spunto-Signature: t=<unix>,v1=<hmac-sha256 de « t.corps »>` — l'horodatage entre dans la signature, sinon une livraison capturée se rejoue indéfiniment.","tags":["Webhooks"],"responses":{"201":{"description":"Endpoint créé, avec son secret","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreatedWebhookEndpoint"}}}},"400":{"description":"URL invalide, ou motif d'abonnement qui ne correspond à aucun type connu","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"parameters":[{"in":"path","name":"workspaceId","schema":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"required":true}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateWebhookEndpoint"}}}}}},"/workspaces/{workspaceId}/webhooks/{endpointId}":{"patch":{"operationId":"updateWebhook","summary":"Update a webhook endpoint","tags":["Webhooks"],"responses":{"200":{"description":"Endpoint mis à jour","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookEndpoint"}}}},"400":{"description":"URL invalide, ou motif inconnu","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Endpoint inconnu","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"parameters":[{"in":"path","name":"workspaceId","schema":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"required":true},{"in":"path","name":"endpointId","schema":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"required":true}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateWebhookEndpoint"}}}}},"delete":{"operationId":"deleteWebhook","summary":"Delete a webhook endpoint","description":"Emporte ses livraisons en attente : plus personne ne les réclame.","tags":["Webhooks"],"responses":{"204":{"description":"Endpoint supprimé"},"404":{"description":"Endpoint inconnu","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"parameters":[{"in":"path","name":"workspaceId","schema":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"required":true},{"in":"path","name":"endpointId","schema":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"required":true}]}},"/workspaces/{workspaceId}/webhooks/{endpointId}/resume":{"post":{"operationId":"resumeWebhook","summary":"Resume a paused endpoint","description":"Remet aussi les livraisons en attente à échéance immédiate : sinon l'endpoint serait actif pendant que sa file attend encore le backoff calculé pendant l'incident.","tags":["Webhooks"],"responses":{"200":{"description":"Endpoint réactivé","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookEndpoint"}}}},"404":{"description":"Endpoint inconnu","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"parameters":[{"in":"path","name":"workspaceId","schema":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"required":true},{"in":"path","name":"endpointId","schema":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"required":true}]}},"/workspaces/{workspaceId}/webhooks/{endpointId}/deliveries":{"get":{"operationId":"listWebhookDeliveries","summary":"List deliveries of an endpoint","description":"Ce qu'un endpoint a reçu, et ce qui a échoué. `payload` est figé depuis l'enfilage — c'est exactement le corps qu'un rejeu renverra.","tags":["Webhooks"],"responses":{"200":{"description":"Les dernières livraisons, de la plus récente à la plus ancienne","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/WebhookDelivery"}}}}}},"parameters":[{"in":"path","name":"workspaceId","schema":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"required":true},{"in":"path","name":"endpointId","schema":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"required":true}]}},"/workspaces/{workspaceId}/webhooks/{endpointId}/deliveries/{deliveryId}/replay":{"post":{"operationId":"replayWebhookDelivery","summary":"Replay a delivery","description":"La **même** charge utile, une nouvelle tentative. Le corps n'est jamais recomposé : un rejeu de six heures plus tard doit raconter la même histoire que l'envoi d'origine.","tags":["Webhooks"],"responses":{"200":{"description":"Livraison remise en file","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookDelivery"}}}},"404":{"description":"Livraison inconnue","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"parameters":[{"in":"path","name":"workspaceId","schema":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"required":true},{"in":"path","name":"endpointId","schema":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"required":true},{"in":"path","name":"deliveryId","schema":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"required":true}]}},"/workspaces/{workspaceId}/app-installations":{"get":{"operationId":"listAppInstallations","summary":"List installed apps","tags":["Apps"],"responses":{"200":{"description":"Les apps installées dans ce workspace","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/AppInstallation"}}}}}},"parameters":[{"in":"path","name":"workspaceId","schema":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"required":true}]},"post":{"operationId":"grantAppInstallation","summary":"Grant an app installation","description":"Le **consentement** — l'étape 2 du flux OAuth, rendue comme une écriture authentifiée plutôt qu'une redirection 302 : l'API ne rend pas de HTML, et cette étape passe ainsi par les mêmes gardes que le reste. Rend l'URL où renvoyer le navigateur ; le code qu'elle porte s'échange à `POST /oauth/token`.","tags":["Apps"],"responses":{"201":{"description":"Consentement accordé — le code est dans `redirectTo`","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AppGrantResult"}}}},"400":{"description":"URI de redirection non déclarée, ou PKCE absent","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Scope non déclaré par l'app, ou rôle au-dessus du vôtre","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Application inconnue","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"parameters":[{"in":"path","name":"workspaceId","schema":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"required":true}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GrantAppInstallation"}}}}}},"/workspaces/{workspaceId}/app-installations/{installationId}":{"delete":{"operationId":"revokeAppInstallation","summary":"Revoke an app installation","description":"Le jeton meurt avec l'installation, immédiatement. Une suppression, pas un drapeau — une ligne survivante n'entretiendrait que le doute sur ce qui est encore actif.","tags":["Apps"],"responses":{"204":{"description":"Installation révoquée"},"404":{"description":"Installation inconnue","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"parameters":[{"in":"path","name":"workspaceId","schema":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"required":true},{"in":"path","name":"installationId","schema":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"required":true}]}},"/apps/scopes":{"get":{"operationId":"listScopes","summary":"List grantable scopes","description":"Ce qu'un workspace peut accorder. Publié pour que l'écran de consentement n'ait pas à recopier la liste — une liste blanche dupliquée est une liste blanche qui divergera.","tags":["Apps"],"responses":{"200":{"description":"Les scopes et leur libellé","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/Scope"}}}}}}}},"/apps":{"get":{"operationId":"listMyApps","summary":"List my apps","tags":["Apps"],"responses":{"200":{"description":"Les apps que j'ai enregistrées","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/OAuthApp"}}}}}}},"post":{"operationId":"registerApp","summary":"Register an app","description":"Déclare un service externe et ses URI de redirection. Le `clientSecret` n'est rendu **qu'ici** — l'API n'en garde que le SHA-256.","tags":["Apps"],"responses":{"201":{"description":"App enregistrée, avec son secret","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreatedOAuthApp"}}}},"400":{"description":"Scope inconnu, ou URI de redirection refusée","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateOAuthApp"}}}}}},"/apps/{clientId}":{"get":{"operationId":"getApp","summary":"Public description of an app","description":"Ce que l'écran de consentement affiche avant qu'on accorde quoi que ce soit : le nom du tiers et ce qu'il demande. Derrière une session — un anonyme n'a aucune raison d'énumérer les apps de l'instance.","tags":["Apps"],"responses":{"200":{"description":"L'app","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OAuthApp"}}}},"404":{"description":"Application inconnue","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"parameters":[{"in":"path","name":"clientId","schema":{"type":"string","minLength":1,"maxLength":200},"required":true}]}},"/apps/{appId}":{"delete":{"operationId":"deleteApp","summary":"Delete an app","description":"Emporte **toutes** ses installations, dans tous les workspaces. C'est la seule opération de ce modèle qui traverse les tenants, et elle appartient au propriétaire de l'app.","tags":["Apps"],"responses":{"204":{"description":"App supprimée"},"404":{"description":"Application inconnue, ou pas la vôtre","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"parameters":[{"in":"path","name":"appId","schema":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"required":true}]}}},"components":{"schemas":{"Health":{"type":"object","properties":{"status":{"type":"string","const":"ok"},"database":{"type":"boolean"},"uptimeSeconds":{"type":"number"}},"required":["status","database","uptimeSeconds"],"description":"État du process et de sa base"},"RegistrationPolicy":{"type":"object","properties":{"mode":{"type":"string","enum":["invite","open"],"description":"Un client prudent teste `!== \"open\"` : un mode qu'il ne connaît pas se lit alors comme fermé."},"google":{"type":"boolean","description":"La connexion Google est-elle activée sur cette instance ? Un formulaire qui l'ignore afficherait un bouton qui rend 403."}},"required":["mode","google"],"description":"À quelles conditions cette instance accepte une inscription, et par quelles portes. Public : un formulaire doit pouvoir le dire avant de faire saisir un mot de passe pour rien."},"Role":{"type":"string","enum":["owner","admin","member","viewer"],"description":"Ordinal : owner > admin > member > viewer. Une route qui exige `member` accepte aussi admin et owner."},"InvitationPreview":{"type":"object","properties":{"workspaceId":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"workspaceName":{"type":"string"},"email":{"type":"string"},"role":{"$ref":"#/components/schemas/Role"},"expiresAt":{"type":"string","description":"Date ISO 8601"}},"required":["workspaceId","workspaceName","email","role","expiresAt"]},"Error":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"],"description":"Erreur applicative"},"AuthUser":{"type":"object","properties":{"id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"email":{"type":"string"},"name":{"type":"string"},"createdAt":{"type":"string","description":"Date ISO 8601"}},"required":["id","email","name","createdAt"]},"Session":{"type":"object","properties":{"user":{"$ref":"#/components/schemas/AuthUser"},"token":{"type":"string"},"expiresAt":{"type":"string","description":"Date ISO 8601"}},"required":["user","token","expiresAt"],"description":"Le jeton n'est rendu qu'à la création. Il n'est jamais stocké en clair."},"Register":{"type":"object","properties":{"email":{"type":"string","maxLength":320,"format":"email","pattern":"^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"},"name":{"type":"string","minLength":1,"maxLength":120},"password":{"type":"string","minLength":10,"maxLength":200,"description":"10 caractères minimum"},"invitation":{"description":"Le jeton `spi_…` reçu d'un membre. Obligatoire tant que `GET /auth/registration` n'annonce pas le mode `open`.","type":"string","minLength":1,"maxLength":200}},"required":["email","name","password"]},"Login":{"type":"object","properties":{"email":{"type":"string","maxLength":320},"password":{"type":"string","maxLength":200}},"required":["email","password"]},"GoogleSignIn":{"type":"object","properties":{"idToken":{"type":"string","minLength":1,"maxLength":4096,"description":"L'`id_token` rendu par Google à l'issue du dance OAuth. Il est **revérifié ici** contre les JWKS de Google (`iss`, `aud`, `exp`, `email_verified`) : ce que le client affirme ne compte pas."},"invitation":{"description":"Le jeton `spi_…`, aux mêmes conditions que pour `POST /auth/register` : obligatoire tant que `GET /auth/registration` n'annonce pas le mode `open`, et il doit nommer l'adresse du compte Google. Google ne contourne pas la politique d'inscription.","type":"string","minLength":1,"maxLength":200}},"required":["idToken"]},"UserIdentity":{"type":"object","properties":{"provider":{"type":"string","description":"`google` aujourd'hui, et rien d'autre"},"email":{"type":"string","description":"L'adresse annoncée par le fournisseur au rattachement — informative"},"createdAt":{"type":"string","description":"Date ISO 8601"}},"required":["provider","email","createdAt"]},"LinkGoogle":{"type":"object","properties":{"idToken":{"type":"string","minLength":1,"maxLength":4096,"description":"L'`id_token` rendu par Google à l'issue du dance OAuth. Il est **revérifié ici** contre les JWKS de Google (`iss`, `aud`, `exp`, `email_verified`) : ce que le client affirme ne compte pas."}},"required":["idToken"]},"OAuthTokenResponse":{"type":"object","properties":{"accessToken":{"type":"string"},"tokenType":{"type":"string","const":"Bearer"},"scope":{"type":"array","items":{"type":"string"}},"workspaceId":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"installationId":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"role":{"$ref":"#/components/schemas/Role"}},"required":["accessToken","tokenType","scope","workspaceId","installationId","role"]},"OAuthTokenRequest":{"type":"object","properties":{"grantType":{"type":"string","const":"authorization_code"},"code":{"type":"string","minLength":1,"maxLength":300},"clientId":{"type":"string","minLength":1,"maxLength":200},"clientSecret":{"type":"string","minLength":1,"maxLength":300},"codeVerifier":{"type":"string","minLength":43,"maxLength":128},"redirectUri":{"type":"string","minLength":1,"maxLength":2048}},"required":["grantType","code","clientId","clientSecret","codeVerifier","redirectUri"]},"CoverLayout":{"type":"string","enum":["banner","square"],"description":"Couverture d'une carte sur sa fiche : `banner` = bandeau paysage au-dessus du titre (défaut), `square` = carré à gauche du titre, champs natifs à côté."},"WorkspaceMembership":{"type":"object","properties":{"id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"slug":{"type":"string"},"name":{"type":"string"},"createdAt":{"type":"string","description":"Date ISO 8601"},"coverLayout":{"$ref":"#/components/schemas/CoverLayout"},"role":{"$ref":"#/components/schemas/Role"}},"required":["id","slug","name","createdAt","role"]},"CreateWorkspace":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":200},"slug":{"type":"string","minLength":1,"maxLength":64,"pattern":"^[a-z0-9][a-z0-9-]*$"}},"required":["name","slug"]},"UpdateWorkspace":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":200},"coverLayout":{"$ref":"#/components/schemas/CoverLayout"}}},"DeleteWorkspace":{"type":"object","properties":{"slug":{"type":"string","minLength":1}},"required":["slug"]},"WorkspaceOverview":{"type":"object","properties":{"items":{"type":"object","properties":{"total":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Cartes non archivées"},"todo":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"doing":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"done":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"archived":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Rangées, donc hors du total : elles ne sortent d'aucune vue non plus"}},"required":["total","todo","doing","done","archived"]},"collections":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"total":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Cartes non archivées"},"todo":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"doing":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"done":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991}},"required":["total","todo","doing","done"]},"description":"Par **liste d'origine**, jamais par rattachement : la somme des listes vaut le total"},"activity":{"type":"array","items":{"type":"object","properties":{"day":{"type":"string","description":"AAAA-MM-JJ"},"count":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991}},"required":["day","count"]},"description":"Quatorze jours **denses**, zéros compris — un graphe à trous ment sur la forme. Ne compte que les événements de cartes."},"catalog":{"type":"object","properties":{"members":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"fields":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"statuses":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"views":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991}},"required":["members","fields","statuses","views"]}},"required":["items","collections","activity","catalog"],"description":"Les compteurs de l'accueil d'un workspace — trois requêtes, une décision de coût"},"Member":{"type":"object","properties":{"userId":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"email":{"type":"string"},"name":{"type":"string"},"role":{"$ref":"#/components/schemas/Role"},"avatarFileId":{"type":["string","null"],"format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"createdAt":{"type":"string","description":"Date ISO 8601"}},"required":["userId","email","name","role","avatarFileId","createdAt"]},"InviteMember":{"type":"object","properties":{"email":{"type":"string","maxLength":320,"format":"email","pattern":"^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$","description":"L'utilisateur doit déjà avoir un compte"},"role":{"$ref":"#/components/schemas/Role"}},"required":["email","role"]},"UpdateMyMembership":{"type":"object","properties":{"avatarFileId":{"type":["string","null"],"format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"Une pièce jointe **image** de ce workspace. `null` retire la photo."}},"required":["avatarFileId"]},"FieldType":{"type":"string","enum":["text","number","select","date","checkbox","multi_select","url","person","file","multi_person"],"description":"`multi_select` et `multi_person` portent un **tableau** : ils se filtrent par containment (« contient »), et ne se trient ni ne se groupent. `url` est une URL absolue `http`/`https`/`mailto`. `person` est l'identifiant d'un membre du workspace, vérifié à l'écriture ; `multi_person` en est la liste, vérifiée de la même façon — c'est le type d'une propriété « Assignés ». `file` est l'identifiant d'un fichier téléversé dans CE workspace."},"StatusColor":{"type":"string","enum":["gray","red","orange","yellow","green","blue","purple","pink"],"description":"Un jeton de la palette fermée du workspace — celle d'un statut comme celle d'une option déclarée d'un `select`. Toujours l'apparence, jamais le sens : sur un statut, c'est `category` qui porte l'avancement et rend une vue portable. Fermée, c'est ce qui permet à chaque client de la rendre sans inventer de contraste."},"FieldConfig":{"type":"object","properties":{"options":{"description":"Valeurs autorisées d'un `select` ou d'un `multi_select`. Vide ou absent = saisie libre.","maxItems":200,"type":"array","items":{"type":"string","minLength":1,"maxLength":200}},"optionColors":{"description":"La couleur de chaque option, indexée par sa valeur. Purement visuel : une option sans entrée se rend en neutre, et une entrée sans option correspondante est ignorée. Une option **nouvelle** écrite sans couleur en reçoit une, tirée de son libellé (le même mot a toujours la même) — une option déjà déclarée n'est jamais recolorée.","type":"object","propertyNames":{"type":"string","minLength":1,"maxLength":200},"additionalProperties":{"$ref":"#/components/schemas/StatusColor"}}},"additionalProperties":{}},"FieldDisplay":{"type":"object","properties":{"placement":{"description":"`sidebar` (défaut) = dans la colonne des métadonnées ; `body` = sous le corps de la carte, là où il y a de la largeur.","type":"string","enum":["sidebar","body"]},"group":{"type":["string","null"],"maxLength":60,"description":"Le nom d'un regroupement dans la fiche. Deux propriétés qui portent le même nom sont dans le même groupe — il n'y a pas d'entité « groupe » à créer. Absent, vide ou `null` = pas de groupe."}},"description":"Comment la propriété se regarde dans la fiche d'une carte. Ne change jamais ce qu'elle accepte."},"Field":{"type":"object","properties":{"id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"key":{"type":"string"},"name":{"type":"string"},"type":{"$ref":"#/components/schemas/FieldType"},"config":{"$ref":"#/components/schemas/FieldConfig"},"display":{"$ref":"#/components/schemas/FieldDisplay"}},"required":["id","key","name","type","config","display"],"description":"Une propriété du registre du WORKSPACE (RFC §6.2) — pas de la collection : un item qui change de liste garde ses valeurs."},"CreateField":{"type":"object","properties":{"key":{"type":"string","pattern":"^[a-z][a-z0-9_]{0,62}$"},"name":{"type":"string","minLength":1,"maxLength":200},"type":{"$ref":"#/components/schemas/FieldType"},"config":{"$ref":"#/components/schemas/FieldConfig"},"display":{"$ref":"#/components/schemas/FieldDisplay"}},"required":["key","name","type"]},"UpdateField":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":200},"config":{"$ref":"#/components/schemas/FieldConfig"},"display":{"$ref":"#/components/schemas/FieldDisplay"}}},"UpsertFieldOption":{"type":"object","properties":{"value":{"type":"string","minLength":1,"maxLength":200,"description":"L'option, telle qu'elle s'écrira dans `item.properties`. Ajoutée en fin de liste si elle n'est pas déclarée."},"color":{"description":"Absent = ne change pas la couleur d'une option existante, et donne à une option nouvelle la couleur de son mot (toujours la même pour le même libellé) ; `null` = neutre ; un jeton = cette couleur.","anyOf":[{"$ref":"#/components/schemas/StatusColor"},{"type":["null"]}]}},"required":["value"]},"Status":{"type":"object","properties":{"id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"label":{"type":"string"},"category":{"type":"string","enum":["todo","doing","done"]},"position":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"color":{"$ref":"#/components/schemas/StatusColor"}},"required":["id","label","category","position","color"],"description":"Un statut du workspace (RFC §6.3). La catégorie est ce qui rend une vue portable."},"CreateStatus":{"type":"object","properties":{"label":{"type":"string","minLength":1,"maxLength":120},"category":{"type":"string","enum":["todo","doing","done"]},"position":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"color":{"$ref":"#/components/schemas/StatusColor"}},"required":["label","category"]},"UpdateStatus":{"type":"object","properties":{"label":{"type":"string","minLength":1,"maxLength":120},"category":{"type":"string","enum":["todo","doing","done"]},"position":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"color":{"$ref":"#/components/schemas/StatusColor"}}},"Collection":{"type":"object","properties":{"id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"name":{"type":"string"},"keyPrefix":{"type":"string","pattern":"^[A-Z][A-Z0-9]{0,9}$","description":"Préfixe des clés de cette liste, ex. `SPR` pour `SPR-12`"},"groupId":{"type":["string","null"],"format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"Le groupe qui la range, ou `null` — à la racine de la barre latérale."},"position":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Son rang dans la barre latérale, à l'intérieur de son groupe."},"createdAt":{"type":"string","description":"Date ISO 8601"}},"required":["id","name","keyPrefix","groupId","position","createdAt"],"description":"Une liste. Elle ne définit aucune propriété (RFC §6.2)."},"CreateCollection":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":200},"keyPrefix":{"type":"string","pattern":"^[A-Z][A-Z0-9]{0,9}$","description":"Préfixe des clés de cette liste, ex. `SPR` pour `SPR-12`"},"fieldIds":{"type":"array","items":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"}}},"required":["name"]},"ReorderCollections":{"type":"object","properties":{"groupId":{"type":["string","null"],"format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"Le groupe qui reçoit ces listes, ou `null` (ou absent) pour la racine."},"collectionIds":{"maxItems":500,"type":"array","items":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"description":"Les listes de ce groupe, dans l'ordre voulu. Une liste citée y est déplacée."}},"required":["collectionIds"]},"SystemField":{"type":"object","properties":{"fieldId":{"type":"string","description":"`sys:title`, `sys:created_at`…"},"name":{"type":"string"},"type":{"$ref":"#/components/schemas/FieldType"}},"required":["fieldId","name","type"]},"FieldRef":{"type":"object","properties":{"fieldId":{"type":"string","description":"Un id du registre, ou un champ promu réservé : `sys:title`, `sys:status`, `sys:order`…"}},"required":["fieldId"]},"Sort":{"type":"object","properties":{"field":{"$ref":"#/components/schemas/FieldRef"},"direction":{"type":"string","enum":["asc","desc"]}},"required":["field","direction"]},"ViewDefinition":{"type":"object","properties":{"filter":{"$ref":"#/components/schemas/Filter"},"sort":{"default":[],"type":"array","items":{"$ref":"#/components/schemas/Sort"}},"groupBy":{"$ref":"#/components/schemas/FieldRef"},"includeSecondaryLists":{"default":false,"type":"boolean"}},"description":"Une vue est de la donnée : filtres, tris, groupement. Aucun fragment SQL ne peut y entrer (RFC §7.1)."},"TableColumns":{"type":"object","properties":{"order":{"description":"Les colonnes dans l'ordre voulu, par `fieldId`. Ce qui n'y est pas nommé suit derrière.","maxItems":100,"type":"array","items":{"type":"string"}},"widths":{"description":"Largeur en pixels, par `fieldId`. Absente = la largeur par défaut du type.","type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"integer","minimum":60,"maximum":1200}},"hidden":{"description":"Les champs masqués dans cette vue, par `fieldId` (`sys:key` et `sys:status` compris ; le titre ne se masque pas). La propriété reste éditable dans la carte.","maxItems":100,"type":"array","items":{"type":"string"}}},"description":"Les champs que la vue montre : ordre, largeurs et champs masqués. Colonnes d'un tableau, puces d'une carte de kanban ou d'une ligne empilée — le même réglage quel que soit le `kind`, les largeurs n'ayant de sens que pour un tableau. Un réglage d'affichage — rien n'atteint le compilateur de vues."},"ViewDisplay":{"type":"object","properties":{"groupOrder":{"description":"L'ordre des groupes, par clé (`null` = « sans valeur »). Ce qui n'y est pas nommé — un statut créé depuis — suit dans l'ordre du workspace, jamais perdu.","maxItems":50,"type":"array","items":{"type":["string","null"]}},"columns":{"$ref":"#/components/schemas/TableColumns"}},"description":"Comment la vue se regarde. Ne change jamais ce qu'elle sélectionne."},"CollectionView":{"type":"object","properties":{"id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"collectionId":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"name":{"type":"string"},"kind":{"type":"string","enum":["table","board","list","calendar"]},"definition":{"$ref":"#/components/schemas/ViewDefinition"},"display":{"$ref":"#/components/schemas/ViewDisplay"},"position":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991}},"required":["id","collectionId","name","kind","definition","display","position"]},"Predicate":{"anyOf":[{"type":"object","properties":{"op":{"type":"string","enum":["eq","neq","lt","lte","gt","gte"]},"field":{"$ref":"#/components/schemas/FieldRef"},"value":{"anyOf":[{"type":"string"},{"type":"number"},{"type":"boolean"},{"type":["null"]}]}},"required":["op","field","value"]},{"type":"object","properties":{"op":{"type":"string","enum":["contains","starts_with"]},"field":{"$ref":"#/components/schemas/FieldRef"},"value":{"type":"string"}},"required":["op","field","value"]},{"type":"object","properties":{"op":{"type":"string","enum":["in","not_in"]},"field":{"$ref":"#/components/schemas/FieldRef"},"values":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"},{"type":"boolean"},{"type":["null"]}]}}},"required":["op","field","values"]},{"type":"object","properties":{"op":{"type":"string","enum":["is_empty","is_not_empty"]},"field":{"$ref":"#/components/schemas/FieldRef"}},"required":["op","field"]}]},"Filter":{"description":"Arbre de prédicats. Sérialisable, donc exécutable ailleurs.","anyOf":[{"$ref":"#/components/schemas/Predicate"},{"type":"object","properties":{"and":{"type":"array","items":{"$ref":"#/components/schemas/Filter"}}},"required":["and"]},{"type":"object","properties":{"or":{"type":"array","items":{"$ref":"#/components/schemas/Filter"}}},"required":["or"]}]},"CollectionDetail":{"type":"object","properties":{"collection":{"$ref":"#/components/schemas/Collection"},"fields":{"type":"array","items":{"$ref":"#/components/schemas/Field"}},"displayedFieldIds":{"type":"array","items":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"}},"systemFields":{"type":"array","items":{"$ref":"#/components/schemas/SystemField"}},"statuses":{"type":"array","items":{"$ref":"#/components/schemas/Status"}},"views":{"type":"array","items":{"$ref":"#/components/schemas/CollectionView"}},"defaultView":{"$ref":"#/components/schemas/ViewDefinition"},"members":{"type":"array","items":{"$ref":"#/components/schemas/Member"}},"role":{"$ref":"#/components/schemas/Role"}},"required":["collection","fields","displayedFieldIds","systemFields","statuses","views","members","role"],"description":"Tout ce qu'il faut pour afficher une collection — en un appel. Le rôle est indicatif : l'autorisation reste vérifiée à chaque écriture, côté API."},"UpdateCollection":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":200},"keyPrefix":{"type":"string","pattern":"^[A-Z][A-Z0-9]{0,9}$","description":"Préfixe des clés de cette liste, ex. `SPR` pour `SPR-12`"},"fieldIds":{"type":"array","items":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"}},"groupId":{"type":["string","null"],"format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"position":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991}}},"CreateCollectionView":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":200},"kind":{"default":"table","type":"string","enum":["table","board","list","calendar"]},"definition":{"$ref":"#/components/schemas/ViewDefinition"},"display":{"$ref":"#/components/schemas/ViewDisplay"}},"required":["name","definition"]},"UpdateCollectionView":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":200},"kind":{"type":"string","enum":["table","board","list","calendar"]},"definition":{"$ref":"#/components/schemas/ViewDefinition"},"display":{"$ref":"#/components/schemas/ViewDisplay"},"position":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991}}},"Span":{"type":"object","properties":{"text":{"type":"string"},"bold":{"type":"boolean","const":true},"italic":{"type":"boolean","const":true},"code":{"type":"boolean","const":true},"strike":{"type":"boolean","const":true},"link":{"description":"URL absolue, `http`/`https`/`mailto`","type":"string","maxLength":2048}},"required":["text"],"description":"Un fragment de texte et ses marques (RFC 0002 §4.3)"},"Block":{"type":"object","properties":{"id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"UUIDv7 stable — conservé d'une écriture à l'autre"},"type":{"type":"string","enum":["paragraph","heading","bulleted","numbered","todo","quote","callout","code","divider","image"]},"indent":{"type":"integer","minimum":0,"maximum":5},"text":{"description":"Blocs textuels uniquement","type":"array","items":{"$ref":"#/components/schemas/Span"}},"level":{"description":"`heading`","type":"integer","minimum":1,"maximum":3},"checked":{"description":"`todo`","type":"boolean"},"code":{"description":"`code` — source brut, sans marques","type":"string"},"language":{"description":"`code` — dans la liste fermée de l'API","type":"string"},"fileId":{"description":"`image` — un fichier de CE workspace","type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"alt":{"description":"`image` — le texte alternatif, qui sert aussi de légende et entre dans `body`","type":"string"}},"required":["id","type","indent"],"description":"Un bloc du corps d'une carte (RFC 0002 §4)"},"BlockDocument":{"type":"object","properties":{"version":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Version du format. Écrite par l'API, jamais par le client."},"blocks":{"type":"array","items":{"$ref":"#/components/schemas/Block"}}},"required":["version","blocks"],"description":"Le corps d'une carte, en blocs. Toujours rendu — dérivé du texte pour une carte jamais éditée en blocs (RFC 0002 §8)."},"ItemCollection":{"type":"object","properties":{"id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"name":{"type":"string"},"keyPrefix":{"type":"string"},"home":{"type":"boolean","description":"`true` pour la liste d'origine, une seule par carte"},"groupPath":{"description":"Les groupes qui rangent cette liste, du plus extérieur au plus intérieur.","type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"name":{"type":"string"}},"required":["id","name"]}}},"required":["id","name","keyPrefix","home"]},"ItemDetail":{"type":"object","properties":{"id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"title":{"type":"string"},"icon":{"type":["string","null"],"description":"Emoji, ou `null`. Aucune vue ne trie ni ne filtre dessus."},"number":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"key":{"type":"string","description":"Clé lisible, ex. `SPR-12`"},"homeCollectionId":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"statusId":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"parentId":{"type":["string","null"],"format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"path":{"type":"string","description":"Chemin matérialisé ltree, self inclus (RFC §6.4)"},"orderKey":{"type":"string","description":"Clé d'indexation fractionnaire (RFC §6.5)"},"properties":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},"createdAt":{"type":"string","description":"Date ISO 8601"},"updatedAt":{"type":"string","description":"Date ISO 8601"},"createdBy":{"type":["string","null"],"format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"`userId` de l'auteur, ou `null`"},"coverFileId":{"type":["string","null"],"format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"Identifiant d'un fichier image du workspace, ou `null`."},"body":{"type":["string","null"],"maxLength":100000,"description":"La **projection texte** du corps, sans marqueurs. Dérivée de `blocks` dès qu'on écrit des blocs ; écrire ce champ remet le document sur sa dérivation depuis le texte (RFC 0002 §8). `null` = jamais écrit."},"blocks":{"$ref":"#/components/schemas/BlockDocument"},"collections":{"type":"array","items":{"$ref":"#/components/schemas/ItemCollection"}}},"required":["id","title","number","key","homeCollectionId","statusId","parentId","path","orderKey","properties","createdAt","updatedAt","body","blocks","collections"],"description":"Un item, son corps et les listes où il apparaît. Les pages de vue rendent `Item` : ni l'un ni les autres ne voyagent par paquets de 50."},"BlockInput":{"type":"object","properties":{"id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"type":{"type":"string","enum":["paragraph","heading","bulleted","numbered","todo","quote","callout","code","divider","image"]},"indent":{"type":"integer","minimum":0,"maximum":5},"text":{"description":"Blocs textuels uniquement","type":"array","items":{"$ref":"#/components/schemas/Span"}},"level":{"description":"`heading`","type":"integer","minimum":1,"maximum":3},"checked":{"description":"`todo`","type":"boolean"},"code":{"description":"`code` — source brut, sans marques","type":"string"},"language":{"description":"`code` — dans la liste fermée de l'API","type":"string"},"fileId":{"description":"`image` — un fichier de CE workspace","type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"alt":{"description":"`image` — le texte alternatif, qui sert aussi de légende et entre dans `body`","type":"string"}},"required":["type"]},"BlockDocumentInput":{"type":"object","properties":{"blocks":{"type":"array","items":{"$ref":"#/components/schemas/BlockInput"}}},"required":["blocks"],"description":"Le document à écrire. Remplacé en bloc — un document ne se fusionne pas."},"CreateItem":{"type":"object","properties":{"title":{"type":"string","minLength":1,"maxLength":1000},"icon":{"type":["string","null"],"maxLength":16,"description":"Emoji (≤ 16 caractères, sans espace). `null` retire l'icône — et `\"\"` aussi, pour un client qui ne sait pas écrire `null`."},"coverFileId":{"type":["string","null"],"description":"Identifiant d'un fichier **image** du workspace (`POST /workspaces/{id}/files`). `null` retire la couverture — et `\"\"` aussi, pour un client qui ne sait pas écrire `null`."},"body":{"type":["string","null"],"maxLength":100000,"description":"La **projection texte** du corps, sans marqueurs. Dérivée de `blocks` dès qu'on écrit des blocs ; écrire ce champ remet le document sur sa dérivation depuis le texte (RFC 0002 §8). `null` = jamais écrit."},"blocks":{"$ref":"#/components/schemas/BlockDocumentInput"},"statusId":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"parentId":{"type":["string","null"],"format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"properties":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},"afterItemId":{"type":["string","null"],"format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"}},"required":["title"]},"Item":{"type":"object","properties":{"id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"title":{"type":"string"},"icon":{"type":["string","null"],"description":"Emoji, ou `null`. Aucune vue ne trie ni ne filtre dessus."},"number":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"key":{"type":"string","description":"Clé lisible, ex. `SPR-12`"},"homeCollectionId":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"statusId":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"parentId":{"type":["string","null"],"format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"path":{"type":"string","description":"Chemin matérialisé ltree, self inclus (RFC §6.4)"},"orderKey":{"type":"string","description":"Clé d'indexation fractionnaire (RFC §6.5)"},"properties":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},"createdAt":{"type":"string","description":"Date ISO 8601"},"updatedAt":{"type":"string","description":"Date ISO 8601"},"createdBy":{"type":["string","null"],"format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"`userId` de l'auteur, ou `null`"},"coverFileId":{"type":["string","null"],"format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"Identifiant d'un fichier image du workspace, ou `null`."}},"required":["id","title","number","key","homeCollectionId","statusId","parentId","path","orderKey","properties","createdAt","updatedAt"],"description":"Un item — ligne de premier ordre (RFC §6.1)"},"ItemPage":{"type":"object","properties":{"rows":{"type":"array","items":{"$ref":"#/components/schemas/Item"}},"nextCursor":{"type":["string","null"],"description":"Curseur opaque, ou `null` en fin de liste. L'API ne publie aucun offset (RFC §6.6)."}},"required":["rows","nextCursor"]},"QueryView":{"type":"object","properties":{"definition":{"$ref":"#/components/schemas/ViewDefinition"},"cursor":{"type":["string","null"]},"limit":{"default":50,"type":"integer","minimum":1,"maximum":200}},"required":["definition"]},"GroupCount":{"type":"object","properties":{"key":{"type":["string","null"]},"count":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991}},"required":["key","count"]},"ViewRequest":{"type":"object","properties":{"definition":{"$ref":"#/components/schemas/ViewDefinition"}},"required":["definition"]},"ItemGroup":{"type":"object","properties":{"key":{"type":["string","null"],"description":"La valeur du groupement, en texte. `null` = sans valeur."},"count":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"L'effectif **entier** du groupe, pas le nombre de lignes rendues."},"rows":{"type":"array","items":{"$ref":"#/components/schemas/Item"}},"nextCursor":{"type":["string","null"]}},"required":["key","count","rows","nextCursor"]},"GroupedPage":{"type":"object","properties":{"groups":{"type":"array","items":{"$ref":"#/components/schemas/ItemGroup"}}},"required":["groups"],"description":"Un groupe par colonne (kanban), par section (vue empilée) ou par case (calendrier) — chacun avec son propre curseur keyset."},"GroupedQuery":{"type":"object","properties":{"definition":{"$ref":"#/components/schemas/ViewDefinition"},"groups":{"description":"Les groupes à rendre, dans cet ordre, **même vides**. Absent : ceux que la donnée contient, par clé croissante, « sans valeur » en dernier.","maxItems":50,"type":"array","items":{"type":["string","null"]}},"cursors":{"description":"Le curseur de chaque groupe déjà chargé — « charger la suite » d'une colonne.","maxItems":50,"type":"array","items":{"type":"object","properties":{"key":{"type":["string","null"],"description":"La clé du groupe. `null` ou absente = « sans valeur »."},"cursor":{"type":"string"}},"required":["cursor"]}},"limit":{"default":20,"description":"Par groupe, pas au total.","type":"integer","minimum":1,"maximum":200}},"required":["definition"]},"BulkPreview":{"type":"object","properties":{"count":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Ce que le filtre sélectionne **maintenant**"},"confirmAbove":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Au-delà, `expectedCount` devient obligatoire"},"max":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Au-delà, l'API refuse : ça devient un job, qui n'existe pas encore"}},"required":["count","confirmAbove","max"]},"BulkResult":{"type":"object","properties":{"batchId":{"type":["string","null"],"description":"Le lot dans le journal : `GET /events?batchId=…`. `null` si rien n'a été touché."},"count":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Cartes réellement modifiées"}},"required":["batchId","count"]},"BulkPatch":{"type":"object","properties":{"statusId":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"properties":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},"archived":{"type":"boolean"}}},"BulkUpdate":{"type":"object","properties":{"definition":{"$ref":"#/components/schemas/ViewDefinition"},"patch":{"$ref":"#/components/schemas/BulkPatch"},"expectedCount":{"description":"Le compte que l'utilisateur a vu. Exigé au-delà de 200 cartes ; refusé en 409 s'il ne correspond plus.","type":"integer","minimum":0,"maximum":9007199254740991}},"required":["definition","patch"],"description":"« Applique ce patch à ce que sélectionne ce filtre » — un `UPDATE … WHERE`, une transaction, un événement par carte, un webhook par lot."},"IndexHint":{"type":"object","properties":{"hash":{"type":"string"},"definition":{"type":"string"},"reason":{"type":"string"}},"required":["hash","definition","reason"],"description":"L'index que cette vue voudrait (RFC §5.4). Décrit, pas créé : le job qui les pose n'existe pas encore."},"CollectionGroup":{"type":"object","properties":{"id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"name":{"type":"string"},"parentId":{"type":["string","null"],"format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"Le groupe qui le range, ou `null` — à la racine. Deux niveaux au plus."},"position":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Son rang **parmi ses frères**, pas dans le workspace."}},"required":["id","name","parentId","position"],"description":"Un groupe de listes — deux niveaux au plus, pour ranger la barre latérale. Le supprimer ne supprime rien : ses listes et ses sous-groupes remontent d'un cran."},"CreateCollectionGroup":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":200},"parentId":{"type":["string","null"],"format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"Le groupe qui le range, ou `null` — à la racine. Deux niveaux au plus."},"position":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991}},"required":["name"]},"ReorderGroups":{"type":"object","properties":{"parentId":{"type":["string","null"],"format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","default":null,"description":"Le parent dont on ordonne les enfants. `null` = la racine."},"groupIds":{"maxItems":200,"type":"array","items":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"description":"Les groupes de ce parent, dans l'ordre voulu."}},"required":["groupIds"]},"UpdateCollectionGroup":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":200},"parentId":{"type":["string","null"],"format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"Le groupe qui le range, ou `null` — à la racine. Deux niveaux au plus."},"position":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991}}},"SearchResults":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/Item"},"description":"Cartes dont le titre correspond — clé exacte en tête."},"collections":{"type":"array","items":{"$ref":"#/components/schemas/Collection"},"description":"Listes dont le nom ou le préfixe correspond."}},"required":["items","collections"],"description":"Résultats d'une recherche de workspace. Porte sur les **titres** et les noms de listes, jamais sur le corps des cartes."},"UpdateItem":{"type":"object","properties":{"title":{"type":"string","minLength":1,"maxLength":1000},"icon":{"type":["string","null"],"maxLength":16,"description":"Emoji (≤ 16 caractères, sans espace). `null` retire l'icône — et `\"\"` aussi, pour un client qui ne sait pas écrire `null`."},"coverFileId":{"type":["string","null"],"description":"Identifiant d'un fichier **image** du workspace (`POST /workspaces/{id}/files`). `null` retire la couverture — et `\"\"` aussi, pour un client qui ne sait pas écrire `null`."},"body":{"type":["string","null"],"maxLength":100000,"description":"La **projection texte** du corps, sans marqueurs. Dérivée de `blocks` dès qu'on écrit des blocs ; écrire ce champ remet le document sur sa dérivation depuis le texte (RFC 0002 §8). `null` = jamais écrit."},"blocks":{"$ref":"#/components/schemas/BlockDocumentInput"},"statusId":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"properties":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},"archived":{"type":"boolean"}}},"MoveItem":{"type":"object","properties":{"afterItemId":{"type":["string","null"],"format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"parentId":{"type":["string","null"],"format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"}}},"AddToCollection":{"type":"object","properties":{"collectionId":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"}},"required":["collectionId"],"description":"Rattache l'item à une liste SUPPLÉMENTAIRE (RFC §6.1, modèle ClickUp)"},"ImportedItem":{"type":"object","properties":{"externalId":{"type":"string"},"id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"key":{"type":"string","description":"La clé lisible, ex. `MON-12`"},"action":{"type":"string","enum":["created","updated","unchanged"],"description":"`unchanged` : la carte existait déjà à l'identique — aucune écriture, aucun événement"}},"required":["externalId","id","key","action"]},"ImportResult":{"type":"object","properties":{"batchId":{"type":"string","description":"À repasser aux tranches suivantes, et à lire en plage : `GET /events?batchId=…`"},"collection":{"$ref":"#/components/schemas/Collection"},"created":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"updated":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"unchanged":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"items":{"type":"array","items":{"$ref":"#/components/schemas/ImportedItem"}}},"required":["batchId","collection","created","updated","unchanged","items"]},"ImportCollection":{"type":"object","properties":{"id":{"description":"Une liste existante, choisie par l'appelant","type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"externalId":{"description":"L'identifiant de la base d'origine","type":"string","minLength":1,"maxLength":200},"name":{"description":"Le nom à donner si elle est créée","type":"string","minLength":1,"maxLength":200},"keyPrefix":{"type":"string","pattern":"^[A-Z][A-Z0-9]{0,9}$","description":"Préfixe des clés de cette liste, ex. `SPR` pour `SPR-12`"}}},"ImportItem":{"type":"object","properties":{"externalId":{"type":"string","minLength":1,"maxLength":200,"description":"L'identifiant de la page chez la source"},"title":{"type":"string","minLength":1,"maxLength":1000},"body":{"type":["string","null"],"maxLength":100000,"description":"La **projection texte** du corps, sans marqueurs. Dérivée de `blocks` dès qu'on écrit des blocs ; écrire ce champ remet le document sur sa dérivation depuis le texte (RFC 0002 §8). `null` = jamais écrit."},"blocks":{"$ref":"#/components/schemas/BlockDocumentInput"},"statusId":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"properties":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}},"required":["externalId","title"]},"Import":{"type":"object","properties":{"source":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,39}$","description":"L'outil d'origine, ex. `notion`. Entre dans la clé d'idempotence."},"collection":{"$ref":"#/components/schemas/ImportCollection"},"items":{"minItems":1,"maxItems":500,"type":"array","items":{"$ref":"#/components/schemas/ImportItem"}},"batchId":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"}},"required":["source","collection","items"],"description":"« Fais exister ces objets externes ici » — une transaction, un événement par carte, un webhook par tranche et par type. Rejouable : ce qui n'a pas changé n'est pas réécrit."},"ApiKey":{"type":"object","properties":{"id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"name":{"type":"string"},"prefix":{"type":"string","description":"La partie publique du jeton, ex. `spk_1a2b3c4d`"},"role":{"$ref":"#/components/schemas/Role"},"workspaceId":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"createdByUserId":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"createdAt":{"type":"string","description":"Date ISO 8601"},"expiresAt":{"type":["string","null"],"description":"Date ISO 8601, ou `null` — sans expiration"},"lastUsedAt":{"type":["string","null"],"description":"Date ISO 8601, ou `null` — jamais servie"},"expired":{"type":"boolean"}},"required":["id","name","prefix","role","workspaceId","createdByUserId","createdAt","expiresAt","lastUsedAt","expired"],"description":"Une clé API du workspace. Le secret n'existe que dans la réponse de création — l'API n'en garde que le SHA-256."},"CreatedApiKey":{"type":"object","properties":{"id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"name":{"type":"string"},"prefix":{"type":"string","description":"La partie publique du jeton, ex. `spk_1a2b3c4d`"},"role":{"$ref":"#/components/schemas/Role"},"workspaceId":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"createdByUserId":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"createdAt":{"type":"string","description":"Date ISO 8601"},"expiresAt":{"type":["string","null"],"description":"Date ISO 8601, ou `null` — sans expiration"},"lastUsedAt":{"type":["string","null"],"description":"Date ISO 8601, ou `null` — jamais servie"},"expired":{"type":"boolean"},"token":{"type":"string","description":"Le jeton complet, `spk_<prefix>_<secret>`"}},"required":["id","name","prefix","role","workspaceId","createdByUserId","createdAt","expiresAt","lastUsedAt","expired","token"],"description":"La clé et son jeton. C'est la seule réponse de l'API qui contienne le secret."},"CreateApiKey":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":120,"description":"À quoi elle sert, ex. « sync site vitrine »"},"role":{"$ref":"#/components/schemas/Role"},"expiresInDays":{"type":"integer","minimum":1,"maximum":3650}},"required":["name","role"]},"Invitation":{"type":"object","properties":{"id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"email":{"type":"string"},"role":{"$ref":"#/components/schemas/Role"},"workspaceId":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"invitedByUserId":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"createdAt":{"type":"string","description":"Date ISO 8601"},"expiresAt":{"type":"string","description":"Date ISO 8601"},"expired":{"type":"boolean","description":"Calculé côté API — le client n'a pas à comparer des dates"}},"required":["id","email","role","workspaceId","invitedByUserId","createdAt","expiresAt","expired"]},"CreatedInvitation":{"type":"object","properties":{"id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"email":{"type":"string"},"role":{"$ref":"#/components/schemas/Role"},"workspaceId":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"invitedByUserId":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"createdAt":{"type":"string","description":"Date ISO 8601"},"expiresAt":{"type":"string","description":"Date ISO 8601"},"expired":{"type":"boolean","description":"Calculé côté API — le client n'a pas à comparer des dates"},"token":{"type":"string","description":"Rendu une seule fois. L'API n'en garde que le SHA-256."}},"required":["id","email","role","workspaceId","invitedByUserId","createdAt","expiresAt","expired","token"],"description":"Le jeton n'est rendu qu'ici. Transmets-le à l'invité par le canal de ton choix : l'API n'envoie pas d'email."},"CreateInvitation":{"type":"object","properties":{"email":{"type":"string","maxLength":320,"format":"email","pattern":"^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$","description":"L'adresse qui pourra créer le compte, et elle seule"},"role":{"$ref":"#/components/schemas/Role"},"expiresInDays":{"description":"7 jours par défaut","type":"integer","minimum":1,"maximum":90}},"required":["email","role"]},"File":{"type":"object","properties":{"id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"name":{"type":"string","description":"Le nom d'origine, assaini. Aucun rôle de stockage : la clé est l'id."},"contentType":{"type":"string","description":"Le type **que l'API sert**, pas celui qui a été déclaré : hors liste blanche, c'est `application/octet-stream`."},"size":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"En octets, mesurés à l'écriture"},"image":{"type":"boolean","description":"`true` si l'API le sert inline comme image — un SVG répond `false`"},"uploadedByUserId":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"createdAt":{"type":"string","description":"Date ISO 8601"}},"required":["id","name","contentType","size","image","uploadedByUserId","createdAt"],"description":"Un fichier du workspace. Ses octets ne s'obtiennent que par la route `/content`, derrière la même appartenance vérifiée que le reste — il n'existe aucune URL devinable."},"EventActor":{"type":"object","properties":{"kind":{"type":"string","enum":["user","api_key","app","system"]},"label":{"type":"string"},"userId":{"type":["string","null"]},"apiKeyId":{"type":["string","null"]},"installationId":{"type":["string","null"]}},"required":["kind","label","userId","apiKeyId","installationId"]},"Change":{"type":"object","properties":{"before":{},"after":{}},"required":["before","after"]},"Event":{"type":"object","properties":{"id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"workspaceId":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"occurredAt":{"type":"string","description":"Date ISO 8601"},"type":{"type":"string","description":"`item.created`, `collection.deleted`… — voir `GET /event-types`"},"resource":{"type":"object","properties":{"kind":{"type":"string"},"id":{"type":"string"}},"required":["kind","id"]},"actor":{"$ref":"#/components/schemas/EventActor"},"changes":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"$ref":"#/components/schemas/Change"},"description":"Les clés réellement modifiées, avec avant/après. Une propriété du registre apparaît en `properties.<clé>`."},"batchId":{"type":["string","null"],"description":"Non nul : cet événement est l'un de N produits par une seule action — une édition en masse, un import. Se lit en plage : `GET /events?batchId=…`."},"cause":{"type":"string","enum":["edit","import"],"description":"`edit` (défaut) ou `import`"}},"required":["id","workspaceId","occurredAt","type","resource","actor","changes","batchId","cause"],"description":"Un changement, écrit dans la transaction qui l'a produit — il existe si et seulement si le changement a commité."},"EventPage":{"type":"object","properties":{"rows":{"type":"array","items":{"$ref":"#/components/schemas/Event"}},"nextCursor":{"type":["string","null"],"description":"À repasser en `since`. `null` = fin de plage (pas fin du journal)."}},"required":["rows","nextCursor"]},"WebhookEndpoint":{"type":"object","properties":{"id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"name":{"type":"string"},"url":{"type":"string"},"eventTypes":{"type":"array","items":{"type":"string"},"description":"Motifs : `*`, `item.*`, ou un type exact. Filtré à l'enfilage, pas à la livraison."},"paused":{"type":"boolean"},"pausedReason":{"type":["string","null"]},"failureCount":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Échecs **consécutifs**. Remis à zéro par un succès."},"createdByUserId":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"createdAt":{"type":"string","description":"Date ISO 8601"},"lastDeliveryAt":{"type":["string","null"],"description":"Date ISO 8601"}},"required":["id","name","url","eventTypes","paused","pausedReason","failureCount","createdByUserId","createdAt","lastDeliveryAt"],"description":"Un destinataire de livraisons. Son secret n'existe que dans la réponse de création."},"CreatedWebhookEndpoint":{"type":"object","properties":{"id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"name":{"type":"string"},"url":{"type":"string"},"eventTypes":{"type":"array","items":{"type":"string"},"description":"Motifs : `*`, `item.*`, ou un type exact. Filtré à l'enfilage, pas à la livraison."},"paused":{"type":"boolean"},"pausedReason":{"type":["string","null"]},"failureCount":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Échecs **consécutifs**. Remis à zéro par un succès."},"createdByUserId":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"createdAt":{"type":"string","description":"Date ISO 8601"},"lastDeliveryAt":{"type":["string","null"],"description":"Date ISO 8601"},"secret":{"type":"string","description":"Rendu une seule fois. Sert à vérifier `X-Spunto-Signature` : `t=<unix>,v1=<hmac>`."}},"required":["id","name","url","eventTypes","paused","pausedReason","failureCount","createdByUserId","createdAt","lastDeliveryAt","secret"],"description":"L'endpoint et son secret de signature. C'est la seule réponse de l'API qui le contienne."},"CreateWebhookEndpoint":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":120,"description":"À quoi il sert, ex. « notifications Slack »"},"url":{"type":"string","minLength":1,"maxLength":2048,"description":"HTTP ou HTTPS"},"eventTypes":{"description":"Par défaut `[\"*\"]`. Un motif qui ne correspond à aucun type connu est refusé.","maxItems":50,"type":"array","items":{"type":"string","minLength":1,"maxLength":60}}},"required":["name","url"]},"UpdateWebhookEndpoint":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":120},"url":{"type":"string","minLength":1,"maxLength":2048},"eventTypes":{"maxItems":50,"type":"array","items":{"type":"string","minLength":1,"maxLength":60}}}},"WebhookDelivery":{"type":"object","properties":{"id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"endpointId":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"kind":{"type":"string","enum":["event","batch"],"description":"`batch` = une édition en masse, un envoi pour N cartes"},"eventType":{"type":"string"},"status":{"type":"string","enum":["pending","delivered","failed"]},"attempts":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"nextAttemptAt":{"type":"string","description":"Date ISO 8601"},"lastStatusCode":{"type":["integer","null"],"minimum":-9007199254740991,"maximum":9007199254740991},"lastError":{"type":["string","null"]},"createdAt":{"type":"string","description":"Date ISO 8601"},"deliveredAt":{"type":["string","null"],"description":"Date ISO 8601"},"payload":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}},"required":["id","endpointId","kind","eventType","status","attempts","nextAttemptAt","lastStatusCode","lastError","createdAt","deliveredAt","payload"]},"AppInstallation":{"type":"object","properties":{"id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"workspaceId":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"appId":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"appName":{"type":"string"},"clientId":{"type":"string"},"scopes":{"type":"array","items":{"type":"string"},"description":"Ce à quoi ce workspace a consenti, nommément"},"role":{"$ref":"#/components/schemas/Role"},"prefix":{"type":"string","description":"La partie publique du jeton, ex. `spa_1a2b3c4d`"},"installedByUserId":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"createdAt":{"type":"string","description":"Date ISO 8601"},"lastUsedAt":{"type":["string","null"],"description":"Date ISO 8601, ou `null` — jamais servie"}},"required":["id","workspaceId","appId","appName","clientId","scopes","role","prefix","installedByUserId","createdAt","lastUsedAt"],"description":"L'identité d'une app DANS un workspace : révocable seule, elle survit au départ de celui qui l'a installée."},"OAuthApp":{"type":"object","properties":{"id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"name":{"type":"string"},"clientId":{"type":"string","description":"Public, porté dans chaque requête OAuth"},"redirectUris":{"type":"array","items":{"type":"string"},"description":"Comparaison **exacte**, jamais par préfixe"},"scopes":{"type":"array","items":{"type":"string"},"description":"Ce que l'app a le droit de **demander**"},"ownerUserId":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"createdAt":{"type":"string","description":"Date ISO 8601"}},"required":["id","name","clientId","redirectUris","scopes","ownerUserId","createdAt"],"description":"Un service externe enregistré sur l'instance. Hors workspace : une app s'INSTALLE dans des workspaces."},"AppGrantResult":{"type":"object","properties":{"redirectTo":{"type":"string","description":"Où renvoyer le navigateur : `redirect_uri` + `code` (+ `state`)"},"app":{"$ref":"#/components/schemas/OAuthApp"}},"required":["redirectTo","app"]},"GrantAppInstallation":{"type":"object","properties":{"clientId":{"type":"string","minLength":1,"maxLength":200},"scopes":{"minItems":1,"maxItems":20,"type":"array","items":{"type":"string","minLength":1,"maxLength":40}},"role":{"$ref":"#/components/schemas/Role"},"redirectUri":{"type":"string","minLength":1,"maxLength":2048,"description":"Doit être **exactement** l'une de celles déclarées"},"state":{"description":"Repassé tel quel dans `redirectTo`","type":"string","maxLength":500},"codeChallenge":{"type":"string","minLength":43,"maxLength":128,"description":"PKCE S256 — `base64url(sha256(code_verifier))`. Exigé en plus du `client_secret`."}},"required":["clientId","scopes","role","redirectUri","codeChallenge"],"description":"Le consentement, comme une écriture authentifiée plutôt qu'une redirection : l'API ne rend pas de HTML, et cette étape passe ainsi par les mêmes gardes que le reste."},"Scope":{"type":"object","properties":{"scope":{"type":"string"},"label":{"type":"string"}},"required":["scope","label"],"description":"Un droit accordable à une app, par famille de ressources × lecture/écriture. La table route→scope est fail-closed (RFC 0003 §7.5)."},"CreatedOAuthApp":{"type":"object","properties":{"id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"name":{"type":"string"},"clientId":{"type":"string","description":"Public, porté dans chaque requête OAuth"},"redirectUris":{"type":"array","items":{"type":"string"},"description":"Comparaison **exacte**, jamais par préfixe"},"scopes":{"type":"array","items":{"type":"string"},"description":"Ce que l'app a le droit de **demander**"},"ownerUserId":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"createdAt":{"type":"string","description":"Date ISO 8601"},"clientSecret":{"type":"string","description":"Rendu une seule fois. L'API n'en garde que le SHA-256."}},"required":["id","name","clientId","redirectUris","scopes","ownerUserId","createdAt","clientSecret"]},"CreateOAuthApp":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":120},"redirectUris":{"minItems":1,"maxItems":10,"type":"array","items":{"type":"string","minLength":1,"maxLength":2048}},"scopes":{"minItems":1,"maxItems":20,"type":"array","items":{"type":"string","minLength":1,"maxLength":40}}},"required":["name","redirectUris","scopes"]}}}}