Workflows

Sections et thème

API v115 août 2026·11 min de lecture

L'arborescence des sections d'un workflow : chaque endpoint de section, les arrière-plans, et le thème typographique du document.

Chaque endpoint ci-dessous est sous /api/v1/workflows. Lisez Workflows d'abord pour le modèle objet ; lisez Modèles, publication et document pour capabilities, la ressource modèle, et la publication. Cette page couvre l'arborescence des sections 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.

Thème

Voir Le modèle objet pour ce qu'est le thème et pourquoi il voyage à l'intérieur de chaque instantané publié. font-weight n'est délibérément pas une propriété modifiable ici : en fixer une sur un titre écraserait le gras que le moteur de rendu lui donne déjà, et sur un paragraphe cela n'apporterait rien de nouveau.

Récupérer le thème

GET/api/v1/workflows/templates/:id/theme
cURL
curl https://app.doclift.io/api/v1/workflows/templates/100050/theme \
  --header "X-Api-Key: <your-api-key>"
200 OK · application/json
{
  "theme": {
    "paragraph": { "font_family": "Arial", "font_size": 12, "color": "#1a1a1a", "line_height": 1.4 },
    "h1": { "font_family": "Arial", "font_size": 24, "color": "#000000" }
  },
  "blocks": ["paragraph", "h1", "h2", "h3"],
  "properties": ["font_family", "font_size", "color", "line_height"],
  "fonts": { "faces": [{ "family": "Arial", "weights": [400, 700] }], "substitutes": { "Calibri": "Liberation Sans" } },
  "font_size": { "min": 6, "max": 96, "unit": "pt" },
  "line_height": { "min": 1.0, "max": 2.5, "unit": null }
}

theme ne porte que les blocs effectivement déclarés : un bloc jamais réglé est simplement absent, pas rempli par défaut dans la réponse. Répond même pendant que le verrou d'édition est détenu.

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

Mettre à jour le thème

PATCH/api/v1/workflows/templates/:id/theme
cURL
curl https://app.doclift.io/api/v1/workflows/templates/100050/theme \
  --request PATCH \
  --header "X-Api-Key: <your-api-key>" \
  --header "Content-Type: application/json" \
  --data '{
    "theme": {
      "h1": { "font_family": "Arial", "font_size": 24, "color": "#000000" }
    }
  }'
200 OK · application/json
{
  "theme": {
    "paragraph": { "font_family": "Arial", "font_size": 12, "color": "#1a1a1a", "line_height": 1.4 },
    "h1": { "font_family": "Arial", "font_size": 24, "color": "#000000" }
  },
  "blocks": ["paragraph", "h1", "h2", "h3"],
  "properties": ["font_family", "font_size", "color", "line_height"],
  "fonts": { "faces": [{ "family": "Arial", "weights": [400, 700] }], "substitutes": {} },
  "font_size": { "min": 6, "max": 96, "unit": "pt" },
  "line_height": { "min": 1.0, "max": 2.5, "unit": null }
}

Fusion bloc par bloc, pas propriété par propriété

Un bloc que votre corps ne nomme pas reste exactement tel qu'enregistré. Un bloc que votre corps nomme est écrit en entier : envoyer h1 seul ne touche pas le paragraph enregistré, mais cela remplace bien chaque propriété que h1 portait déjà par uniquement ce que vous avez envoyé pour h1 cette fois.

Toute valeur qui ne s'interprète pas est retirée du thème enregistré plutôt que de refuser toute la requête. La réponse que vous recevez est toujours la valeur fraîchement rechargée et renormalisée, jamais un écho de ce que vous avez envoyé : lire la réponse est le seul moyen de voir un abandon se produire. Une famille de police non provisionnée, un line_height portant une unité, un font_size hors de 6-96 pt, un color non hexadécimal, un bloc ou une propriété inconnus, et tout font_weight sont tous abandonnés de cette façon. Voir Pièges pour la liste complète et à quoi ressemble chaque abandon dans la réponse.

StatutCorpsQuand
200 corps ci-dessussuccès, même si certaines valeurs ont été silencieusement abandonnées
400 {"error": "..."}corps sans la clé racine theme
404 {"error": "..."}pas un workflow, archivé, ou d'une autre organisation
409 {"error": "..."}verrou d'édition détenu
403 {"error": "..."}voir Comportement commun

