Workflows

Valider et publier

API v115 août 2026·6 min de lecture

Valider un workflow avant de le publier : lire les anomalies bloquantes et non bloquantes, utiliser le contrat de payload, et contrôler la publication et le retrait.

Un workflow ne génère de documents qu'une fois publié. Entre les deux se trouve la validation : un contrôle en lecture seule qui vous dit exactement ce qui ne va pas, avant que vous ne demandiez à l'API de publier et vous fassiez refuser.

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


Valider

POST/api/v1/workflows/templates/:id/validate

Lit l'état actuel du workflow. Il n'écrit jamais rien. L'appeler à répétition, ou pendant qu'un autre éditeur détient le verrou du constructeur, est toujours sans danger.

cURL
curl https://app.doclift.io/api/v1/workflows/templates/100050/validate \
  --request POST \
  --header "X-Api-Key: <your-api-key>" \
  --header "Content-Type: application/json" \
  --data '{}'
200 OK · application/json
{
  "publishable": false,
  "anomalies": [
    {
      "type": "broken_reference",
      "blocking": true,
      "variable": "investor_name",
      "section": { "id": 8823, "title": "Introduction", "kind": "rich_content" }
    },
    {
      "type": "missing_background",
      "blocking": false,
      "variable": null,
      "section": { "id": 8830, "title": "Cover", "kind": "image_with_variable" }
    }
  ],
  "sanitisation": [],
  "stored": {
    "inert_tokens": [],
    "unreachable_images": [],
    "clipped_bands": [],
    "unused_variables": ["country_code"],
    "layout_tables": [],
    "palette": ["#1a1a1a", "#0057ff"],
    "page_breaks": { "declared": [8825], "sections": 6, "content_length": 4210 },
    "typography": {
      "sizes_pt": [12, 14, 18],
      "weights": [400, 700],
      "sizes_off_the_editor_scale": [],
      "weights_no_family_carries": []
    },
    "last_render": { "at": "2026-07-30T09:12:00+02:00", "stale": true }
  }
}

Le corps porte trois lectures indépendantes du même workflow, toutes calculées contre la même arborescence enregistrée que lisent la génération et la publication :

CléCe qu'elle vous dit
publishableSi POST .../publication accepterait le workflow en l'état (exactement la même règle, lue à l'avance).
anomaliesLes problèmes structurels dans l'arborescence enregistrée : références cassées, contradictions, un document vide. Chacune a un type, un indicateur blocking, la variable qu'elle nomme (ou null), et la section qu'elle cible ({id, title, kind}, ou null pour une anomalie qui porte sur tout le document).
sanitisationUne simulation de l'assainisseur de contenu, une entrée par fragment que vous transmettez dans le corps de la requête (rien n'est enregistré).
storedDes constats sur le contenu déjà enregistré : tokens inertes, images inaccessibles, bandes de titre courant tronquées, variables inutilisées, tableaux de mise en page non standard, la palette utilisée, l'usage des sauts de page, et la typographie (tailles et graisses réellement utilisées, et celles qu'aucune famille ne porte).

Pour simuler un contenu avant de l'enregistrer, envoyez des fragments :

cURL
curl https://app.doclift.io/api/v1/workflows/templates/100050/validate \
  --request POST \
  --header "X-Api-Key: <your-api-key>" \
  --header "Content-Type: application/json" \
  --data '{
    "content": ["<p onclick=\"x()\">Hello <variable>investor_name</variable></p>"],
    "scope": "content"
  }'
200 OK · application/json (sanitisation entry)
{
  "scope": "content",
  "given": "<p onclick=\"x()\">Hello <variable>investor_name</variable></p>",
  "sanitised": "<p>Hello <variable>investor_name</variable></p>",
  "removed_tags": [],
  "removed_attributes": ["onclick"],
  "unknown_references": [],
  "inert_tokens": [],
  "unreachable_images": [],
  "ejected_from_paragraph": [],
  "removed_css_declarations": [],
  "clean": false
}

clean vaut false dès que l'un de removed_tags, removed_attributes, inert_tokens, unreachable_images, ejected_from_paragraph, ou removed_css_declarations est non vide : c'est le seul endroit qui vous dit qu'un enregistrement perdrait quelque chose, avant même de l'enregistrer. Voir Les échecs qui répondent 200 pour ce que chacun de ces champs attrape réellement.

content, s'il est présent, doit être un tableau de strings ; scope (content ou running_title) choisit quel assainisseur s'exécute. Chacune de ces deux erreurs répond 422 avant que quoi que ce soit d'autre ne s'exécute :

ÉchecStatut
content422
scope422

Types d'anomalies

Six types bloquent la publication ; trois sont non bloquants et ne le font jamais.

