Workflows

Les échecs qui répondent 200

API v115 août 2026·9 min de lecture

Comment repérer et éviter les échecs de l'API workflow qui ne renvoient jamais d'erreur : contenu assaini, clés de placement supprimées, surcharges de thème figées, et polices remplacées silencieusement.

La majeure partie de cette API refuse ce qu'elle ne peut pas faire. Une poignée d'écritures, à l'inverse, acceptent la requête, font discrètement moins que ce qui a été demandé, et répondent 200 ou 201 comme si de rien n'était. Aucune de ces situations n'est un bug (chacune est un choix délibéré et documenté), mais chacune ne peut être repérée qu'en relisant un champ précis, jamais en lisant le code de statut.

Les workflows sont en bêta privée, disponible sur demande à [email protected] ; voir Workflows.


Repérez votre symptôme

SymptômeCauseOù ça apparaît
Le contenu écrit disparaît après rechargementL'assainisseur le retire à l'enregistrementsanitisation/stored de validate, removed_tags/removed_attributes
Le libellé d'un placement n'apparaît jamaisClé de placement mal orthographiéeRien (la clé a disparu de la section enregistrée)
Un token affiche son nom de variable, pas sa valeurToken <variable> inerteinert_tokens dans sanitisation/stored
Une image s'affiche comme un cadre videImage citée par une URL externeunreachable_images dans sanitisation/stored
Un paragraphe devient deux ou trois dans la sortieÉlément de bloc éjecté d'un <p>ejected_from_paragraph de sanitisation
Une règle CSS posée n'a aucun effet visibleDéclaration supprimée de styleremoved_css_declarations dans sanitisation/stored
Un en-tête ou pied de page perd ses premières lignesBande tronquée à la margeclipped_bands de stored, validate seulement
Modifier le thème ne change plus une sectionValeur en ligne figée face au thèmeRien (relire le contenu propre de la section)
PATCHValeur non interprétable abandonnéeRelire le thème après l'avoir écrit
Le texte s'affiche dans une graisse inattendueAucune famille ne porte cette graisseworkflow_selfcheck seulement (MCP), pas un champ REST

1. Le contenu est assaini à l'enregistrement

Vous écrivez : un fragment de contenu riche ou de titre courant contenant une balise <script>, un attribut onclick, ou toute autre chose hors de la liste blanche de l'assainisseur.

Ce qui se passe : le POST/PATCH sur la section répond 200/201 avec le fragment déjà retiré. La balise ou l'attribut interdit a disparu de ce qui est enregistré ; rien dans la réponse ne le signale.

Comment le repérer : simulez le fragment exact via POST .../validate avec content: ["<fragment>"] avant de l'enregistrer. Voir Valider. Les endpoints de création/mise à jour de section eux-mêmes ne donnent aucun signal dans un sens ou dans l'autre.

Comment l'éviter : traitez sanitisation.clean de validate comme le portail avant toute écriture qui porte du HTML nouveau, pas le code de statut de l'endpoint de section.


2. Une clé de placement mal orthographiée est supprimée

Vous écrivez : un objet de placement sur une section image_with_variable avec une clé qui n'est pas exactement l'une de id, kind, variable, value, x, y, width, height, align, font_size, color, font_family, font_weight.

Ce qui se passe : la clé inconnue est retirée silencieusement. L'écriture répond quand même 200/201 ; le libellé ne s'affiche simplement jamais à la position qu'il aurait dû occuper.

Comment le repérer : rien ne le signale après coup. La clé a disparu, pas été signalée. Comparez ce que vous envoyez avec capabilities.placement.keys_by_kind/.keys avant de l'envoyer.

Comment l'éviter : orthographiez les clés de placement exactement comme les liste l'endpoint capabilities en direct ; ne codez jamais en dur une liste mémorisée.


3. Un token inerte affiche son propre nom

Vous écrivez : un élément <variable> avec la bonne balise mais sans la classe editor-parsed (ou toute autre classe que requiert capabilities.content.tokens).

