Workflows

Driving the API from an agent

API v115 August 2026·6 min read

How the Doclift MCP server drives the workflow API in the right order, surfaces the failures that answer 200, and where its self-check can never replace reading the document.

What it is

The MCP server is a small process that speaks the Model Context Protocol over stdio. It gives an agent, or a script standing in for one, the same workflow authoring surface described across Templates, Sections & theme and Variables & data, as callable tools, so that building or inspecting a workflow does not require a browser.

It is not additional API surface. Every tool is a thin wrapper over the REST endpoints documented across these pages: nothing it answers with comes from anywhere else, and nothing it refuses is refused for a reason those pages do not already give.

Workflows are a private beta, available on request at [email protected]; see Workflows.


What it encodes that the REST API cannot

Two things a plain HTTP client has no way to know from the shape of the endpoints alone.

The order operations should happen in. Nothing stops a caller from writing section content before the variable it cites exists, or before the theme is set: both save cleanly and fail later, quietly. See Building a workflow end to end for that order, alongside which steps the API actually enforces and which it only advises against.

The failures that answer 200. A stripped tag, a misspelled placement key, an inert token, an image the renderer cannot reach, a theme value that silently freezes: none of these refuse the write that caused them. See Failures that answer 200 for the complete list and how to catch each one.

It holds no catalogue of its own. Anything a caller needs to know about what it may write, kinds, operators, placement keys, organization limits, comes from calling workflow_capabilities live, exactly as Capabilities are authoritative, not a copy describes for the REST endpoint underneath it. A hardcoded copy in a guide, or in a client's own memory, would eventually be wrong.

One more thing worth knowing before a first write: while a human holds the workflow's edit lock in the builder, every write through the server is refused with 409, exactly as it is over plain REST. See Shared behaviour.


Running it

Terminal
npm install
npm run build
DOCLIFT_API_KEY="<the key you send in X-Api-Key>" DOCLIFT_API_URL="https://app.doclift.io" node dist/index.js
VariableRequiredDefault
DOCLIFT_API_KEYyesnone
DOCLIFT_API_URLnohttps://app.doclift.io

The key must belong to an organization workflows are enabled for, and it carries exactly the rights it carries on the REST API: no more.

A client declares the server like any other MCP server, over stdio:

Configuration
{
  "mcpServers": {
    "doclift-workflows": {
      "command": "node",
      "args": ["/path/to/mcp/dist/index.js"],
      "env": {
        "DOCLIFT_API_KEY": "...",
        "DOCLIFT_API_URL": "..."
      }
    }
  }
}

The five guides

Alongside its tools, the server exposes five read-only resources: prose the API's shape cannot carry on its own.

ResourceWhat it answers
doclift://guides/how-to-buildThe order to create things in, and the mistakes that produce no error.
doclift://guides/conditionsWhich key each condition operator takes, and what a collection variable tolerates.
doclift://guides/repetitionsSection repetition against row repetition, and why nesting either is refused.
doclift://guides/before-you-finishWhat a self-check can verify, and the one thing it cannot.
doclift://guides/anomaliesWhich anomaly types block publication, and which only warn.

Everything factual in them is already covered by Workflows, Publishing a workflow and Failures that answer 200. Reading a guide before a write only saves a round trip against workflow_capabilities.


Generation is not a separate mechanism

workflow_render wraps a single existing endpoint: POST /api/v1/document_requests, with one entry in document_generations. It adds no logic of its own: it accepts nothing the endpoint itself would refuse, so the workflow still has to be published first. See Create a document request for the request and response shapes, and Workflow templates for what variables has to satisfy before you call it.


The self-check is a composition, not new logic

workflow_selfcheck calls three read endpoints already documented elsewhere and reduces their bodies into a checklist: POST .../validate, GET .../templates/:id, and GET .../payload_contract. It adds no server-side logic and no data that was not already there for a caller to read directly.

Line on the checklistComes from
Nothing blocks publicationvalidate's blocking anomalies
No token is inertvalidate's stored.inert_tokens
No picture is unreachablevalidate's stored.unreachable_images
No running-title band is clippedvalidate's stored.clipped_bands
No variable is declared and never usedvalidate's stored.unused_variables
A PDF exists and is newer than the last editvalidate's stored.last_render
No font weight prints by substitutionvalidate's stored.typography against capabilities.authoring.fonts
Page breaks were actually decidedvalidate's stored.page_breaks

Each line reads pass, todo, or unknown, never a bare pass when the evidence for it was absent from a response: a self-check that read silence as success would defeat its own purpose.


The one line a self-check can never pass

This line stays unknown by design

The self-check names, but never checks off, whether the document is the one that was asked for. No tool can compare a rendered PDF to an intention it never saw.

Everything above it is a reading of the workflow's own state: anomalies, tokens, images, fonts, timestamps. This last line is not, because that information does not exist anywhere the tool can reach. Whether the wording, the order of the sections and the values in place of the tokens match what was actually requested is a judgment only the caller holding the original request can make: fetch the URL workflow_render answers with, and read the file.

Treat a self-check with everything else passing as a workflow that is mechanically sound, not as one confirmed correct.


Next