Workflows

Modèles, publication et document

API v115 août 2026·12 min de lecture

Capabilities, la ressource modèle de workflow, validate, le contrat de payload, publier/retirer, et l'export/import du document complet.

Chaque endpoint ci-dessous est sous /api/v1/workflows. Lisez Workflows d'abord pour le modèle objet et l'ordre dans lequel ces appels sont censés s'enchaîner. Cette page et ses deux voisines forment la référence champ par champ : celle-ci couvre capabilities, la ressource modèle, la publication, et l'export/import du document complet ; Sections et thème couvre l'arborescence des sections, les arrière-plans et le thème ; Variables et données couvre les variables, les jeux de données de prévisualisation et les images de contenu.

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

Comportement commun

Chaque appel ci-dessous porte le même en-tête X-Api-Key que le reste de l'API (voir Bases de l'API), plus trois règles propres à cet espace :

  • L'organisation doit être activée pour les workflows. Sinon, chaque appel (lectures comprises) répond 403 {"error": "<message>"}. C'est un indicateur propre à l'organisation activé par Doclift, pas un palier de forfait qu'une clé peut débloquer elle-même.
  • Chaque :id est cadré sur les modèles de workflow non archivés de l'organisation appelante. L'id d'un modèle personnalisé ou d'un formulaire à remplir, l'id d'un workflow archivé, ou l'id d'un workflow d'une autre organisation sont tous indiscernables d'un id qui n'existe pas : 404 {"error": "<message>"}.
  • Une écriture est refusée tant que le constructeur du dashboard détient le verrou d'édition. C'est un verrou de session de trois minutes pris par un humain en train d'éditer le même workflow dans le navigateur : 409 {"error": "<message>"}. Les lectures ne sont jamais bloquées par ce verrou ; une fois le verrou périmé (plus de trois minutes), l'écriture repasse sans que vous n'ayez rien à faire.

