> ## Documentation Index
> Fetch the complete documentation index at: https://docs.anyformat.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Node schemas

> Every field the API accepts on each workflow node, the shared types they use, the edge shape, and the topology rules a graph must satisfy.

This page is transcribed from the API's request schema. It lists the fields a public caller may set on each node of a [workflow](/concepts/workflows), with their type, whether they are required, and their default. The machine-readable form is the OpenAPI document at [https://api.anyformat.ai/schema/](https://api.anyformat.ai/schema/). Each node's guide page explains what the fields do and when to use them.

A workflow body is `{ 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](/guides/nodes/parse).

| Field | Type | Required | Default | Description |
| - | - | - | - | - |
| `id` | string | yes | | Stable identifier for this node within the graph. |
| `type` | `"parse"` | yes | | |
| `mode` | `standard` \| `agentic` \| `lite` \| `flash` | no | `standard` | The parse tier. `lite` is shown as **Fast** in the app. |
| `prompt_hint` | string \| null | no | `null` | Free-form hint that biases the parse output. Standard, agentic and flash only; ignored in lite. |
| `figure_enhancement` | boolean | no | `false` | Standard mode: extract structured data from charts and images. Extra processing per figure. Ignored in agentic and lite. |
| `cache` | boolean | no | `true` | Reuse the parsed output when the same file was already parsed with the same mode and settings. A hit skips the whole parse. Set `false` to force a fresh parse. |
| `effort` | `low` \| `mid` \| `accurate` | no | `mid` | Agentic mode only: quality and cost preset. |
| `scanned_pages` | `ocr` \| `skip` \| `fail` | no | `ocr` | Flash mode only: what to do with a page whose text layer produced no words. `ocr` parses it like any other page, `skip` serves it blank and flags it, `fail` raises and names the page numbers. |

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.

```json theme={null}
{ "id": "parse_1", "type": "parse", "mode": "agentic", "effort": "accurate" }
```

Echoed by `GET`:

```json theme={null}
{ "id": "parse_1", "type": "parse", "mode": "agentic", "prompt_hint": null, "figure_enhancement": false, "cache": true, "effort": "accurate", "ocr_effort": "high", "scanned_pages": "ocr" }
```

## extract

Pulls the fields of a schema out of the parsed document, each with a value, a confidence and evidence. Guide: [Extract](/guides/nodes/extract).

| Field | Type | Required | Default | Description |
| - | - | - | - | - |
| `id` | string | yes | | Stable identifier for this node within the graph. |
| `type` | `"extract"` | yes | | |
| `mode` | `standard` \| `agentic` \| `lite` | no | `standard` | The extraction tier. `lite` is shown as **Fast** in the app. |
| `extraction_schema` | [ExtractionSchema](#extractionschema-and-fields) | yes | | The fields this node extracts. At least one. |
| `lookup_file_ids` | string\[] | no | `[]` | Ids of the workflow's [Smart Lookup](/guides/nodes/smart-lookup) reference files this node looks values up in. Read it back from `GET` and send it back unchanged to keep the files; write `lookup_file_uploads` to add files. An id that is not one of this workflow's files fails the run. |
| `lookup_suggestion` | string \| null | no | `null` | Free-form hint shown to the smart-lookup matcher. |
| `lookup_reasoning_effort` | `minimal` \| `low` \| `medium` \| `high` \| null | no | `null` | Reasoning effort for the smart-lookup matcher. `null` is the model default. Higher effort improves match reliability on noisy join keys at higher latency and cost. |
| `lookup_file_uploads` | [LookupFileUpload](#lookupfileupload)\[] | no | `[]` | Inline reference-file content. Each entry is uploaded and its new id is appended to `lookup_file_ids`. Create-input only: the API does not store it on the node. |
| `use_images` | boolean | no | `false` | Retired. Accepted for compatibility and ignored: extraction reads the parsed text only. |

```json theme={null}
{
  "id": "extract_1",
  "type": "extract",
  "extraction_schema": {
    "fields": [
      { "name": "invoice_number", "description": "The unique invoice identifier", "data_type": "string" },
      { "name": "total_amount",   "description": "Total invoice amount",          "data_type": "float" }
    ]
  }
}
```

Echoed by `GET`. Every field carries the server-assigned `persistent_id` and its `source`; echo both back on `PATCH`:

```json theme={null}
{
  "id": "extract_1",
  "type": "extract",
  "mode": "standard",
  "extraction_schema": {
    "fields": [
      { "persistent_id": "0686bb97-8c30-70f0-8000-97669e00aaaa", "name": "invoice_number", "description": "The unique invoice identifier", "source": "extraction", "data_type": "string" },
      { "persistent_id": "0686bb97-8c30-70f0-8000-97669e00aaab", "name": "total_amount",   "description": "Total invoice amount",          "source": "extraction", "data_type": "float" }
    ]
  },
  "lookup_file_ids": [],
  "lookup_suggestion": null,
  "lookup_reasoning_effort": null,
  "lookup_file_uploads": [],
  "use_images": false
}
```

## classify

Labels the document as one of your categories and routes it down that category's branch. Guide: [Classify](/guides/nodes/classify).

| Field | Type | Required | Default | Description |
| - | - | - | - | - |
| `id` | string | yes | | Stable identifier for this node within the graph. |
| `type` | `"classify"` | yes | | |
| `user_prompt` | string \| null | no | `null` | Extra instructions inserted between the classifier's system prompt and the document text. |
| `categories` | [ClassifyCategory](#classifycategory)\[] | yes | | At least one. Category ids and names must each be unique within the node. |

```json theme={null}
{
  "id": "classify_1",
  "type": "classify",
  "categories": [
    { "id": "INVOICE", "name": "Invoice", "description": "A vendor invoice." },
    { "id": "RECEIPT", "name": "Receipt", "description": "A point-of-sale receipt." }
  ]
}
```

## splitter

Breaks one file that holds several documents into pieces, one rule per piece, and routes each rule down its own branch. The wire `type` is `splitter`; the app calls it **Split**. Guide: [Split](/guides/nodes/split).

| Field | Type | Required | Default | Description |
| - | - | - | - | - |
| `id` | string | yes | | Stable identifier for this node within the graph. |
| `type` | `"splitter"` | yes | | |
| `rules` | [SplitterRule](#splitterrule)\[] | yes | | At least one. Rule ids and names must each be unique within the node. |

```json theme={null}
{
  "id": "split_1",
  "type": "splitter",
  "rules": [
    { "id": "STATEMENT", "name": "Statement", "description": "A bank account statement.", "partition_key": "account_number" },
    { "id": "CHECK",     "name": "Check",     "description": "A scanned check." }
  ]
}
```

## validate

Checks the values an Extract node produced against your rules and records a verdict per rule. Guide: [Validate](/guides/nodes/validate).

| Field | Type | Required | Default | Description |
| - | - | - | - | - |
| `id` | string | yes | | Stable identifier for this node within the graph. |
| `type` | `"validate"` | yes | | |
| `rules` | [ValidationRule](#validationrule)\[] | yes | | At least one. |

```json theme={null}
{
  "id": "validate_1",
  "type": "validate",
  "rules": [
    { "id": "vendor-legit", "description": "The vendor is a real, named company." },
    {
      "id": "totals-add-up",
      "kind": "deterministic",
      "check": { "type": "arithmetic", "operands": ["<subtotal persistent_id>", "<tax persistent_id>"], "equals": "<total persistent_id>", "tolerance": 0.01 }
    }
  ]
}
```

## if\_else

Evaluates one check against the upstream extraction and routes the run down a `true` or a `false` branch. Guide: [If/Else](/guides/nodes/if-else).

| Field | Type | Required | Default | Description |
| - | - | - | - | - |
| `id` | string | yes | | Stable identifier for this node within the graph. |
| `type` | `"if_else"` | yes | | |
| `condition` | [Check](#check) | yes | | The check to evaluate. A `validation` check routes on the outcome of an upstream Validate rule. |

The outgoing edges carry `"branch": "true"` and `"branch": "false"`. The `true` edge is required; the `false` edge may be left unwired.

```json theme={null}
{
  "id": "if_else_1",
  "type": "if_else",
  "condition": { "type": "validation", "rule_id": "totals-add-up", "status": "fail" }
}
```

## slack\_alert

Posts a rendered message to a Slack channel at the end of a branch. It emits nothing downstream. Guide: [Slack alert](/guides/nodes/slack-alert).

| Field | Type | Required | Default | Description |
| - | - | - | - | - |
| `id` | string | yes | | Stable identifier for this node within the graph. |
| `type` | `"slack_alert"` | yes | | |
| `channel_id` | string | yes | | Canonical Slack channel id, for example `C0123ABC`. Stable across renames. |
| `channel_name` | string | yes | | The channel's display name at save time, for example `#finance`. Shown in logs and Studio; not re-resolved on every send. |
| `message_template` | string | yes | | Message body. `${field.<persistent_id>}` inserts an extracted value. With an upstream Validate node, `${validation.<rule_id>.status}`, `.detail` and `.severity` insert a rule's outcome. A missing field or unknown rule id renders as `<missing: ...>`. |
| `severity` | `info` \| `warning` \| `critical` | no | `info` | Colour of the message's attachment bar. Presentational only. |

