{"openapi":"3.1.0","info":{"title":"Runeya Cloud API","version":"1.0.0","description":"L'API publique du cloud Runeya : alimenter les tableaux kanban d'une organisation depuis un système tiers — CI, formulaire, boîte mail, supervision.\n\n## Authentification\n\nChaque appel porte une clé émise dans **Réglages → Organisation → Clés API** :\n\n```\nAuthorization: Bearer rk_…\n```\n\nLe secret n'est montré qu'à la création — le cloud n'en garde qu'un condensat. Une clé perdue se révoque et se réémet ; elle ne se retrouve pas.\n\nUne clé agit **au nom de la personne qui l'a émise** : elle ne lui survit pas. Si cette personne quitte l'organisation, la clé cesse de fonctionner (`401 key_owner_inactive`) et doit être réémise par quelqu'un d'autre.\n\n## Portées\n\n| Portée | Ce qu'elle ouvre |\n| --- | --- |\n| `kanban:read` | Lister les tableaux, lire un tableau et ses cartes |\n| `kanban:write` | Ajouter une carte |\n| `kanban:comment` | Commenter une carte |\n| `kanban:delete` | Supprimer une carte ou une colonne |\n\n`kanban:delete` est séparée à dessein : effacer est le seul geste que rien ne rattrape, et une clé qui ne fait qu'alimenter n'a pas à en disposer.\n\nUne clé peut en outre être restreinte à certains tableaux. Un tableau hors de sa portée répond `404` — depuis l'extérieur, il n'existe pas.\n\n## Limites\n\nLes quotas du plan de l'organisation s'appliquent comme dans l'application : un refus porte alors un `code` en `PLAN_LIMIT_*`. Le corps d'une requête est borné à 256 Ko."},"servers":[{"url":"https://api.runeya.dev/api/v1"}],"security":[{"ApiKeyAuth":[]}],"tags":[{"name":"Kanban","description":"Tableaux et cartes"},{"name":"Clé","description":"La clé elle-même"}],"paths":{"/me":{"get":{"tags":["Clé"],"summary":"Ce que porte la clé présentée","description":"Son organisation, ses portées, les tableaux qu'elle atteint. De quoi diagnostiquer un 403 sans deviner.","operationId":"getSelf","responses":{"200":{"description":"La clé","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"organizationId":{"type":"string"},"scopes":{"type":"array","items":{"type":"string","enum":["kanban:read","kanban:write","kanban:comment","kanban:delete"]}},"boards":{"type":["array","null"],"items":{"type":"string"},"description":"`null` veut dire « tous les tableaux de l'organisation »."}}}}}},"401":{"description":"Erreur","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"insufficient_scope"},"message":{"type":"string"}}}}}}}}}}},"/members":{"get":{"tags":["Kanban"],"summary":"Les membres de l'organisation","description":"Id et nom, rien d'autre — de quoi assigner une carte sans deviner un identifiant. L'adresse électronique n'est jamais exposée.","operationId":"listMembers","responses":{"200":{"description":"Les membres","content":{"application/json":{"schema":{"type":"object","properties":{"members":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"}}}}}}}}},"401":{"description":"Erreur","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"insufficient_scope"},"message":{"type":"string"}}}}}}}},"403":{"description":"Erreur","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"insufficient_scope"},"message":{"type":"string"}}}}}}}}}}},"/boards":{"get":{"tags":["Kanban"],"summary":"Lister les tableaux","description":"Les tableaux non archivés que la clé atteint, avec leurs colonnes — sans les cartes : c'est le titre de colonne qu'on vient chercher pour déposer une carte.","operationId":"listBoards","responses":{"200":{"description":"Les tableaux","content":{"application/json":{"schema":{"type":"object","properties":{"boards":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"description":{"type":"string"},"updatedAt":{"type":"string","format":"date-time"},"columns":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"title":{"type":"string"},"cardCount":{"type":"integer"}}}}}}}}}}}},"401":{"description":"Erreur","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"insufficient_scope"},"message":{"type":"string"}}}}}}}},"403":{"description":"Erreur","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"insufficient_scope"},"message":{"type":"string"}}}}}}}}}},"post":{"tags":["Kanban"],"summary":"Créer un tableau","description":"Le tableau naît **hors de portée** d'une clé restreinte : la liste de tableaux d'une clé ne s'élargit pas toute seule. À l'émetteur de l'y ajouter s'il le souhaite.","operationId":"createBoard","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name"],"properties":{"name":{"type":"string","minLength":1,"maxLength":200},"description":{"type":"string","maxLength":10000},"instructions":{"type":"string","maxLength":20000,"description":"Le mode d'emploi du tableau, lu surtout par l'IA."},"columns":{"type":"array","maxItems":50,"items":{"type":"object","required":["title"],"properties":{"title":{"type":"string"}}},"description":"Des titres ; le serveur pose les identifiants."}}},"example":{"name":"Incidents","columns":[{"title":"Nouveau"},{"title":"En cours"},{"title":"Résolu"}]}}}},"responses":{"201":{"description":"Le tableau créé"},"400":{"description":"Erreur","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"insufficient_scope"},"message":{"type":"string"}}}}}}}},"401":{"description":"Erreur","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"insufficient_scope"},"message":{"type":"string"}}}}}}}},"403":{"description":"Erreur","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"insufficient_scope"},"message":{"type":"string"}}}}}}}}}}},"/boards/{boardId}":{"get":{"tags":["Kanban"],"summary":"Lire un tableau","description":"Le tableau, ses colonnes et ses cartes. Les journaux de cartes (commentaires compris) ne sont pas inclus : seul leur nombre est donné.","operationId":"getBoard","parameters":[{"name":"boardId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Le tableau","content":{"application/json":{"schema":{"type":"object","properties":{"board":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"description":{"type":"string"},"instructions":{"type":"string","description":"Le mode d'emploi du tableau, écrit par l'équipe."},"archived":{"type":"boolean"},"updatedAt":{"type":"string","format":"date-time"},"columns":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"title":{"type":"string"},"cards":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"title":{"type":"string"},"markdown":{"type":"string","description":"Le corps de la carte, en Markdown."},"order":{"type":"integer","description":"Rang dans la colonne, de haut en bas."},"priority":{"type":["integer","null"],"minimum":1,"maximum":10,"description":"10 est le plus urgent."},"groups":{"type":"array","items":{"type":"string"},"description":"Étiquettes libres posées par l'équipe."},"color":{"type":["string","null"],"example":"#f97316"},"deadline":{"type":["string","null"],"pattern":"^\\d{4}-\\d{2}-\\d{2}$","example":"2026-03-04","description":"L'échéance de la carte, au jour près (`AAAA-MM-JJ`). Un jour et non un instant : une équipe se dit « pour vendredi », et un instant s'afficherait la veille pour qui travaille à l'ouest de Greenwich. `null` quand la carte n'en a pas."},"assignees":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"}}}},"attachments":{"type":"array","description":"Les fichiers portés par la carte. Leur `id` est ce que demande la route de lecture ; ceux d'un commentaire se lisent dans le journal de la carte.","items":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"size":{"type":"integer"},"contentType":{"type":"string"}}}},"commentCount":{"type":"integer"},"archivedAt":{"type":["string","null"],"format":"date-time","description":"Quand la carte a été archivée, ou `null` si elle est au tableau. Une carte archivée ne figure dans aucune colonne mais reste lisible par son adresse."},"archivedFrom":{"type":["object","null"],"description":"La colonne d'où la carte a été archivée — celle où `unarchive` la remettra, si elle existe encore.","properties":{"columnId":{"type":"string"},"columnTitle":{"type":"string"}}},"origin":{"type":["object","null"],"description":"Le tableau d'origine d'une carte posée ici en **référence**, ou `null` si la carte est chez elle. Une référence est la même carte : ce qu'on y écrit se lit sur son tableau d'origine.","properties":{"boardId":{"type":"string"},"boardName":{"type":"string"}}},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}}}}}}}}}}}}}},"401":{"description":"Erreur","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"insufficient_scope"},"message":{"type":"string"}}}}}}}},"403":{"description":"Erreur","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"insufficient_scope"},"message":{"type":"string"}}}}}}}},"404":{"description":"Erreur","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"insufficient_scope"},"message":{"type":"string"}}}}}}}}}},"patch":{"tags":["Kanban"],"summary":"Modifier un tableau","description":"Son nom, sa description, ses consignes, son archivage. Jamais ses cartes — celles-ci ont leurs propres routes.","operationId":"updateBoard","parameters":[{"name":"boardId","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","minProperties":1,"properties":{"name":{"type":"string","minLength":1,"maxLength":200},"description":{"type":"string","maxLength":10000},"instructions":{"type":"string","maxLength":20000},"archived":{"type":"boolean"}}},"example":{"archived":true}}}},"responses":{"200":{"description":"Le tableau modifié"},"400":{"description":"Erreur","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"insufficient_scope"},"message":{"type":"string"}}}}}}}},"401":{"description":"Erreur","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"insufficient_scope"},"message":{"type":"string"}}}}}}}},"403":{"description":"Erreur","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"insufficient_scope"},"message":{"type":"string"}}}}}}}},"404":{"description":"Erreur","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"insufficient_scope"},"message":{"type":"string"}}}}}}}}}}},"/boards/{boardId}/columns":{"post":{"tags":["Kanban"],"summary":"Ajouter une colonne","operationId":"addColumn","parameters":[{"name":"boardId","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["title"],"properties":{"title":{"type":"string","minLength":1,"maxLength":200}}},"example":{"title":"En revue"}}}},"responses":{"201":{"description":"La colonne créée"},"400":{"description":"Erreur","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"insufficient_scope"},"message":{"type":"string"}}}}}}}},"401":{"description":"Erreur","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"insufficient_scope"},"message":{"type":"string"}}}}}}}},"403":{"description":"Erreur","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"insufficient_scope"},"message":{"type":"string"}}}}}}}},"404":{"description":"Erreur","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"insufficient_scope"},"message":{"type":"string"}}}}}}}}}}},"/boards/{boardId}/columns/reorder":{"post":{"tags":["Kanban"],"summary":"Réordonner les colonnes","description":"Seuls des identifiants voyagent : les cartes ne traversent pas le réseau, donc une réorganisation ne peut pas en perdre une. Une colonne non nommée se range à la suite plutôt que de disparaître.","operationId":"reorderColumns","parameters":[{"name":"boardId","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["columnIds"],"properties":{"columnIds":{"type":"array","minItems":1,"maxItems":100,"items":{"type":"string"}}}}}}},"responses":{"200":{"description":"Réordonnées"},"400":{"description":"Erreur","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"insufficient_scope"},"message":{"type":"string"}}}}}}}},"401":{"description":"Erreur","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"insufficient_scope"},"message":{"type":"string"}}}}}}}},"403":{"description":"Erreur","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"insufficient_scope"},"message":{"type":"string"}}}}}}}},"404":{"description":"Erreur","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"insufficient_scope"},"message":{"type":"string"}}}}}}}}}}},"/boards/{boardId}/columns/{columnId}":{"patch":{"tags":["Kanban"],"summary":"Renommer une colonne","operationId":"renameColumn","parameters":[{"name":"boardId","in":"path","required":true,"schema":{"type":"string"}},{"name":"columnId","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["title"],"properties":{"title":{"type":"string","minLength":1,"maxLength":200}}}}}},"responses":{"200":{"description":"Renommée"},"400":{"description":"Erreur","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"insufficient_scope"},"message":{"type":"string"}}}}}}}},"401":{"description":"Erreur","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"insufficient_scope"},"message":{"type":"string"}}}}}}}},"403":{"description":"Erreur","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"insufficient_scope"},"message":{"type":"string"}}}}}}}},"404":{"description":"Erreur","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"insufficient_scope"},"message":{"type":"string"}}}}}}}}}},"delete":{"tags":["Kanban"],"summary":"Supprimer une colonne","description":"La colonne et les cartes qu'elle porte quittent le tableau. Rien n'est effacé : elles sont conservées avec leurs journaux et leurs fichiers, mais ne sont plus jointes par aucune route. Exige `kanban:delete`, comme la suppression d'une carte.","operationId":"deleteColumn","parameters":[{"name":"boardId","in":"path","required":true,"schema":{"type":"string"}},{"name":"columnId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"204":{"description":"Supprimée"},"401":{"description":"Erreur","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"insufficient_scope"},"message":{"type":"string"}}}}}}}},"403":{"description":"Erreur","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"insufficient_scope"},"message":{"type":"string"}}}}}}}},"404":{"description":"Erreur","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"insufficient_scope"},"message":{"type":"string"}}}}}}}}}}},"/boards/{boardId}/cards":{"post":{"tags":["Kanban"],"summary":"Ajouter une carte","description":"Dépose **une** carte dans une colonne, sans toucher au reste du tableau : deux appels simultanés se rangent tous les deux, et l'écran de quelqu'un qui édite au même moment n'est pas écrasé.\n\nLa carte apparaît immédiatement chez tous ceux qui regardent le tableau.","operationId":"addCard","parameters":[{"name":"boardId","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["column","title"],"properties":{"column":{"type":"string","description":"L'id de la colonne, ou son titre — comparé sans tenir compte de la casse.","example":"À faire"},"title":{"type":"string","minLength":1,"maxLength":500},"markdown":{"type":"string","maxLength":100000},"priority":{"type":"integer","minimum":1,"maximum":10},"groups":{"type":"array","maxItems":30,"items":{"type":"string","maxLength":60}},"color":{"type":"string","pattern":"^#[0-9a-fA-F]{6}$"},"deadline":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$","example":"2026-03-04","description":"L'échéance, au jour près."},"attachments":{"type":"array","maxItems":50,"description":"Des fichiers déjà déposés par `POST .../attachments`, à joindre d'emblée. La liste **remplace** celle de la carte : c'est ainsi qu'on en retire un.","items":{"type":"object","required":["id","name","size","contentType"],"properties":{"id":{"type":"string"},"name":{"type":"string"},"size":{"type":"integer"},"contentType":{"type":"string"}}}},"assignees":{"type":"array","maxItems":20,"items":{"type":"string"},"description":"Identifiants d'utilisateurs (voir `GET /members`). Un identifiant qui n'est pas membre est écarté en silence."},"placement":{"type":"string","enum":["top","bottom"],"description":"Où la carte se pose dans sa colonne. En bas par défaut."},"createdAt":{"type":"string","format":"date-time","description":"La date de naissance de la carte, pour un import : sans elle, tout un tableau repris naît le même jour et l'antériorité devient illisible. Un instant ISO complet, jamais dans le futur. Acceptée ici seulement — `PATCH` ne la change pas. L'entrée « créée » du journal porte la même date et signale qu'elle a été reprise."}}},"example":{"column":"À faire","title":"Build #4821 en échec","markdown":"`yarn test` échoue sur `kanban-quota.test.ts`.\n\n[Voir le job](https://ci.example.com/4821)","priority":8,"groups":["ci"]}}}},"responses":{"201":{"description":"La carte créée","content":{"application/json":{"schema":{"type":"object","properties":{"card":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"title":{"type":"string"},"markdown":{"type":"string","description":"Le corps de la carte, en Markdown."},"order":{"type":"integer","description":"Rang dans la colonne, de haut en bas."},"priority":{"type":["integer","null"],"minimum":1,"maximum":10,"description":"10 est le plus urgent."},"groups":{"type":"array","items":{"type":"string"},"description":"Étiquettes libres posées par l'équipe."},"color":{"type":["string","null"],"example":"#f97316"},"deadline":{"type":["string","null"],"pattern":"^\\d{4}-\\d{2}-\\d{2}$","example":"2026-03-04","description":"L'échéance de la carte, au jour près (`AAAA-MM-JJ`). Un jour et non un instant : une équipe se dit « pour vendredi », et un instant s'afficherait la veille pour qui travaille à l'ouest de Greenwich. `null` quand la carte n'en a pas."},"assignees":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"}}}},"attachments":{"type":"array","description":"Les fichiers portés par la carte. Leur `id` est ce que demande la route de lecture ; ceux d'un commentaire se lisent dans le journal de la carte.","items":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"size":{"type":"integer"},"contentType":{"type":"string"}}}},"commentCount":{"type":"integer"},"archivedAt":{"type":["string","null"],"format":"date-time","description":"Quand la carte a été archivée, ou `null` si elle est au tableau. Une carte archivée ne figure dans aucune colonne mais reste lisible par son adresse."},"archivedFrom":{"type":["object","null"],"description":"La colonne d'où la carte a été archivée — celle où `unarchive` la remettra, si elle existe encore.","properties":{"columnId":{"type":"string"},"columnTitle":{"type":"string"}}},"origin":{"type":["object","null"],"description":"Le tableau d'origine d'une carte posée ici en **référence**, ou `null` si la carte est chez elle. Une référence est la même carte : ce qu'on y écrit se lit sur son tableau d'origine.","properties":{"boardId":{"type":"string"},"boardName":{"type":"string"}}},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}}}}}}}},"400":{"description":"Erreur","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"insufficient_scope"},"message":{"type":"string"}}}}}}}},"401":{"description":"Erreur","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"insufficient_scope"},"message":{"type":"string"}}}}}}}},"403":{"description":"Erreur","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"insufficient_scope"},"message":{"type":"string"}}}}}}}},"404":{"description":"Erreur","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"insufficient_scope"},"message":{"type":"string"}}}}}}}},"409":{"description":"Erreur","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"insufficient_scope"},"message":{"type":"string"}}}}}}}}}}},"/boards/{boardId}/cards/{cardId}":{"get":{"tags":["Kanban"],"summary":"Lire une carte","description":"La carte, sa colonne, et **son journal** — que la lecture de tableau n'emporte pas, parce qu'il grossit sans fin.","operationId":"getCard","parameters":[{"name":"boardId","in":"path","required":true,"schema":{"type":"string"}},{"name":"cardId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"La carte, sa colonne et son journal","content":{"application/json":{"schema":{"type":"object","properties":{"card":{"allOf":[{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"title":{"type":"string"},"markdown":{"type":"string","description":"Le corps de la carte, en Markdown."},"order":{"type":"integer","description":"Rang dans la colonne, de haut en bas."},"priority":{"type":["integer","null"],"minimum":1,"maximum":10,"description":"10 est le plus urgent."},"groups":{"type":"array","items":{"type":"string"},"description":"Étiquettes libres posées par l'équipe."},"color":{"type":["string","null"],"example":"#f97316"},"deadline":{"type":["string","null"],"pattern":"^\\d{4}-\\d{2}-\\d{2}$","example":"2026-03-04","description":"L'échéance de la carte, au jour près (`AAAA-MM-JJ`). Un jour et non un instant : une équipe se dit « pour vendredi », et un instant s'afficherait la veille pour qui travaille à l'ouest de Greenwich. `null` quand la carte n'en a pas."},"assignees":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"}}}},"attachments":{"type":"array","description":"Les fichiers portés par la carte. Leur `id` est ce que demande la route de lecture ; ceux d'un commentaire se lisent dans le journal de la carte.","items":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"size":{"type":"integer"},"contentType":{"type":"string"}}}},"commentCount":{"type":"integer"},"archivedAt":{"type":["string","null"],"format":"date-time","description":"Quand la carte a été archivée, ou `null` si elle est au tableau. Une carte archivée ne figure dans aucune colonne mais reste lisible par son adresse."},"archivedFrom":{"type":["object","null"],"description":"La colonne d'où la carte a été archivée — celle où `unarchive` la remettra, si elle existe encore.","properties":{"columnId":{"type":"string"},"columnTitle":{"type":"string"}}},"origin":{"type":["object","null"],"description":"Le tableau d'origine d'une carte posée ici en **référence**, ou `null` si la carte est chez elle. Une référence est la même carte : ce qu'on y écrit se lit sur son tableau d'origine.","properties":{"boardId":{"type":"string"},"boardName":{"type":"string"}}},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}}},{"type":"object","properties":{"columnId":{"type":"string"},"columnTitle":{"type":"string"},"history":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","description":"Identifiant de l'entrée. Absent sur les plus anciennes."},"type":{"type":"string","enum":["created","edited","moved","image_added","image_removed","title_changed","conversation_started","scenario_started","comment","archived","unarchived"]},"timestamp":{"type":"string","format":"date-time"},"detail":{"type":"string","description":"Le texte d'un commentaire, ou le contexte du geste."},"columnId":{"type":"string","description":"La colonne où l'événement fait entrer la carte (`created`, `moved`, `unarchived`). Absent des événements anciens que le titre seul ne permettait pas de rattacher."},"author":{"type":"string","enum":["ai","user"],"description":"Sur les commentaires seulement."},"authorUser":{"type":"object","description":"Qui est derrière le geste, estampillé par le serveur. Pour une clé, c'est le nom de la clé et non son porteur — personne ne doit croire qu'un humain a déplacé ce qu'un robot a déplacé.","properties":{"id":{"type":"string"},"name":{"type":"string"}}},"editedAt":{"type":"string","format":"date-time","description":"Posé quand l'auteur a repris son commentaire."},"attachments":{"type":"array","description":"Les fichiers d'un commentaire. Ils se lisent par la route des pièces jointes de la carte, comme les siens.","items":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"size":{"type":"integer"},"contentType":{"type":"string"}}}}}}}}}]}}}}}},"401":{"description":"Erreur","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"insufficient_scope"},"message":{"type":"string"}}}}}}}},"403":{"description":"Erreur","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"insufficient_scope"},"message":{"type":"string"}}}}}}}},"404":{"description":"Erreur","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"insufficient_scope"},"message":{"type":"string"}}}}}}}}}},"patch":{"tags":["Kanban"],"summary":"Modifier une carte","description":"Un champ absent n'est pas modifié. `priority: null` et `color: null` retirent le champ — une carte sans priorité n'est pas une carte de priorité nulle.\n\n`assignees` prend des **identifiants d'utilisateurs** (voir `GET /members`), jamais des noms : le nom affiché est lu dans l'organisation, ce qui empêche d'attacher un inconnu à une carte. Un identifiant qui n'est pas membre est écarté en silence, et `[]` désassigne.\n\n`attachments` est la liste **complète** des fichiers de la carte, telle qu'elle doit devenir : en retirer un revient à l'omettre. Chaque fichier doit appartenir à l'organisation, et un fichier retiré est effacé s'il ne sert plus ailleurs.\n\nUne modification qui ne change rien n'écrit rien : la carte est renvoyée telle quelle, sans ligne au journal ni réveil des autres lecteurs.","operationId":"updateCard","parameters":[{"name":"boardId","in":"path","required":true,"schema":{"type":"string"}},{"name":"cardId","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","minProperties":1,"properties":{"title":{"type":"string","minLength":1,"maxLength":500},"markdown":{"type":"string","maxLength":100000},"priority":{"type":["integer","null"],"minimum":1,"maximum":10},"groups":{"type":"array","maxItems":30,"items":{"type":"string","maxLength":60}},"color":{"type":["string","null"],"pattern":"^#[0-9a-fA-F]{6}$"},"deadline":{"type":["string","null"],"pattern":"^\\d{4}-\\d{2}-\\d{2}$","example":"2026-03-04","description":"L'échéance, au jour près. `null` la retire."},"assignees":{"type":"array","maxItems":20,"items":{"type":"string"},"description":"Identifiants d'utilisateurs."},"attachments":{"type":"array","maxItems":50,"description":"Des fichiers déjà déposés par `POST .../attachments`, à joindre d'emblée. La liste **remplace** celle de la carte : c'est ainsi qu'on en retire un.","items":{"type":"object","required":["id","name","size","contentType"],"properties":{"id":{"type":"string"},"name":{"type":"string"},"size":{"type":"integer"},"contentType":{"type":"string"}}}}}},"example":{"priority":10,"color":"#ef4444","assignees":["user_abc"]}}}},"responses":{"200":{"description":"La carte modifiée","content":{"application/json":{"schema":{"type":"object","properties":{"card":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"title":{"type":"string"},"markdown":{"type":"string","description":"Le corps de la carte, en Markdown."},"order":{"type":"integer","description":"Rang dans la colonne, de haut en bas."},"priority":{"type":["integer","null"],"minimum":1,"maximum":10,"description":"10 est le plus urgent."},"groups":{"type":"array","items":{"type":"string"},"description":"Étiquettes libres posées par l'équipe."},"color":{"type":["string","null"],"example":"#f97316"},"deadline":{"type":["string","null"],"pattern":"^\\d{4}-\\d{2}-\\d{2}$","example":"2026-03-04","description":"L'échéance de la carte, au jour près (`AAAA-MM-JJ`). Un jour et non un instant : une équipe se dit « pour vendredi », et un instant s'afficherait la veille pour qui travaille à l'ouest de Greenwich. `null` quand la carte n'en a pas."},"assignees":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"}}}},"attachments":{"type":"array","description":"Les fichiers portés par la carte. Leur `id` est ce que demande la route de lecture ; ceux d'un commentaire se lisent dans le journal de la carte.","items":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"size":{"type":"integer"},"contentType":{"type":"string"}}}},"commentCount":{"type":"integer"},"archivedAt":{"type":["string","null"],"format":"date-time","description":"Quand la carte a été archivée, ou `null` si elle est au tableau. Une carte archivée ne figure dans aucune colonne mais reste lisible par son adresse."},"archivedFrom":{"type":["object","null"],"description":"La colonne d'où la carte a été archivée — celle où `unarchive` la remettra, si elle existe encore.","properties":{"columnId":{"type":"string"},"columnTitle":{"type":"string"}}},"origin":{"type":["object","null"],"description":"Le tableau d'origine d'une carte posée ici en **référence**, ou `null` si la carte est chez elle. Une référence est la même carte : ce qu'on y écrit se lit sur son tableau d'origine.","properties":{"boardId":{"type":"string"},"boardName":{"type":"string"}}},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}}}}}}}},"400":{"description":"Erreur","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"insufficient_scope"},"message":{"type":"string"}}}}}}}},"401":{"description":"Erreur","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"insufficient_scope"},"message":{"type":"string"}}}}}}}},"403":{"description":"Erreur","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"insufficient_scope"},"message":{"type":"string"}}}}}}}},"404":{"description":"Erreur","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"insufficient_scope"},"message":{"type":"string"}}}}}}}}}},"delete":{"tags":["Kanban"],"summary":"Supprimer une carte","description":"Exige la portée `kanban:delete`. La carte disparaît de partout, mais rien n'est effacé : elle est conservée avec son journal et ses fichiers, et n'est plus jointe par aucune route. Pour un retrait réversible, préférer `POST .../archive`.","operationId":"deleteCard","parameters":[{"name":"boardId","in":"path","required":true,"schema":{"type":"string"}},{"name":"cardId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"204":{"description":"Supprimée"},"401":{"description":"Erreur","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"insufficient_scope"},"message":{"type":"string"}}}}}}}},"403":{"description":"Erreur","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"insufficient_scope"},"message":{"type":"string"}}}}}}}},"404":{"description":"Erreur","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"insufficient_scope"},"message":{"type":"string"}}}}}}}}}}},"/boards/{boardId}/archive":{"get":{"tags":["Kanban"],"summary":"L'archive d'un tableau","description":"Les cartes sorties du tableau sans être supprimées, la plus récemment archivée en tête. `q` cherche dans le titre, le corps, les commentaires et les conversations de support suivies par la carte ; `cursor` déroule les pages suivantes.","operationId":"listArchivedCards","parameters":[{"name":"boardId","in":"path","required":true,"schema":{"type":"string"}},{"name":"q","in":"query","required":false,"schema":{"type":"string","maxLength":200},"description":"Recherche dans le titre, le corps, le texte des commentaires et les conversations de support suivies (titre, messages et nom de leurs auteurs), sans casse ni accents."},{"name":"cursor","in":"query","required":false,"schema":{"type":"string","maxLength":512},"description":"Le `nextCursor` de la page précédente. Opaque : à repasser tel quel, jamais à fabriquer. Un curseur illisible repart du début plutôt que d'échouer."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":200,"default":200},"description":"Taille de page voulue. Bornée à 200 : une valeur plus grande est ignorée, pas refusée."}],"responses":{"200":{"description":"Les cartes de l'archive","content":{"application/json":{"schema":{"type":"object","properties":{"cards":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"title":{"type":"string"},"markdown":{"type":"string","description":"Le corps de la carte, en Markdown."},"order":{"type":"integer","description":"Rang dans la colonne, de haut en bas."},"priority":{"type":["integer","null"],"minimum":1,"maximum":10,"description":"10 est le plus urgent."},"groups":{"type":"array","items":{"type":"string"},"description":"Étiquettes libres posées par l'équipe."},"color":{"type":["string","null"],"example":"#f97316"},"deadline":{"type":["string","null"],"pattern":"^\\d{4}-\\d{2}-\\d{2}$","example":"2026-03-04","description":"L'échéance de la carte, au jour près (`AAAA-MM-JJ`). Un jour et non un instant : une équipe se dit « pour vendredi », et un instant s'afficherait la veille pour qui travaille à l'ouest de Greenwich. `null` quand la carte n'en a pas."},"assignees":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"}}}},"attachments":{"type":"array","description":"Les fichiers portés par la carte. Leur `id` est ce que demande la route de lecture ; ceux d'un commentaire se lisent dans le journal de la carte.","items":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"size":{"type":"integer"},"contentType":{"type":"string"}}}},"commentCount":{"type":"integer"},"archivedAt":{"type":["string","null"],"format":"date-time","description":"Quand la carte a été archivée, ou `null` si elle est au tableau. Une carte archivée ne figure dans aucune colonne mais reste lisible par son adresse."},"archivedFrom":{"type":["object","null"],"description":"La colonne d'où la carte a été archivée — celle où `unarchive` la remettra, si elle existe encore.","properties":{"columnId":{"type":"string"},"columnTitle":{"type":"string"}}},"origin":{"type":["object","null"],"description":"Le tableau d'origine d'une carte posée ici en **référence**, ou `null` si la carte est chez elle. Une référence est la même carte : ce qu'on y écrit se lit sur son tableau d'origine.","properties":{"boardId":{"type":"string"},"boardName":{"type":"string"}}},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}}}},"truncated":{"type":"boolean","description":"Vrai quand il reste des cartes après celles-ci : au plus 200 sont rendues par appel, les plus récemment archivées d'abord."},"nextCursor":{"type":"string","nullable":true,"description":"À repasser dans `cursor` pour la page suivante. `null` quand l'archive est épuisée — c'est cela, et non le nombre de cartes rendues, qui dit qu'on a tout lu."}}}}}},"401":{"description":"Erreur","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"insufficient_scope"},"message":{"type":"string"}}}}}}}},"403":{"description":"Erreur","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"insufficient_scope"},"message":{"type":"string"}}}}}}}},"404":{"description":"Erreur","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"insufficient_scope"},"message":{"type":"string"}}}}}}}}}}},"/boards/{boardId}/cards/{cardId}/archive":{"post":{"tags":["Kanban"],"summary":"Archiver une carte","description":"La carte quitte le tableau et rejoint l'archive ; sa colonne est retenue pour l'y remettre. Portée `kanban:write` et non `kanban:delete` : le geste se rattrape.","operationId":"archiveCard","parameters":[{"name":"boardId","in":"path","required":true,"schema":{"type":"string"}},{"name":"cardId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"La carte archivée","content":{"application/json":{"schema":{"type":"object","properties":{"card":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"title":{"type":"string"},"markdown":{"type":"string","description":"Le corps de la carte, en Markdown."},"order":{"type":"integer","description":"Rang dans la colonne, de haut en bas."},"priority":{"type":["integer","null"],"minimum":1,"maximum":10,"description":"10 est le plus urgent."},"groups":{"type":"array","items":{"type":"string"},"description":"Étiquettes libres posées par l'équipe."},"color":{"type":["string","null"],"example":"#f97316"},"deadline":{"type":["string","null"],"pattern":"^\\d{4}-\\d{2}-\\d{2}$","example":"2026-03-04","description":"L'échéance de la carte, au jour près (`AAAA-MM-JJ`). Un jour et non un instant : une équipe se dit « pour vendredi », et un instant s'afficherait la veille pour qui travaille à l'ouest de Greenwich. `null` quand la carte n'en a pas."},"assignees":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"}}}},"attachments":{"type":"array","description":"Les fichiers portés par la carte. Leur `id` est ce que demande la route de lecture ; ceux d'un commentaire se lisent dans le journal de la carte.","items":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"size":{"type":"integer"},"contentType":{"type":"string"}}}},"commentCount":{"type":"integer"},"archivedAt":{"type":["string","null"],"format":"date-time","description":"Quand la carte a été archivée, ou `null` si elle est au tableau. Une carte archivée ne figure dans aucune colonne mais reste lisible par son adresse."},"archivedFrom":{"type":["object","null"],"description":"La colonne d'où la carte a été archivée — celle où `unarchive` la remettra, si elle existe encore.","properties":{"columnId":{"type":"string"},"columnTitle":{"type":"string"}}},"origin":{"type":["object","null"],"description":"Le tableau d'origine d'une carte posée ici en **référence**, ou `null` si la carte est chez elle. Une référence est la même carte : ce qu'on y écrit se lit sur son tableau d'origine.","properties":{"boardId":{"type":"string"},"boardName":{"type":"string"}}},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}}}}}}}},"400":{"description":"Erreur","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"insufficient_scope"},"message":{"type":"string"}}}}}}}},"401":{"description":"Erreur","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"insufficient_scope"},"message":{"type":"string"}}}}}}}},"403":{"description":"Erreur","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"insufficient_scope"},"message":{"type":"string"}}}}}}}},"404":{"description":"Erreur","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"insufficient_scope"},"message":{"type":"string"}}}}}}}}}}},"/boards/{boardId}/cards/{cardId}/unarchive":{"post":{"tags":["Kanban"],"summary":"Désarchiver une carte","description":"La carte revient dans la colonne d'où elle est partie ; si cette colonne n'existe plus, dans la première du tableau. La réponse dit laquelle.","operationId":"unarchiveCard","parameters":[{"name":"boardId","in":"path","required":true,"schema":{"type":"string"}},{"name":"cardId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"La carte remise au tableau, et sa colonne","content":{"application/json":{"schema":{"type":"object","properties":{"card":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"title":{"type":"string"},"markdown":{"type":"string","description":"Le corps de la carte, en Markdown."},"order":{"type":"integer","description":"Rang dans la colonne, de haut en bas."},"priority":{"type":["integer","null"],"minimum":1,"maximum":10,"description":"10 est le plus urgent."},"groups":{"type":"array","items":{"type":"string"},"description":"Étiquettes libres posées par l'équipe."},"color":{"type":["string","null"],"example":"#f97316"},"deadline":{"type":["string","null"],"pattern":"^\\d{4}-\\d{2}-\\d{2}$","example":"2026-03-04","description":"L'échéance de la carte, au jour près (`AAAA-MM-JJ`). Un jour et non un instant : une équipe se dit « pour vendredi », et un instant s'afficherait la veille pour qui travaille à l'ouest de Greenwich. `null` quand la carte n'en a pas."},"assignees":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"}}}},"attachments":{"type":"array","description":"Les fichiers portés par la carte. Leur `id` est ce que demande la route de lecture ; ceux d'un commentaire se lisent dans le journal de la carte.","items":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"size":{"type":"integer"},"contentType":{"type":"string"}}}},"commentCount":{"type":"integer"},"archivedAt":{"type":["string","null"],"format":"date-time","description":"Quand la carte a été archivée, ou `null` si elle est au tableau. Une carte archivée ne figure dans aucune colonne mais reste lisible par son adresse."},"archivedFrom":{"type":["object","null"],"description":"La colonne d'où la carte a été archivée — celle où `unarchive` la remettra, si elle existe encore.","properties":{"columnId":{"type":"string"},"columnTitle":{"type":"string"}}},"origin":{"type":["object","null"],"description":"Le tableau d'origine d'une carte posée ici en **référence**, ou `null` si la carte est chez elle. Une référence est la même carte : ce qu'on y écrit se lit sur son tableau d'origine.","properties":{"boardId":{"type":"string"},"boardName":{"type":"string"}}},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}}},"columnId":{"type":"string"}}}}}},"400":{"description":"Erreur","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"insufficient_scope"},"message":{"type":"string"}}}}}}}},"401":{"description":"Erreur","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"insufficient_scope"},"message":{"type":"string"}}}}}}}},"403":{"description":"Erreur","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"insufficient_scope"},"message":{"type":"string"}}}}}}}},"404":{"description":"Erreur","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"insufficient_scope"},"message":{"type":"string"}}}}}}}}}}},"/boards/{boardId}/cards/{cardId}/move":{"post":{"tags":["Kanban"],"summary":"Déplacer une carte","description":"Vers une colonne du même tableau, désignée par son id ou son titre. Sans `index`, la carte se range en bas.","operationId":"moveCard","parameters":[{"name":"boardId","in":"path","required":true,"schema":{"type":"string"}},{"name":"cardId","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["column"],"properties":{"column":{"type":"string","example":"En cours"},"index":{"type":"integer","minimum":0,"description":"Rang voulu ; en bas par défaut."}}},"example":{"column":"En cours"}}}},"responses":{"200":{"description":"Les colonnes de départ et d'arrivée","content":{"application/json":{"schema":{"type":"object","properties":{"fromColumnId":{"type":"string"},"toColumnId":{"type":"string"}}}}}},"400":{"description":"Erreur","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"insufficient_scope"},"message":{"type":"string"}}}}}}}},"401":{"description":"Erreur","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"insufficient_scope"},"message":{"type":"string"}}}}}}}},"403":{"description":"Erreur","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"insufficient_scope"},"message":{"type":"string"}}}}}}}},"404":{"description":"Erreur","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"insufficient_scope"},"message":{"type":"string"}}}}}}}}}}},"/boards/{boardId}/cards/{cardId}/parent":{"post":{"tags":["Kanban"],"summary":"Poser ou retirer le parent d'une carte","description":"La filiation : `parent` nomme la carte dont celle-ci dépend, éventuellement sur un autre tableau de la même organisation. `parent: null` détache. Un cycle est refusé.","operationId":"setCardParent","parameters":[{"name":"boardId","in":"path","required":true,"schema":{"type":"string"}},{"name":"cardId","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["parent"],"properties":{"parent":{"type":["object","null"],"required":["cardId"],"properties":{"cardId":{"type":"string"},"boardId":{"type":"string","description":"Le tableau du parent ; celui de l'enfant par défaut."}}}}},"example":{"parent":{"cardId":"44374d6c-4027-4b1b-ac17-eb8a57123e89"}}}}},"responses":{"200":{"description":"Le lien, ou `null` après détachement"},"400":{"description":"Erreur","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"insufficient_scope"},"message":{"type":"string"}}}}}}}},"401":{"description":"Erreur","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"insufficient_scope"},"message":{"type":"string"}}}}}}}},"403":{"description":"Erreur","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"insufficient_scope"},"message":{"type":"string"}}}}}}}},"404":{"description":"Erreur","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"insufficient_scope"},"message":{"type":"string"}}}}}}}}}}},"/boards/{boardId}/cards/{cardId}/placements":{"get":{"tags":["Kanban"],"summary":"Lister les emplacements d'une carte","description":"Où se trouve la carte : son tableau d'origine (`home: true`), puis chaque tableau où elle est posée en référence. La carte peut être désignée depuis n'importe lequel de ces tableaux.","operationId":"listCardPlacements","parameters":[{"name":"boardId","in":"path","required":true,"schema":{"type":"string"}},{"name":"cardId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Les emplacements, le tableau d'origine en premier","content":{"application/json":{"schema":{"type":"object","properties":{"placements":{"type":"array","items":{"type":"object","properties":{"boardId":{"type":"string"},"boardName":{"type":"string"},"columnId":{"type":"string"},"columnTitle":{"type":"string"},"home":{"type":"boolean"}}}}}}}}},"401":{"description":"Erreur","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"insufficient_scope"},"message":{"type":"string"}}}}}}}},"403":{"description":"Erreur","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"insufficient_scope"},"message":{"type":"string"}}}}}}}},"404":{"description":"Erreur","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"insufficient_scope"},"message":{"type":"string"}}}}}}}}}}},"/boards/{boardId}/placements":{"post":{"tags":["Kanban"],"summary":"Poser une carte existante en référence","description":"Fait apparaître sur ce tableau une carte d'un autre tableau de la même organisation, **sans la copier**. La carte reste chez elle ; ce tableau lui donne une colonne et un rang à lui.\n\nUne fois posée, la carte se désigne aussi depuis ce tableau : la déplacer (`/move`) ne bouge que la référence ; la modifier, la commenter, l'archiver ou la supprimer agit sur la carte elle-même, donc partout.\n\nRefusé (`400`) pour une carte archivée, une carte déjà chez elle sur ce tableau, ou déjà posée ici. Le tableau d'origine de la carte doit être à la portée de la clé, sinon `404`.","operationId":"placeCard","parameters":[{"name":"boardId","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["cardId","column"],"properties":{"cardId":{"type":"string"},"column":{"type":"string","description":"La colonne : son id, ou son titre."},"index":{"type":"integer","minimum":0,"description":"Le rang dans la colonne ; en bas par défaut."}}},"example":{"cardId":"44374d6c-4027-4b1b-ac17-eb8a57123e89","column":"En cours"}}}},"responses":{"201":{"description":"La carte, vue depuis ce tableau"},"400":{"description":"Erreur","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"insufficient_scope"},"message":{"type":"string"}}}}}}}},"401":{"description":"Erreur","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"insufficient_scope"},"message":{"type":"string"}}}}}}}},"403":{"description":"Erreur","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"insufficient_scope"},"message":{"type":"string"}}}}}}}},"404":{"description":"Erreur","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"insufficient_scope"},"message":{"type":"string"}}}}}}}}}}},"/boards/{boardId}/placements/{cardId}":{"delete":{"tags":["Kanban"],"summary":"Retirer une référence","description":"Retire la carte de ce tableau, où elle était posée en référence. La carte elle-même n'est pas touchée : elle reste sur son tableau d'origine. D'où la portée `kanban:write`, et non `kanban:delete`.","operationId":"unplaceCard","parameters":[{"name":"boardId","in":"path","required":true,"schema":{"type":"string"}},{"name":"cardId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Référence retirée"},"401":{"description":"Erreur","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"insufficient_scope"},"message":{"type":"string"}}}}}}}},"403":{"description":"Erreur","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"insufficient_scope"},"message":{"type":"string"}}}}}}}},"404":{"description":"Erreur","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"insufficient_scope"},"message":{"type":"string"}}}}}}}}}}},"/boards/{boardId}/cards/{cardId}/attachments":{"post":{"tags":["Kanban"],"summary":"Joindre un fichier","description":"Une seule route pour les deux cibles : sans paramètre, le fichier va sur la **carte** ; avec `?comment=<id>`, sur ce **commentaire**.\n\nLe corps de la requête est le fichier lui-même, brut — pas de `multipart` : l'appelant est un script, et `--data-binary @fichier` s'écrit plus simplement qu'un corps composite.\n\nLa carte (et le commentaire) sont vérifiés **avant** que le moindre octet parte au stockage, et si le rattachement échoue le fichier est repris : rien ne se dépose sans être référencé.\n\nLe type sous lequel le fichier est *stocké* est décidé par ses octets, pas par l'en-tête `Content-Type` déclaré.\n\nPortée exigée : `kanban:comment` avec `?comment=`, `kanban:write` sinon. Au plus 50 fichiers par carte et 20 par commentaire ; 55 Mo par fichier.","operationId":"attachFile","parameters":[{"name":"boardId","in":"path","required":true,"schema":{"type":"string"}},{"name":"cardId","in":"path","required":true,"schema":{"type":"string"}},{"name":"name","in":"query","required":false,"schema":{"type":"string"},"description":"Nom du fichier ; « file » à défaut."},{"name":"comment","in":"query","required":false,"schema":{"type":"string"},"description":"Id du commentaire visé ; absent, le fichier va sur la carte."}],"requestBody":{"required":true,"content":{"application/octet-stream":{"schema":{"type":"string","format":"binary"}}}},"responses":{"201":{"description":"Le fichier joint","content":{"application/json":{"schema":{"type":"object","properties":{"attachment":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"size":{"type":"integer"},"contentType":{"type":"string"}}}}}}}},"400":{"description":"Erreur","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"insufficient_scope"},"message":{"type":"string"}}}}}}}},"401":{"description":"Erreur","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"insufficient_scope"},"message":{"type":"string"}}}}}}}},"403":{"description":"Erreur","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"insufficient_scope"},"message":{"type":"string"}}}}}}}},"404":{"description":"Erreur","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"insufficient_scope"},"message":{"type":"string"}}}}}}}},"413":{"description":"Erreur","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"insufficient_scope"},"message":{"type":"string"}}}}}}}},"501":{"description":"Erreur","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"insufficient_scope"},"message":{"type":"string"}}}}}}}}}}},"/boards/{boardId}/cards/{cardId}/attachments/{attachmentId}":{"get":{"tags":["Kanban"],"summary":"Relire un fichier joint","description":"Les octets du fichier, adressé par sa carte — celle-ci porte déjà le contrôle d'accès, il n'y a donc pas de route « fichiers » séparée où il faudrait le refaire.\n\nUn fichier attaché à un commentaire se relit exactement comme un fichier de carte : la carte est ce qui les contient tous les deux. Un fichier que cette carte ne porte pas répond `404`, même s'il existe ailleurs.\n\nServi avec `X-Content-Type-Options: nosniff` et une CSP en bac à sable ; tout ce qui n'est pas une image part en téléchargement plutôt que d'être interprété.","operationId":"getAttachment","parameters":[{"name":"boardId","in":"path","required":true,"schema":{"type":"string"}},{"name":"cardId","in":"path","required":true,"schema":{"type":"string"}},{"name":"attachmentId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Les octets du fichier","content":{"application/octet-stream":{"schema":{"type":"string","format":"binary"}}}},"401":{"description":"Erreur","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"insufficient_scope"},"message":{"type":"string"}}}}}}}},"403":{"description":"Erreur","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"insufficient_scope"},"message":{"type":"string"}}}}}}}},"404":{"description":"Erreur","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"insufficient_scope"},"message":{"type":"string"}}}}}}}}}},"delete":{"tags":["Kanban"],"summary":"Détacher un fichier","description":"Retire le fichier de la carte, ou du commentaire désigné par `comment`. Portée `kanban:delete` : rien ne remet la référence en place.\n\nLes octets ne partent pas dans l'instant — le fichier devient orphelin, et la purge quotidienne l'efface. C'est le même chemin qu'une carte supprimée, et la même raison : un fichier peut être cité ailleurs.","operationId":"deleteAttachment","parameters":[{"name":"boardId","in":"path","required":true,"schema":{"type":"string"}},{"name":"cardId","in":"path","required":true,"schema":{"type":"string"}},{"name":"attachmentId","in":"path","required":true,"schema":{"type":"string"}},{"name":"comment","in":"query","required":false,"schema":{"type":"string","maxLength":128},"description":"L'identifiant du commentaire qui porte le fichier. Absent : le fichier de la carte."}],"responses":{"200":{"description":"Le fichier est détaché","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"}}}}}},"400":{"description":"Erreur","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"insufficient_scope"},"message":{"type":"string"}}}}}}}},"401":{"description":"Erreur","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"insufficient_scope"},"message":{"type":"string"}}}}}}}},"403":{"description":"Erreur","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"insufficient_scope"},"message":{"type":"string"}}}}}}}},"404":{"description":"Erreur","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"insufficient_scope"},"message":{"type":"string"}}}}}}}}}}},"/boards/{boardId}/cards/{cardId}/comments":{"post":{"tags":["Kanban"],"summary":"Commenter une carte","description":"Le commentaire est signé du nom de la clé, jamais de celui de la personne qui l'a émise — personne ne doit croire qu'un humain l'a écrit.","operationId":"addComment","parameters":[{"name":"boardId","in":"path","required":true,"schema":{"type":"string"}},{"name":"cardId","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["comment"],"properties":{"comment":{"type":"string","minLength":1,"maxLength":100000}}},"example":{"comment":"Déployé en recette — build #4822."}}}},"responses":{"201":{"description":"L'entrée de journal créée","content":{"application/json":{"schema":{"type":"object","properties":{"event":{"type":"object","properties":{"id":{"type":"string"},"type":{"type":"string","enum":["comment"]},"timestamp":{"type":"string","format":"date-time"},"detail":{"type":"string"},"authorUser":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"}}}}}}}}}},"400":{"description":"Erreur","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"insufficient_scope"},"message":{"type":"string"}}}}}}}},"401":{"description":"Erreur","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"insufficient_scope"},"message":{"type":"string"}}}}}}}},"403":{"description":"Erreur","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"insufficient_scope"},"message":{"type":"string"}}}}}}}},"404":{"description":"Erreur","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"insufficient_scope"},"message":{"type":"string"}}}}}}}}}}}},"components":{"securitySchemes":{"ApiKeyAuth":{"type":"http","scheme":"bearer","description":"Une clé émise dans Réglages → Organisation → Clés API."}}}}