Un corps de requête auquel manque sa clé racine attendue (template, section, variable, dataset, image, background, theme, ou document, selon l'endpoint) répond 400 {"error": "<message>"} avant même que les règles ci-dessus soient vérifiées : le même schéma que le reste de l'API, voir Codes de réponse.


Capabilities

Récupérer les capabilities

GET/api/v1/workflows/capabilities
cURL
curl https://app.doclift.io/api/v1/workflows/capabilities \
  --header "X-Api-Key: <your-api-key>"
200 OK · application/json
{
  "contract_version": 1,
  "section": {
    "kinds": ["group", "rich_content", "image_with_variable"],
    "carries": {
      "group": { "children": true, "content": false, "placements": false, "image": false, "running_titles": "root only" },
      "rich_content": { "children": false, "content": true, "placements": false, "image": false, "running_titles": false },
      "image_with_variable": { "children": false, "content": false, "placements": true, "image": true, "running_titles": false }
    }
  },
  "condition": {
    "operators": {
      "single_value": ["eq", "not_eq", "contains"],
      "multi_value": ["in", "not_in"],
      "valueless": ["blank", "present"]
    },
    "collection_operators": ["present", "blank"]
  },
  "placement": {
    "keys_by_kind": { "text": ["variable"], "checkbox": ["variable"], "static_text": ["value"] },
    "unknown_keys": "dropped without error: the write answers 200"
  },
  "variable": {
    "field_types": ["text", "checkbox", "radio", "select", "collection"],
    "collection": { "fields": { "keys": ["name", "description", "required", "seed_value"] } }
  },
  "theme": {
    "blocks": ["paragraph", "h1", "h2", "h3"],
    "properties": ["font_family", "font_size", "color", "line_height"],
    "font_size": { "min": 6, "max": 96, "unit": "pt" },
    "line_height": { "min": 1.0, "max": 2.5, "unit": null }
  },
  "page": {
    "size_mm": { "portrait": [210, 297], "landscape": [297, 210] },
    "margin_sides": ["top", "right", "bottom", "left"]
  },
  "content": {
    "allowed_tags": ["..."],
    "allowed_attributes": ["..."],
    "tokens": {
      "text": "<variable class=\"editor-text-variable non-editable-content editor-parsed\">investor_name</variable>"
    }
  },
  "authoring": {
    "fonts": { "faces": ["..."], "substitutes": {} },
    "font_sizes_pt": [8, 10, 12, 14, 16, 18, 24, 36, 48],
    "table": { "layout_row_class": "wf-layout-row" }
  },
  "anomalies": [{ "type": "broken_reference", "blocking": true }],
  "limits": {
    "sections": 200,
    "image_bytes": 10485760,
    "document_length": 800000,
    "tree_depth": 3,
    "numbering_start_max": 100,
    "dataset_name_length": 60
  }
}

Aucun paramètre, 200 toujours une fois authentifié et l'organisation activée. Rien dans cette réponse n'est une valeur produit figée à l'exception des clés à forme constante (section.kinds, condition.operators, placement.keys_by_kind, page.size_mm, variable.field_types, theme.blocks/properties, anomalies). limits.sections, limits.image_bytes et limits.document_length sont les propres plafonds de cette organisation, et content.allowed_tags/ allowed_attributes ainsi que authoring.fonts sont lus depuis l'application en cours d'exécution plutôt que figés au démarrage. Voir Capabilities fait foi, ce n'est pas une copie.

content.tokens.text est le HTML exact et littéral qu'un token de variable texte doit porter à l'intérieur du contenu d'une section. Tout ce qui s'en approche sans être identique (une classe manquante, une balise erronée) est un token inerte qui survit à l'assainissement et affiche son propre nom de variable. Voir Pièges pour la liste complète des écritures qui font silencieusement moins que ce qui a été demandé.


Modèles

Un workflow est une ressource modèle avec category: "workflow", forcée côté serveur : le POST /api/v1/templates classique ne peut pas en créer un, et aucun des endpoints d'écriture classiques ne le touche jamais (voir Les écritures n'atteignent que les modèles personnalisés). Il ne porte jamais le champ historique content.

Lister les modèles

GET/api/v1/workflows/templates
cURL
curl https://app.doclift.io/api/v1/workflows/templates \
  --header "X-Api-Key: <your-api-key>"
200 OK · application/json
[
  {
    "id": 100050,
    "title": "Subscription bulletin",
    "description": "Investor onboarding document",
    "published": false,
    "orientation": "portrait",
    "created_at": "2026-01-10 09:00:00 +0100",
    "updated_at": "2026-02-03 11:15:00 +0100"
  }
]

Les modèles de workflow non archivés de l'organisation appelante, les plus récemment mis à jour en premier, paginés à 30 par page (mêmes en-têtes que Pagination).

StatutCorpsQuand
200 tableausuccès
403 {"error": "..."}clé d'API manquante/invalide, ou workflows non activés pour cette organisation

Afficher un modèle

GET/api/v1/workflows/templates/:id
cURL
curl https://app.doclift.io/api/v1/workflows/templates/100050 \
  --header "X-Api-Key: <your-api-key>"
200 OK · application/json
{
  "id": 100050,
  "title": "Subscription bulletin",
  "description": "Investor onboarding document",
  "published": false,
  "orientation": "portrait",
  "margin_top": 10,
  "margin_right": 10,
  "margin_bottom": 10,
  "margin_left": 10,
  "created_at": "2026-01-10 09:00:00 +0100",
  "updated_at": "2026-02-03 11:15:00 +0100",
  "sections_count": 14,
  "variables_count": 6,
  "being_edited": false
}

being_edited vaut true tant que le constructeur du dashboard détient le verrou d'édition : la seule raison pour laquelle une écriture sur ce workflow peut être refusée pour une cause que le payload lui-même ne laisse rien deviner. Vérifiez-le avant de retenter un 409.