```json theme={null}
{
  "id": "slack_alert_1",
  "type": "slack_alert",
  "channel_id": "C0123ABC",
  "channel_name": "#finance",
  "message_template": "Totals do not reconcile on ${field.0686bb97-8c30-70f0-8000-97669e00aaaa}: ${validation.totals-add-up.detail}",
  "severity": "warning"
}
```

## 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](/guides/nodes/edit).

| Field | Type | Required | Default | Description |
| - | - | - | - | - |
| `id` | string | yes | | Stable identifier for this node within the graph. |
| `type` | `"edit"` | yes | | |
| `instructions` | string \| null | no | `null` | Free-text values to write into the detected fields, for example `Name: ACME SL; Date: 2026-01-01; I accept the terms: yes`. Wins over a reference document where both address the same field. |
| `reference_document_ids` | string\[] | no | `[]` | Ids of reference documents uploaded to this workflow. Every id must belong to this workflow and be fully resolved, or the run is refused. |
| `font` | `sans` \| `serif` \| `mono` | no | `sans` | Typeface the filled values are written in. |
| `output_mode` | `flattened` \| `editable` | no | `flattened` | `flattened` paints the values on and the PDF is final. `editable` leaves each value in a live form field. |

```json theme={null}
{
  "id": "edit_1",
  "type": "edit",
  "instructions": "Name: ACME SL; Date: 2026-01-01; I accept the terms: yes",
  "reference_document_ids": ["069dcc2c-e14c-7606-8000-2ee4fb17b4e2"]
}
```