Ce qui se passe : il survit à l'assainissement et passe le contrôle d'intégrité (ce n'est pas un broken_reference), mais au moment du rendu il affiche son propre nom de variable au lieu de sa valeur.

Comment le repérer : sanitisation/stored de validate le signalent comme inert_tokens. Rien d'autre dans l'API ne le signale.

Comment l'éviter : écrivez toujours le HTML de token exact que donne capabilities.content.tokens pour le type de champ, par exemple pour un texte : <variable class="editor-text-variable non-editable-content editor-parsed">field_name</variable>.


4. Une image externe affiche un cadre vide

Vous écrivez : un <img> citant une URL absolue ou externe au lieu d'une URL renvoyée par POST .../images ou .../background.

Ce qui se passe : le moteur de rendu n'émet aucune requête réseau en produisant le PDF, donc l'image s'affiche comme rien : un cadre vide, pas une erreur.

Comment le repérer : sanitisation/stored de validate le signalent comme unreachable_images.

Comment l'éviter : importez chaque image via les endpoints images ou arrière-plan d'abord, et citez uniquement le token/l'URL qu'ils renvoient.


5. Un élément de bloc est éjecté d'un paragraphe

Vous écrivez : un div, un table, ou un autre <p> imbriqué dans un <p>.

Ce qui se passe : l'analyseur HTML5 le déplace hors du paragraphe, le scindant en jusqu'à trois éléments frères. Rien n'est retiré, donc même un comptage de balises avant/après reste propre en apparence.

Comment le repérer : sanitisation de validate le signale comme ejected_from_paragraph.

Comment l'éviter : n'imbriquez jamais un élément de niveau bloc dans un paragraphe.


6. Une déclaration CSS est supprimée de style

Vous écrivez : un attribut style="..." où une déclaration n'est pas sur la liste des propriétés autorisées par l'assainisseur.

Ce qui se passe : l'attribut lui-même survit (donc removed_attributes reste vide), mais cette déclaration précise à l'intérieur est retirée. Le fragment « paraît propre » au seul comptage des attributs.

Comment le repérer : sanitisation/stored de validate le signalent séparément, comme removed_css_declarations.

Comment l'éviter : vérifiez removed_css_declarations spécifiquement ; ne déduisez pas la sécurité du seul fait que removed_attributes est vide.


7. Une bande de titre courant est tronquée

Vous écrivez : une bande d'en-tête ou de pied de page dont le contenu rendu est plus long que la marge réservée sur ce côté.

Ce qui se passe : le moteur de rendu la tronque sans erreur nulle part. Il tronque depuis les lignes du début, pas depuis la fin.

Comment le repérer : seul stored.clipped_bands de validate, avec un nombre de lignes estimé. L'aperçu en simulation de sanitisation ne vérifie pas la longueur du tout ; ceci n'apparaît qu'une fois la bande réellement enregistrée.

Comment l'éviter : gardez les bandes assez courtes pour les marges de la page ; vérifiez stored.clipped_bands après tout changement à un en-tête/pied de page ou aux marges de la page elles-mêmes.


8. Une valeur en ligne se fige par rapport au thème

Vous écrivez : une propriété de thème directement sur une section, avec une valeur qui correspond déjà à ce que le thème du modèle déclare actuellement pour ce type de bloc.

Ce qui se passe : le rendu est identique aujourd'hui. La section arrête de suivre le thème à partir de ce moment. Une modification ultérieure du thème (une nouvelle police, une nouvelle taille) n'atteindra plus cette section, parce qu'elle porte désormais sa propre valeur figée.

Comment le repérer : rien ne le signale : aucune anomalie, aucun champ stored. C'est une discipline de modélisation, pas une règle vérifiée.

Comment l'éviter : réglez le thème du document d'abord, avant d'écrire le contenu des sections, et ne déclarez une propriété en ligne que lorsque vous voulez délibérément surcharger le thème pour cette seule section.


9. Une écriture de thème non interprétable est abandonnée

Vous écrivez : PATCH .../theme avec une valeur qui ne s'interprète pas selon les règles de cette propriété : une famille de police que le moteur de rendu ne provisionne pas, un line_height portant une unité (par exemple "1.45em"), un font_size dans une unité autre que pt, une taille ou un interlignage hors de ses bornes, un color non hexadécimal, un bloc ou une propriété inconnus, ou n'importe quel font_weight (délibérément jamais une propriété modifiable).

Ce qui se passe : la valeur fautive est abandonnée (propriété par propriété, pas requête par requête), et l'écriture répond quand même 200. Un nom de famille connu qui se résout vers un autre est en revanche réécrit silencieusement (par exemple "Arial" est enregistré sous son substitut métrique, "Liberation Sans").

Comment le repérer : la réponse est toujours le thème enregistré et renormalisé, relu après l'écriture, jamais un écho de ce que vous avez envoyé. Lire cette réponse est le seul moyen de voir un abandon se produire.

Comment l'éviter : après chaque PATCH .../theme, comparez la réponse à ce que vous avez envoyé, propriété par propriété, plutôt que de faire confiance au 200.

ValeurBornes/unité
font_size6-96, unité pt
line_height1.0-2.5, sans unité
colorhex 3 ou 6 chiffres
font_weightjamais une propriété modifiable, toujours abandonnée

Un cas corrigé, pour information

Un payload dont les conditions excluaient toutes les sections, ou n'en laissait subsister que celles qui ne rendaient aucun contenu visible, était autrefois livré comme un document vide avec un statut de succès. Ceci est corrigé : les deux cas sont aujourd'hui refusés avec un 422 (excluded_sections/printing_nothing) sur POST /api/v1/document_requests. Voir Modèles workflow. Ce point n'est listé ici que parce qu'un code d'intégration ancien, ou une documentation ancienne, pourrait encore supposer ce comportement de page blanche ; rien à ce sujet n'est un piège actif.


10. Une graisse qu'aucune famille ne porte

Vous écrivez : une graisse dans le contenu ou dans le thème qu'aucune famille de police provisionnée ne porte réellement.

Ce qui se passe : aucune écriture ne la refuse. Le moteur de rendu arrondit ou synthétise la graisse la plus proche qu'il possède et l'affiche à la place : une épaisseur que personne n'a demandée.

Comment le repérer : validate ne le vérifie pas. Seule la couche MCP, via workflow_selfcheck, le révèle, comme weights_no_family_carries, calculé à partir de stored.typography contre capabilities.authoring.fonts.

Comment l'éviter : choisissez les graisses parmi les familles déclarées de capabilities.authoring.fonts, pas parmi ce qu'un menu d'éditeur propose par hasard.