Sections

Voir Le modèle objet pour ce que porte un groupe par rapport à une section de contenu, et pourquoi une référence cassée survit plutôt que d'être balayée. Le tableau ci-dessous est la référence propre à cette page : l'arborescence compte trois niveaux au maximum (groupe, groupe, section) et capabilities.limits.tree_depth l'indique.

Kindchildrencontentplacementsimagerunning_titles
groupouinonnonnongroupe racine seulement
rich_contentnonouinonnonnon
image_with_variablenonnonouiouinon

Lister les sections

GET/api/v1/workflows/templates/:id/sections
cURL
curl https://app.doclift.io/api/v1/workflows/templates/100050/sections \
  --header "X-Api-Key: <your-api-key>"
200 OK · application/json
{
  "tree": [
    {
      "id": 300100,
      "kind": "group",
      "title": "Investor identity",
      "condition": null,
      "children": [
        { "id": 300101, "kind": "rich_content", "title": "Identity paragraph", "content": "<p>...</p>" }
      ]
    }
  ]
}

Toute l'arborescence, ordonnée et imbriquée.

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

Afficher une section

GET/api/v1/workflows/templates/:id/sections/:id
cURL
curl https://app.doclift.io/api/v1/workflows/templates/100050/sections/300101 \
  --header "X-Api-Key: <your-api-key>"
200 OK · application/json
{
  "id": 300101,
  "kind": "rich_content",
  "title": "Identity paragraph",
  "content": "<p>Dear <variable class=\"editor-text-variable non-editable-content editor-parsed\">investor_name</variable></p>",
  "layout": "inline",
  "page_break": "continue",
  "repeat_over": null,
  "parent_id": 300100,
  "position": 0,
  "condition": null,
  "running_titles": null,
  "placements": null,
  "image_url": null
}

Un seul nœud (pas de clé tree ici). reference (un champ réservé à l'export/import) n'est délibérément jamais publié : rien ne le lit, donc le publier mettrait dans le contrat un champ dont personne ne pourrait dire quoi faire.

StatutCorpsQuand
200 sectionsuccès
404 {"error": "..."}la section appartient à un autre workflow, ou le workflow lui-même est hors du périmètre
403 {"error": "..."}voir Comportement commun

Créer une section

POST/api/v1/workflows/templates/:id/sections
cURL
curl https://app.doclift.io/api/v1/workflows/templates/100050/sections \
  --request POST \
  --header "X-Api-Key: <your-api-key>" \
  --header "Content-Type: application/json" \
  --data '{
    "section": {
      "kind": "group",
      "title": "Investor identity",
      "position": 0,
      "children": [
        {
          "kind": "rich_content",
          "title": "Identity paragraph",
          "content": "<p>Dear <variable class=\"editor-text-variable non-editable-content editor-parsed\">investor_name</variable></p>"
        }
      ]
    }
  }'
201 Created · application/json
{
  "section": { "id": 300100, "kind": "group", "title": "Investor identity", "children": ["..."] },
  "tree": ["..."]
}

children est parcouru récursivement : un groupe et toute sa sous-arborescence peuvent être créés en un seul appel, jusqu'au troisième niveau, de sorte qu'un appelant ne rejoue pas N créations en laissant un groupe à moitié constitué si l'une d'elles échoue. La réponse porte à la fois le nœud créé et l'arborescence entière, pour que vous voyiez les frères et sœurs renumérotés.

ChampTypeRequisNotes
kindstringouivoir Sections ; lisez section.kinds depuis capabilities
titlestringoui
contentstringnonrich_content seulement (refusé sur group/image_with_variable)
conditionobjectnon{match: "all"|"any", rules: [...]}
layoutstringnoninline ou full_page
page_breakstringnoncontinue, new_page, ou own_page
repeat_overstringnonnom d'une variable collection ; refusé si imbriqué dans une autre répétition
placementsarraynonimage_with_variable seulement ; chaque entrée accepte id, kind, variable, value, x, y, width, height, align, font_size, color, font_family, font_weight
running_titlesobjectnongroupes racines seulement
parent_idintegernondoit nommer un groupe de ce même workflow, à une profondeur légale
positionintegernonordre entre frères et sœurs
childrenarraynonrécursif, même forme, jusqu'au troisième niveau

