Ce qu'il ne fait pas
Les manques, les pièges et les refus tranchés de Spunto CMS, énoncés ici pour que votre premier appel à l'API REST n'ait pas à vous les apprendre.
Une page qui ne liste que des fonctionnalités coûte plus cher que pas de page du
tout : elle fait perdre son temps à qui la croit, et le premier curl la
corrige de toute façon.
Voici donc l'autre liste. Elle a trois parties, et elles ne sont pas de même nature.
Refus tranchés
Ce sont des décisions, pas des manques. Elles ne changeront pas.
Pas de GraphQL. REST uniquement, maintenant comme plus tard.
Pas d'URL de fichier partageable. Ni bucket public, ni lien signé. Pourquoi.
Pas de tri ni de groupement sur une propriété tableau. multi_select et
multi_person refusent les deux, avec un 400 qui le dit, plutôt que de trier
selon l'ordre des documents d'un tableau JSON.
Pourquoi.
Pas de pagination par offset. Des curseurs keyset uniquement, partout. Pourquoi.
Pas de 403 pour un espace de travail dont vous n'êtes pas membre. Vous recevez un 404, et l'API ne vous dira pas lequel des deux cas il recouvre. Pourquoi.
Pas de HTML dans le corps des cartes. Le format en blocs ne peut pas l'exprimer. Pourquoi.
Pas encore construit
Ce sont des absences, et elles peuvent changer. Elles sont listées parce que bâtir sur leur absence, ou sur leur arrivée imminente, est un choix qu'il vaut mieux faire en connaissance de cause.
Publier, c'est une carte, une version, maintenant. Une carte a un brouillon et au plus une version publiée. Il n'y a ni publication programmée, ni historique de versions vers lequel revenir, ni route qui publie en masse : un script publie carte par carte.
Le publié est un choix de lecture, pas un droit. version: "published",
c'est le lecteur qui le demande. Une clé qui peut lire un espace peut aussi en
lire les brouillons — gardez-la côté serveur, comme de toute façon.
Tout ne lit pas la version publiée. Les requêtes de vue, les lectures
groupées, les compteurs de groupe et GET …/items/{itemId} prennent une
version. La recherche, les sous-tâches, le journal d'activité et l'accueil
montrent toujours les brouillons.
Pas de recherche plein texte dans le corps des cartes. La recherche ne porte que sur les titres des cartes et les noms des listes. Il n'existe aucun index sur le corps, et les vues ne peuvent pas non plus filtrer ni trier dessus.
Pas de rétroliens, et un signet ne se rafraîchit pas. Une mention suit la carte qu'elle cite, mais rien ne liste les cartes qui mentionnent une carte donnée. Le titre d'un signet est lu une fois, au moment du coller ; il ne change pas quand la page change. L'import Notion ne convertit pas les mentions de pages Notion. Les embeds sont une liste blanche — un autre fournisseur est un changement de code, pas un réglage.
Pas de calendrier dans l'app iOS. L'application web affiche les vues calendrier (mois, semaine, chronologie) ; l'app iOS montre une vue calendrier en tableau.
Les limites du calendrier. Les cases d'un calendrier et les barres d'une
chronologie viennent de propriétés date : pas d'heure, pas de récurrence, et
pas de durée sur plusieurs jours dans la grille du mois — les durées sont
l'affaire de la chronologie, entre deux propriétés. Les jours se lisent en UTC,
comme toutes les dates que l'app affiche.
Pas de sprints automatiques. Un sprint se crée par quelqu'un — sur le bandeau, Terminer le sprint propose de créer le suivant —, jamais parce qu'une date est passée : il n'y a pas de planificateur. Sprints.
Pas de création automatique d'index. POST …/index-hints vous dit quels
index une vue réclame, et s'arrête là — la tâche qui les appliquerait n'existe
pas. Détails.
Pas d'édition en masse au-delà de 5 000 cartes. L'API refuse plutôt que de
prétendre être atomique ou de mettre du travail en file en silence. Entre 200 et
5 000, vous devez passer expectedCount.
Détails.
Pas de duplication d'un sous-arbre au-delà de 200 cartes. La duplication est un geste synchrone et atomique qui vous rend la copie ; au-delà, ce serait un import, et elle refuse plutôt que de faire semblant. Détails.
Pas de mode hors ligne. L'application web s'installe comme une application autonome (Chrome sur Android et sur ordinateur, « Sur l'écran d'accueil » sur iOS), mais rien de ce que vous y lisez n'est conservé sur l'appareil : sans réseau, chaque écran affiche la même page « pas de connexion ». C'est voulu — une page en cache montrerait une liste périmée, ou celle de la personne connectée avant vous sur le même appareil.
Pas de revue des fiches de la marketplace. N'importe quel compte peut publier une app dans la marketplace ; le badge Vérifiée, posé par l'exploitant de l'instance, est le seul signal de confiance. Une fiche non vérifiée le dit. Regardez où mène le bouton d'installation avant d'accorder quoi que ce soit.
Pas de limite de débit. Aucune aujourd'hui. C'est la description de l'implémentation actuelle, pas une garantie — ne construisez rien qui dépende de leur absence.
Pas d'email sortant. Ce déploiement n'en envoie aucun. Les invitations sont des jetons que quelqu'un transmet ; rien ne vérifie qu'une adresse appartient à la personne qui l'a tapée. Ce dernier point compte si vous raisonnez sur la sécurité des comptes : c'est précisément pour cela qu'une identité externe n'est jamais rattachée à un compte sur la foi d'une adresse email identique.
Pièges
Ce sont de vrais comportements, qui surprennent. Ils sont documentés plutôt que corrigés, parce que dans chaque cas l'alternative était pire.
Un select se trie alphabétiquement, pas dans l'ordre de ses options. Une
vue « par priorité » rend critical, high, low, normal. Encodez l'ordre dans
les valeurs s'il vous le faut.
Détails.
L'ordre manuel est global à la liste, pas propre à chaque colonne du kanban. Déposer une carte en tête d'une colonne en fait aussi la première ligne de la vue tableau. Détails.
display se remplace en bloc. Enregistrer les colonnes d'un tableau sans
renvoyer groupOrder efface l'ordre des colonnes du kanban.
config se remplace en bloc, lui aussi. Enregistrer les options d'une
propriété sans renvoyer optionColors efface les couleurs. Pour ajouter ou
repeindre une seule option, POST …/fields/{fieldId}/options fusionne au lieu
de remplacer.
Les propriétés sont indexées par key, les vues référencent fieldId. Deux
noms pour le même champ, employés à des endroits différents : les confondre vous
vaut un 400 « propriété inconnue ».
Détails.
Le type sous lequel un fichier est servi n'est pas celui sous lequel il a été
envoyé. Un SVG revient en téléchargement application/octet-stream, avec
image: false. Pourquoi.
icon: null retire l'icône ; omettre icon n'y touche pas. C'est la même
distinction que dans le reste de PATCH, et elle mérite d'être connue avant de
construire un client qui renvoie toute la carte à chaque frappe.
coverFileId est un identifiant de fichier, pas une URL, et seule une image
passe. Envoyez d'abord le fichier (POST …/files), puis écrivez son
identifiant. Un fichier d'un autre espace de travail, ou qui n'est pas servi
comme image (un SVG, un PDF), vaut un 400. null — ou "" — retire la
couverture, l'omettre n'y touche pas, et la retirer ne supprime pas le fichier.
Fichiers.
Un burndown oublie ce que le journal a oublié ; la vélocité, non. Le
burndown se relit dans le journal, qu'une instance purge par mois : un vieux
sprint montre des jours inconnus (historyFrom). La vélocité lit ce que chaque
sprint contient aujourd'hui — donc une carte rouverte plus tard réécrit la
vélocité du sprint qui l'avait livrée. Sprints.
createdBy vient de l'appelant, jamais du corps de la requête. En envoyer
un n'est pas une erreur — les clés inconnues sont ignorées, vous obtenez donc un
201 et un auteur qui est vous. Il vaut null pour les imports, les données
d'amorçage et tout ce qui a été créé avant l'existence de la colonne.
Quand cette page se trompe
Elle est écrite à la main, et la référence de l'API est générée depuis le code. S'ils se contredisent, croyez la référence — et l'écart est un bug de cette page.