{ name, description, nodes, edges }. Every node carries an id (unique within the graph) and a type (the discriminator). The API rejects unknown keys on any node with 400 VALIDATION_ERROR, so a field that is not on this page is not accepted, even if the app shows it.
Write only the fields you want to set. A GET echoes every node with every default filled in (for example "cache": true on a Parse node you created with only id and type), and every edge with "branch": null where you set none. That echoed shape is what PATCH accepts back.
parse
Reads the document and turns it into text, tables and layout. Every workflow has exactly one. Guide: Parse.
A field that belongs to another tier is accepted and ignored. A
GET also echoes "ocr_effort": "high": a retired lite-tier knob, accepted and ignored, kept on the wire for compatibility. Do not set it.
GET:
extract
Pulls the fields of a schema out of the parsed document, each with a value, a confidence and evidence. Guide: Extract.GET. Every field carries the server-assigned persistent_id and its source; echo both back on PATCH:
classify
Labels the document as one of your categories and routes it down that category’s branch. Guide: Classify.splitter
Breaks one file that holds several documents into pieces, one rule per piece, and routes each rule down its own branch. The wiretype is splitter; the app calls it Split. Guide: Split.
validate
Checks the values an Extract node produced against your rules and records a verdict per rule. Guide: Validate.if_else
Evaluates one check against the upstream extraction and routes the run down atrue or a false branch. Guide: If/Else.
The outgoing edges carry
"branch": "true" and "branch": "false". The true edge is required; the false edge may be left unwired.
slack_alert
Posts a rendered message to a Slack channel at the end of a branch. It emits nothing downstream. Guide: Slack alert.edit
Detects the fillable fields on the parsed PDF, fills them, and returns the filled PDF. Terminal: nothing accepts an edge from it. Guide: Edit.knowledge
Indexes every document the workflow parses into a per-workflow knowledge base. Its presence is the switch; it has no options. At most one per workflow. Guide: Knowledge.Shared types
ExtractionSchema and fields
extraction_schema is { "fields": [ ... ] } with at least one field. Every field carries these keys, plus the ones its data_type adds. See Field types for what each type extracts.
EnumOption
ClassifyCategory
SplitterRule
ValidationRule
A rule is either AI-evaluated (kind: "ai", a natural-language description) or deterministic (kind: "deterministic", a structured check). An ai rule must not carry a check; a deterministic rule must carry one.
Check
A check is a JSON object discriminated ontype. Field operands are persistent_ids, except in expression, which addresses fields by name. Combinators nest.
LookupFileUpload
Edges
edges is a list of directed connections between node ids. A workflow with only a Parse node needs no edges.
branch must equal a category id on a Classify node, a rule id on a Split node, or "true" / "false" on an If/Else node. It is the id, not the name. A GET echoes an edge you wrote as { "source", "target" } as { "source": "parse_1", "target": "extract_1", "branch": null }.
Topology rules
The API validates the whole graph on create and on update. A graph that breaks a rule is rejected with400 and error_code: "TOPOLOGY_INVALID"; detail.violations[] names every rule broken and the node ids involved, so one round trip shows every problem.
Which node may feed which:
The graph-level rules:
- Exactly one
parsenode. - Node ids are unique. Every edge names existing nodes. Every node except
parsehas an inbound edge. No cycles. - Only
classify,splitterandif_elsefan out. Every other node has at most one outgoing edge. validate,if_elseandslack_alertread one upstream extraction, so each accepts exactly one incoming edge.extractmay receive several branches of one Classify node.- Each Split rule routes to its own target node. Two rules cannot share a target.
- Within one node, category or rule
ids are unique andnames are unique. Two edges leaving the same node cannot carry the samebranch. - Every
if_elsehas atrueoutgoing edge. - An
extractcannot sit downstream of anif_elsetoday. That fan-out shape is not yet supported. - Every
extracthas at least one field. - At most one
knowledgenode. - Every
rule_idin anif_elsecondition, and every${validation.<rule_id>...}token in aslack_alerttemplate, names a rule on a Validate node reachable upstream of that node. editis terminal: no node accepts an edge from it.