StatutCorpsQuand
200 modèlesuccès
404 {"error": "..."}pas un workflow, archivé, ou d'une autre organisation
403 {"error": "..."}voir Comportement commun

Créer un modèle

POST/api/v1/workflows/templates
cURL
curl https://app.doclift.io/api/v1/workflows/templates \
  --request POST \
  --header "X-Api-Key: <your-api-key>" \
  --header "Content-Type: application/json" \
  --data '{
    "template": {
      "title": "Subscription bulletin",
      "description": "Investor onboarding document",
      "orientation": "portrait",
      "margin_top": 10,
      "margin_right": 10,
      "margin_bottom": 10,
      "margin_left": 10
    }
  }'
201 Created · application/json
{
  "id": 100050,
  "title": "Subscription bulletin",
  "description": "Investor onboarding document",
  "published": false,
  "orientation": "portrait",
  "margin_top": 10,
  "margin_right": 10,
  "margin_bottom": 10,
  "margin_left": 10,
  "created_at": "2026-01-10 09:00:00 +0100",
  "updated_at": "2026-01-10 09:00:00 +0100",
  "sections_count": 0,
  "variables_count": 0,
  "being_edited": false
}

category est toujours forcé à "workflow", quoi que vous envoyiez.

ChampTypeRequisNotes
titlestringoui
descriptionstringoui
orientationstringnonportrait ou landscape, défaut portrait
margin_topintegernonchacun >= 5, défaut 10
StatutCorpsQuand
201 modèlesuccès
422 {"errors": ["..."]}titre/description vide, ou une marge sous 5
400 {"error": "..."}corps sans la clé racine template
403 {"error": "..."}voir Comportement commun

Mettre à jour un modèle

PATCH/api/v1/workflows/templates/:id

Mêmes champs qu'à la création. 200 avec la même forme en cas de succès ; le titre enregistré reste inchangé sur un 422.

StatutCorpsQuand
200 modèlesuccès
422 {"errors": ["..."]}mêmes validations qu'à la création
404 {"error": "..."}pas un workflow, archivé, ou d'une autre organisation
409 {"error": "..."}verrou d'édition détenu
403 {"error": "..."}voir Comportement commun

Supprimer un modèle

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

204 No Content. Ceci archive le modèle plutôt que de le supprimer. Les générations déjà produites continuent de pointer vers le modèle qui les a créées.

StatutCorpsQuand
204 aucunsuccès
404 {"error": "..."}pas un workflow, déjà archivé, ou d'une autre organisation
409 {"error": "..."}verrou d'édition détenu
403 {"error": "..."}voir Comportement commun

Valider un workflow

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