kind sur une entrée de placement sélectionne le type de placement, pas le kind de la section au-dessus : la liste actuelle et faisant foi est capabilities.placement.keys_by_kind, par exemple text et checkbox (portant toutes deux variable) et static_text (portant value). Elle décide seulement si l'entrée porte variable (liée à une variable déclarée) ou value (une chaîne littérale) ; les clés de positionnement/style (x, y, width, height, align, font_size, color, font_family, font_weight) s'appliquent à chaque kind, et toute clé de géométrie laissée absente est lue comme 0. Le fait qu'une variable radio/select puisse être placée n'est pas précisé par cette référence. Lisez capabilities.placement.keys_by_kind en direct plutôt que de supposer une liste figée.

Une clé de placement mal orthographiée est supprimée, pas refusée

Seules les clés exactes listées ci-dessus sont conservées. Tout le reste répond 201 et est silencieusement absent de la section enregistrée. Le libellé s'affiche comme rien à la position qu'il aurait dû occuper. Voir Pièges pour ceci et toute autre écriture qui répond 200 en faisant discrètement moins que demandé.

Le contenu est assaini à l'écriture selon les listes blanches que publie capabilities : une balise ou un attribut hors liste est retiré silencieusement, comme la clé de placement mal orthographiée ci-dessus.

StatutCorpsQuand
201 corps ci-dessussuccès
422 {"errors": ["..."]}titre vide ; contenu sur un groupe ou une section image ; placements sur autre chose que image_with_variable ; parent_id inconnu, étranger, ou à mauvaise profondeur ; répétition imbriquée ; running_titles sur un groupe non racine ; un start de numérotation hors de 1-100 ; le plafond de sections de l'organisation atteint
400 {"error": "..."}corps sans la clé racine section
404 {"error": "..."}pas un workflow, archivé, ou d'une autre organisation
409 {"error": "..."}verrou d'édition détenu
403 {"error": "..."}voir Comportement commun

Mettre à jour une section

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

Mêmes champs qu'à la création, moins les champs structurels (kind, parent_id, position sont le travail de déplacer). Envoyer repeat_over: "" est lu comme « pas de répétition ».

200 OK · application/json
{
  "section": { "id": 300101, "title": "Identity paragraph", "content": "<p>...</p>" },
  "tree": ["..."]
}
StatutCorpsQuand
200 corps ci-dessussuccès
422 {"errors": ["..."]}mêmes validations qu'à la création, là où c'est pertinent
404 {"error": "..."}section ou workflow hors du périmètre
409 {"error": "..."}verrou d'édition détenu
403 {"error": "..."}voir Comportement commun

Déplacer une section

PATCH/api/v1/workflows/templates/:id/sections/:id/move
cURL
curl https://app.doclift.io/api/v1/workflows/templates/100050/sections/300101/move \
  --request PATCH \
  --header "X-Api-Key: <your-api-key>" \
  --header "Content-Type: application/json" \
  --data '{ "section": { "parent_id": 300100, "position": 1 } }'
200 OK · application/json
{
  "section": { "id": 300101, "parent_id": 300100, "position": 1 },
  "tree": ["..."]
}

Change le parent et/ou le rang ; les frères et sœurs, sur l'ancien comme sur le nouveau parent, sont renumérotés.

ChampTypeRequisNotes
parent_idintegernondoit nommer un groupe de ce workflow à une profondeur légale
positionintegernonrang entre frères et sœurs
StatutCorpsQuand
200 corps ci-dessussuccès
422 {"errors": ["..."]}parent_id inconnu, ou déplacer un groupe qui contient déjà un groupe vers un autre groupe
404 {"error": "..."}section ou workflow hors du périmètre
409 {"error": "..."}verrou d'édition détenu
403 {"error": "..."}voir Comportement commun

Dupliquer une section