## knowledge

Indexes the parsed documents of a workflow into its knowledge base. It has no options. It accepts one inbound edge from `parse`, `classify`, `extract` or `validate`, emits nothing downstream, and appears at most once per workflow. Its presence makes completed runs eligible for ingestion. Guide: [Knowledge](/guides/nodes/knowledge).

| Field | Type | Required | Default | Description |
| - | - | - | - | - |
| `id` | string | yes | | Stable identifier for this node within the graph. |
| `type` | `"knowledge"` | yes | | |

```json theme={null}
{ "id": "knowledge_1", "type": "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](/concepts/field-types) for what each type extracts.

| Field | Type | Required | Default | Description |
| - | - | - | - | - |
| `data_type` | `string` \| `integer` \| `float` \| `boolean` \| `date` \| `datetime` \| `enum` \| `multi_select` \| `object` | yes | | The discriminator. |
| `name` | string | yes | | Field name. Used as the key in the extraction results. |
| `description` | string | no | `""` | Free-form description shown to the extraction model. An empty description is accepted but degrades quality. |
| `source` | `extraction` \| `smart_lookup` \| `lookup_if_missing` | no | `extraction` | Where the value comes from: the document, the lookup file (always overwrites), or the document first and the lookup file where extraction left no value. |
| `persistent_id` | string (UUID) \| null | no | `null` | Server-assigned stable identity, returned by `GET`. Echo it on updates, including across renames. Omit for new fields. Sending one on create is rejected with `400`. |
| `enum_options` | [EnumOption](#enumoption)\[] | `enum`, `multi_select` only | | At least one option. |
| `nested_fields` | field\[] | `object` only | | At least one child field. Only one level of nesting: a child cannot be an `object`. |

### EnumOption

| Field | Type | Required | Default | Description |
| - | - | - | - | - |
| `name` | string | yes | | The option value. |
| `description` | string | yes | | Free-form description shown to the model. |

### ClassifyCategory

| Field | Type | Required | Default | Description |
| - | - | - | - | - |
| `id` | string | yes | | Stable category id. The `branch` value on the edge that leaves the Classify node for this category. |
| `name` | string | yes | | Display name shown to the model. Must be unique across the node's categories. |
| `description` | string | yes | | Free-form description shown to the model. |

### SplitterRule

| Field | Type | Required | Default | Description |
| - | - | - | - | - |
| `id` | string | yes | | Stable rule id. The `branch` value on the edge that leaves the Split node for this rule. |
| `name` | string | yes | | Display name shown to the model. Must be unique across the node's rules. |
| `description` | string | yes | | Free-form description shown to the model. |
| `partition_key` | string | no | `""` | Field that further partitions this rule into separate sub-documents, for example `invoice_number`. Each distinct value becomes its own split. Empty means the whole rule flows on as one document. |

### 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.

| Field | Type | Required | Default | Description |
| - | - | - | - | - |
| `id` | string | yes | | Stable rule id. Round-trips through the validation results, and is what `if_else` and `slack_alert` reference. |
| `name` | string \| null | no | `null` | Human-readable rule name. |
| `kind` | `ai` \| `deterministic` | no | `ai` | How the rule is evaluated. |
| `description` | string \| null | no | `null` | Natural-language rule sent to the model. Required when `kind` is `ai`. |
| `check` | [Check](#check) \| null | no | `null` | Structured check run in code. Required when `kind` is `deterministic`. Must not contain a `validation` check. |
| `severity` | `error` \| `warning` | no | `error` | How a failed rule is labelled in results. Never blocks the run. |
| `source_fields` | string\[] | no | `[]` | Persistent ids of the fields this rule references. |

### Check

A check is a JSON object discriminated on `type`. Field operands are `persistent_id`s, except in `expression`, which addresses fields by name. Combinators nest.

| `type` | Fields | Description |
| - | - | - |
| `range` | `field` (string, required), `min` (number \| null), `max` (number \| null) | The numeric field lies within the inclusive bounds. |
| `date` | `field` (string, required), `earliest` (string \| null), `latest` (string \| null) | The date field lies within the inclusive bounds. Each bound is an ISO date `YYYY-MM-DD` or the literal `today`. |
| `arithmetic` | `operands` (string\[], at least one, required), `operator` (`sum` \| `subtract` \| `product`, default `sum`), `equals` (string, required), `tolerance` (number, default `0`) | Combining the operands with the operator equals the `equals` field, within `tolerance`. |
| `comparison` | `left` (string, required), `op` (`==` \| `!=` \| `>` \| `>=` \| `<` \| `<=`, required), `right` (required) | Compares the `left` field against `right`, which is `{ "source": "field", "field": "<persistent_id>" }` or `{ "source": "literal", "value": <number \| string \| boolean> }`. |
| `one_of` | `field` (string, required), `allowed` (string\[], at least one, required), `case_sensitive` (boolean, default `false`) | The value is one of `allowed`. |
| `regex` | `field` (string, required), `pattern` (string, required) | The value matches the pattern. Evaluated with RE2 (linear time). |
| `required` | `field` (string, required) | The field has a value. |
| `confidence` | `field` (string, required), `op` (`<` \| `<=` \| `>` \| `>=` \| `==` \| `!=`, required), `threshold` (integer 0 to 100, required) | Compares the field's extraction confidence against `threshold`. `field` is a `persistent_id` or the field's name. |
| `expression` | `expression` (string, 1 to 1000 characters, required) | A CEL expression over the root variable `data`, for example `data.total == data.subtotal + data.tax`. Fields are addressed by name. |
| `validation` | `rule_id` (string, required), `status` (`fail` \| `pass` \| `inconclusive`, default `fail`) | True when the named upstream Validate rule ended with `status`. Only valid on an `if_else` condition. |
| `all_of` | `checks` (Check\[], 1 to 32, required) | Passes when every child passes. |
| `any_of` | `checks` (Check\[], 1 to 32, required) | Passes when any child passes. |

### LookupFileUpload

| Field | Type | Required | Default | Description |
| - | - | - | - | - |
| `filename` | string | yes | | The file's name, for example `suppliers.csv`. |
| `content` | string | yes | | The file's bytes, base64-encoded. |

## Edges

`edges` is a list of directed connections between node ids. A workflow with only a Parse node needs no edges.

| Field | Type | Required | Default | Description |
| - | - | - | - | - |
| `source` | string | yes | | The `id` of the node the edge leaves. |
| `target` | string | yes | | The `id` of the node the edge enters. |
| `branch` | string \| null | no | `null` | The source port. Required on every edge that leaves a `classify`, `splitter` or `if_else` node; forbidden on every other edge. |

`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 }`.

```json theme={null}
"edges": [
  { "source": "parse_1",    "target": "classify_1" },
  { "source": "classify_1", "target": "extract_invoice", "branch": "INVOICE" },
  { "source": "classify_1", "target": "extract_receipt", "branch": "RECEIPT" }
]
```

## Topology rules

The API validates the whole graph on create and on update. A graph that breaks a rule is rejected with `400` 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:

| Node | Accepts an edge from |
| - | - |
| `parse` | nothing. Parse is always first. |
| `classify` | `parse` |
| `splitter` | `parse`, `classify` |
| `extract` | `parse`, `classify`, `splitter` |
| `validate` | `extract` |
| `if_else` | `extract`, `validate` |
| `slack_alert` | `extract`, `validate`, `if_else` |
| `knowledge` | `parse`, `classify`, `extract`, `validate` |
| `edit` | `parse` |

The graph-level rules:

* Exactly one `parse` node.
* Node ids are unique. Every edge names existing nodes. Every node except `parse` has an inbound edge. No cycles.
* Only `classify`, `splitter` and `if_else` fan out. Every other node has at most one outgoing edge.
* `validate`, `if_else` and `slack_alert` read one upstream extraction, so each accepts exactly one incoming edge. `extract` may 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 `id`s are unique and `name`s are unique. Two edges leaving the same node cannot carry the same `branch`.
* Every `if_else` has a `true` outgoing edge.
* An `extract` cannot sit downstream of an `if_else` today. That fan-out shape is not yet supported.
* Every `extract` has at least one field.
* Within one `extract`, and within one `object` field, no two fields share a result key. The key is the field name in lower snake case, so `Invoice Number` and `invoice_number` clash. The same key under different parents is fine.
* At most one `knowledge` node.
* Every `rule_id` in an `if_else` condition, and every `${validation.<rule_id>...}` token in a `slack_alert` template, names a rule on a Validate node reachable upstream of that node.
* `edit` is terminal: no node accepts an edge from it.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.