Type bloquantSignification
broken_referenceUn token, une condition, un placement, une bande, un repeat_over, ou un data-loop nomme une variable ou un champ de ligne qui n'existe pas.
out_of_loop_referenceUn collection.field qualifié est cité là où aucune boucle de lignes ne met cette collection dans le périmètre.
nested_row_loopUn data-loop se trouve à l'intérieur d'un nœud qui répète déjà, ou à l'intérieur de la propre ligne d'une autre boucle.
collection_comparisonLe nom propre d'une variable collection est comparé avec un opérateur autre que present/blank.
contradictionUne condition qu'aucun payload ne peut jamais satisfaire (par exemple la même variable devant valoir deux choses différentes à la fois). Signalée une seule fois, sur l'ancêtre le plus haut où la contradiction tient.
empty_documentL'arborescence n'a aucun nœud rich_content ni image_with_variable : seulement des groupes, ou rien. Ne cible aucune section.
Type non bloquantSignification
missing_backgroundUne section image_with_variable n'a pas encore d'image définie.
empty_membershipUne règle in/not_in a une liste values vide : elle ne décide rien à elle seule.
optional_conditioningUne condition repose sur une variable qui n'est pas required, si bien qu'un oubli d'un auteur peut silencieusement raccourcir le document.

Le contrat de payload

GET/api/v1/workflows/templates/:id/payload_contract

Lecture seule, aucun corps. Renvoie exactement les variables dont un payload de génération pour ce workflow a besoin, plus un exemple prêt à envoyer.

cURL
curl https://app.doclift.io/api/v1/workflows/templates/100050/payload_contract \
  --header "X-Api-Key: <your-api-key>"
200 OK · application/json
{
  "variables": [
    {
      "name": "investor_name",
      "description": "Full legal name",
      "required": true,
      "field_type": "text",
      "allowed_values": [],
      "seed_value": "Jane Doe",
      "fields": null
    }
  ],
  "required": ["investor_name"],
  "collections": ["investments"],
  "limits": { "collection_rows": 200 },
  "example": {
    "document_request": {
      "document_generations": [
        {
          "template_id": 100050,
          "variables": {
            "investor_name": "Jane Doe",
            "investments": [{ "product": "SCPI", "amount": "10 000 €" }]
          },
          "tag": ""
        }
      ],
      "tag": ""
    }
  }
}

example est un payload réel et satisfaisant : chaque variable déclarée présente, initialisée depuis son seed_value quand elle en a une, chaque collection rendue comme un tableau de lignes plates utilisant ses noms de champs déclarés. Copiez-le dans POST /api/v1/document_requests et attendez-vous à ce qu'il réussisse, sans avoir à deviner les formes. Voir Modèles workflow pour la façon dont cet endpoint impose le contrat de variables requises que cet exemple satisfait déjà.


Publier et dépublier

POST/api/v1/workflows/templates/:id/publication
cURL
curl https://app.doclift.io/api/v1/workflows/templates/100050/publication \
  --request POST \
  --header "X-Api-Key: <your-api-key>"

Exécute le même contrôle d'intégrité que validate, contre la même arborescence enregistrée. Si une anomalie bloquante subsiste, la publication est refusée et le corps de la réponse les liste toutes, pas seulement la première :

422 Unprocessable Content · application/json
{
  "error": "<message>",
  "anomalies": [
    { "type": "broken_reference", "section_id": 8823, "variable": "investor_name" }
  ]
}

Sinon :

200 OK · application/json
{ "published": true }

Les anomalies non bloquantes ne bloquent jamais cet appel : un workflow avec un missing_background ou un empty_membership se publie sans problème. 409 si le verrou d'édition du constructeur est détenu.

DELETE/api/v1/workflows/templates/:id/publication
cURL
curl https://app.doclift.io/api/v1/workflows/templates/100050/publication \
  --request DELETE \
  --header "X-Api-Key: <your-api-key>"

204 No Content. Le retrait n'est jamais soumis à l'intégrité. Un workflow dont les références se sont cassées après sa publication peut tout de même être retiré, volontairement : c'est exactement celui qui doit pouvoir l'être. Toujours 409 si le verrou d'édition est détenu.

Un workflow publié est ce qui le rend éligible à la génération de documents : seul un workflow publié peut être ciblé par POST /api/v1/document_requests.


La boucle

  1. 1POST .../validate : lisez publishable et anomalies.
  2. 2Corrigez chaque anomalie bloquante que nomme la réponse. Les non bloquantes sont à vous d'agir ou de laisser.
  3. 3POST .../validate de nouveau pour confirmer publishable: true.
  4. 4POST .../publication. Un 422 ici signifie que l'étape 3 a manqué quelque chose. Le corps de la réponse liste exactement quoi.

Les anomalies ne bloquent jamais l'écriture d'une section ou d'une variable ; elles ne bloquent que la publication. Vous pouvez enregistrer une référence cassée, continuer d'éditer, et ne le découvrir qu'au moment de validate ou de publication. Voir Les échecs qui répondent 200 pour les échecs qui n'atteignent même pas ce stade.