POST/api/v1/workflows/templates/:id/sections/:id/duplicate
cURL
curl https://app.doclift.io/api/v1/workflows/templates/100050/sections/300100/duplicate \
  --request POST \
  --header "X-Api-Key: <your-api-key>"
201 Created · application/json
{
  "section": { "id": 300200, "kind": "group", "title": "Investor identity" },
  "tree": ["..."]
}

Copie en profondeur le nœud et toute sa sous-arborescence à l'intérieur du même modèle, arrière-plans compris. La copie est ajoutée à la fin de l'arborescence.

StatutCorpsQuand
201 corps ci-dessussuccès
422 {"errors": ["..."]}le plafond de sections de l'organisation serait dépassé par la copie
404 {"error": "..."}section ou workflow hors du périmètre
409 {"error": "..."}verrou d'édition détenu
403 {"error": "..."}voir Comportement commun

Supprimer une section

DELETE/api/v1/workflows/templates/:id/sections/:id
cURL
curl https://app.doclift.io/api/v1/workflows/templates/100050/sections/300101 \
  --request DELETE \
  --header "X-Api-Key: <your-api-key>"
200 OK · application/json
{ "tree": ["..."] }

Supprimer un groupe emporte toute sa sous-arborescence avec lui. Pas de clé "section" dans la réponse : le nœud nommé dans l'URL n'existe plus. Les frères et sœurs suivants sont renumérotés.

StatutCorpsQuand
200 {"tree": [...]}succès
404 {"error": "..."}section ou workflow hors du périmètre
409 {"error": "..."}verrou d'édition détenu
403 {"error": "..."}voir Comportement commun

Définir l'arrière-plan d'une section

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

Valide uniquement sur une section image_with_variable. Deux formes d'import : un import multipart sous background[file], ou un corps JSON avec filename, content_type, et data (base64 brut ou une URI data: complète).

cURL
curl https://app.doclift.io/api/v1/workflows/templates/100050/sections/300150/background \
  --request PATCH \
  --header "X-Api-Key: <your-api-key>" \
  --header "Content-Type: application/json" \
  --data '{
    "background": {
      "filename": "cover.png",
      "content_type": "image/png",
      "data": "data:image/png;base64,<...>"
    }
  }'
200 OK · application/json
{
  "section": { "id": 300150, "kind": "image_with_variable", "image_url": "/workflows/images/9f2a1c..." },
  "tree": ["..."]
}

Chaque import passe par le même pipeline : réencodé en WebP, redimensionné à au maximum 2480 px (arrière-plans) ou 1654 px (images de contenu), et vérifié contre le plafond d'octets de l'organisation. Les arrière-plans imposent en plus une largeur minimale (le workflow_image_min_width propre à l'organisation, défaut 800 px), puisqu'un arrière-plan est étiré à la largeur de la page ; une image de contenu est placée à la taille que vous lui donnez, elle n'a donc pas de plancher.

StatutCorpsQuand
200 corps ci-dessussuccès
422 {"errors": ["..."]}pas une section image_with_variable ; missing (aucun fichier/octet) ; too_large (au-dessus du plafond d'octets de l'organisation) ; wrong_type (vérifié contre les octets réencodés, pas le content-type déclaré) ; too_narrow (sous la largeur minimale)
400 {"error": "..."}corps sans la clé racine background
404 {"error": "..."}section ou workflow hors du périmètre
409 {"error": "..."}verrou d'édition détenu
403 {"error": "..."}voir Comportement commun

Supprimer l'arrière-plan d'une section

DELETE/api/v1/workflows/templates/:id/sections/:id/background
200 OK · application/json
{
  "section": { "id": 300150, "kind": "image_with_variable", "image_url": null },
  "tree": ["..."]
}

Ceci remet la référence à néant plutôt que de changer quoi que ce soit : le fichier sous-jacent reste dans le stockage, jamais supprimé, car un instantané publié doit continuer de se rendre comme il se rendait le jour de sa publication.

StatutCorpsQuand
200 corps ci-dessussuccès
422 {"errors": ["..."]}pas une section image_with_variable
404 {"error": "..."}section ou workflow hors du périmètre
409 {"error": "..."}verrou d'édition détenu
403 {"error": "..."}voir Comportement commun