Un POST qui n'écrit rien : il n'est pas soumis au verrou d'édition, et répond même pendant que le constructeur le détient. Le corps est entièrement optionnel : content (tableau de fragments HTML sous forme de chaînes) et scope ("content" ou "running_title") exécutent une simulation de l'assainisseur sur des fragments que vous n'avez pas encore enregistrés. anomalies et stored décrivent ce qui est déjà enregistré sur ce workflow, indépendamment de tout content que vous transmettez. Voir Valider pour la forme complète de la réponse, les tableaux des types d'anomalies bloquantes/non bloquantes, et le déroulé de la simulation (c'est le même endpoint, documenté là en entier pour ne pas être répété sur chaque page qui le mentionne).

ChampTypeRequisNotes
contenttableau de stringsnonfragments de simulation ; 422 si présent mais pas un tableau de strings
scopestringnoncontent ou running_title ; 422 pour toute autre valeur
StatutCorpsQuand
200 voir Validertoujours, dès lors que la requête elle-même est bien formée
422 {"errors": ["..."]}content présent mais pas un tableau de strings, ou scope hors de ses deux valeurs
404 {"error": "..."}pas un workflow, archivé, ou d'une autre organisation
403 {"error": "..."}voir Comportement commun

Récupérer le contrat de payload

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

Lecture seule, aucun paramètre. Renvoie exactement les variables dont un payload de génération pour ce workflow a besoin, plus un exemple prêt à envoyer. Voir Le contrat de payload pour la forme complète de la réponse et la façon d'utiliser example.document_request.

StatutCorpsQuand
200 voir Le contrat de payloadsuccès
404 {"error": "..."}pas un workflow, archivé, ou d'une autre organisation
403 {"error": "..."}voir Comportement commun

Publication

Une ressource singleton sous le modèle : published est un simple champ booléen du modèle, mais seule cette route est autorisée à le faire basculer pour un workflow. Voir Publier et dépublier pour le déroulé complet de la requête/réponse et la boucle qui vous y mène (validate → corriger → validate de nouveau → publication).

Publier un workflow

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

Exécute le même contrôle d'intégrité que celui que lit validate. Si une anomalie bloquante subsiste, l'écriture est refusée et rien ne change ; chaque anomalie bloquante est listée, pas seulement la première. Les anomalies non bloquantes (missing_background, empty_membership, optional_conditioning) ne bloquent jamais cet appel : un workflow avec un arrière-plan non défini se publie sans problème.

StatutCorpsQuand
200 {"published": true}aucune anomalie bloquante ne subsiste
422 {"error": "<message>", "anomalies": [...]}une anomalie bloquante subsiste ; rien n'est modifié
404 {"error": "..."}pas un workflow, archivé, ou d'une autre organisation
409 {"error": "..."}verrou d'édition détenu
403 {"error": "..."}voir Comportement commun

Retirer un workflow

DELETE/api/v1/workflows/templates/:id/publication

204 No Content. Jamais soumis à l'intégrité : un workflow dont les références se sont cassées après sa publication est exactement celui qui doit pouvoir être retiré sans d'abord devoir repasser un contrôle qu'il ne réussirait peut-être plus.

Publier un workflow est ce qui le rend visible depuis GET /api/v1/templates et générable depuis POST /api/v1/document_requests ; voir Types de modèles.

StatutCorpsQuand
204 aucunsuccès, qu'il passe ou non actuellement le contrôle d'intégrité
404 {"error": "..."}pas un workflow, archivé, ou d'une autre organisation
409 {"error": "..."}verrou d'édition détenu
403 {"error": "..."}voir Comportement commun

Document (export/import du workflow complet)

Une ressource singleton : le workflow entier (thème, marges, variables, jeux de données, images de contenu, et toute l'arborescence des sections, images incluses en base64) comme un seul objet JSON.

Exporter le document

GET/api/v1/workflows/templates/:id/document
cURL
curl https://app.doclift.io/api/v1/workflows/templates/100050/document \
  --header "X-Api-Key: <your-api-key>"
200 OK · application/json
{
  "document": {
    "format_version": 1,
    "key": "subscription-bulletin",
    "template": {
      "title": "Subscription bulletin",
      "description": "Investor onboarding document",
      "orientation": "portrait",
      "margin_top": 10,
      "margin_right": 10,
      "margin_bottom": 10,
      "margin_left": 10,
      "workflow_theme": { "paragraph": { "font_family": "Arial", "font_size": 12 } }
    },
    "variables": [{ "name": "investor_name", "field_type": "text", "required": true }],
    "datasets": [{ "name": "sample_natural_person", "values": { "investor_name": "Jane Doe" } }],
    "content_images": [{ "token": "a1b2c3", "file": "content-1.png" }],
    "sections": [{ "kind": "group", "title": "Cover page", "children": [] }],
    "pictures": {
      "background-1.png": "<base64>",
      "content-1.png": "<base64>"
    }
  },
  "unresolved_tokens": []
}

unresolved_tokens nomme tout token cité dans le contenu qui ne résout vers aucune image enregistrée : signalé ici plutôt que de produire silencieusement une image cassée. Répond même pendant que le constructeur détient le verrou d'édition, puisque c'est une lecture.

StatutCorpsQuand
200 corps ci-dessussuccès
404 {"error": "..."}pas un workflow, archivé, ou d'une autre organisation
403 {"error": "..."}voir Comportement commun

Remplacer le document

PUT/api/v1/workflows/templates/:id/document

Un remplacement complet destructeur, pas une fusion

Les sections, variables et jeux de données de prévisualisation de la cible sont effacés et reconstruits à partir du corps de la requête : tout ce que le document ne porte pas disparaît. Si une section échoue à s'enregistrer en cours de reconstruction, rien de l'écriture n'est appliqué : la cible est laissée exactement comme avant le PUT, jamais à moitié reconstruite.

cURL
curl https://app.doclift.io/api/v1/workflows/templates/100050/document \
  --request PUT \
  --header "X-Api-Key: <your-api-key>" \
  --header "Content-Type: application/json" \
  --data '{
    "document": {
      "format_version": 1,
      "key": "subscription-bulletin",
      "template": { "title": "Subscription bulletin", "description": "..." },
      "variables": [],
      "datasets": [],
      "content_images": [],
      "sections": [],
      "pictures": {}
    }
  }'
200 OK · application/json
{
  "replacement": true,
  "notice": "<message>",
  "template": { "id": 100050, "title": "Subscription bulletin", "published": false },
  "tree": [],
  "datasets": 0,
  "content_images": "<count or list, not pinned by this API's test suite>",
  "backgrounds": "<count or list, not pinned by this API's test suite>",
  "dangling_tokens": [],
  "discarded_images": []
}

Le corps document est admis dans son intégralité, sans liste blanche champ par champ : un thème, une condition et un placement ont la forme que produit le constructeur du dashboard, pas un schéma figé, et une liste d'autorisation ici ne ferait que dupliquer cette forme à maintenir en parallèle. Deux comportements à connaître avant de s'appuyer dessus :

  • document.template.published est lu mais jamais appliqué. La cible garde sa propre valeur published actuelle, quoi que dise le corps de la requête. Cela ferme une porte autour du contrôle d'intégrité propre à publication, que cet endpoint n'exécute pas.
  • Les images de contenu citées depuis le contenu reçoivent de nouvelles copies enregistrées avec de nouveaux tokens sur la cible ; le contenu est réécrit pour pointer vers ces copies plutôt que vers les images du workflow source.
ChampTypeRequisNotes
format_versionintegerouidoit être une version que cette API prend encore en charge
keystringoui
templateobjectouiworkflow_theme et les quatre marges vivent ici
variablesarrayouiremplace chaque variable de la cible
datasetsarrayouiremplace chaque jeu de données de prévisualisation de la cible
content_imagesarrayouile file de chaque entrée doit avoir une clé correspondante dans pictures
sectionsarrayouitoute l'arborescence, remplaçant celle de la cible
picturesobjectouicarte {filename => base64} pour chaque image citée ci-dessus

La réponse rapporte aussi content_images et backgrounds de l'import ; si chacun est un compte ou une liste n'est pas figé par la suite de tests de cette API, donc traitez-les comme informatifs plutôt que comme un contrat typé.

StatutCorpsQuand
200 corps ci-dessussuccès
422 {"errors": ["..."]}format_version non pris en charge ; une entrée de content_images nommant un fichier absent de pictures ; une valeur de pictures qui n'est pas du base64 valide ; le corps n'est pas un Hash ; une clé requise manque ; la catégorie du document ne correspond pas à celle de la cible ; une section échoue sa validation à la reconstruction (retour en arrière)
404 {"error": "..."}pas un workflow, archivé, ou d'une autre organisation
409 {"error": "..."}verrou d'édition détenu
403 {"error": "..."}voir Comportement commun