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

# Validate

> Checks the values an Extract node produced against your rules and records a verdict per rule. Deterministic checks are free; AI rules cost 5 credits each.

**Validate** runs a list of rules over the fields an [Extract](/guides/nodes/extract) node produced and records a verdict for each rule: **pass**, **fail**, or **inconclusive**. It lives in the **Logic** section of the Studio palette.

Every rule is one of two kinds, and one Validate node can mix both:

| Rule kind         | How it is checked                                                  | Best for                                                                                                                           | Cost               |
| ----------------- | ------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------- | ------------------ |
| **Deterministic** | A structured check runs in code. No model.                         | Exact, testable conditions: a number in range, `a + b = c`, a value in a set, a pattern, a required field, a confidence threshold. | Free               |
| **AI**            | A model reads your plain-language description and judges the data. | Fuzzy or semantic conditions: "the vendor looks like a real company", "the two names refer to the same person".                    | 5 credits per rule |

<Note>
  Use a **deterministic** rule whenever a calculator or a lookup could settle the condition. It is instant, free, and never flakes. Use an **AI** rule when the condition needs judgement or language understanding.
</Note>

## The node

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

  ```python Python theme={null}
  import os
  from anyformat.sdk import Client
  from anyformat.workflow import Schema, ValidationRule, ArithmeticCheck

  client = Client(api_key=os.environ["ANYFORMAT_API_KEY"])

  workflow = (
      client.workflow("Invoice with checks")
      .parse()
      .extract([
          Schema.float("subtotal", "Subtotal before tax."),
          Schema.float("tax", "Tax amount."),
          Schema.float("total", "Grand total."),
          Schema.string("vendor_name", "Vendor name."),
      ])
      .validate(
          ValidationRule(id="totals-add-up", kind="deterministic", severity="error",
              check=ArithmeticCheck(type="arithmetic", operands=["subtotal", "tax"], equals="total", tolerance=0.01)),
          ValidationRule(id="vendor-legit", description="The vendor is a real, named company."),
      )
      .create()
  )
  ```

  ```typescript TypeScript theme={null}
  import { Anyformat, Schema } from "@anyformat/sdk";

  const af = new Anyformat({ apiKey: process.env.ANYFORMAT_API_KEY! });

  const workflow = await af
    .workflow("Invoice with checks")
    .parse()
    .extract([
      Schema.float("subtotal", "Subtotal before tax."),
      Schema.float("tax", "Tax amount."),
      Schema.float("total", "Grand total."),
      Schema.string("vendor_name", "Vendor name."),
    ])
    .validate([
      { id: "totals-add-up", kind: "deterministic", severity: "error",
        check: { type: "arithmetic", operands: ["subtotal", "tax"], operator: "sum", equals: "total", tolerance: 0.01 } },
      { id: "vendor-legit", kind: "ai", description: "The vendor is a real, named company." },
    ])
    .create();
  ```
</CodeGroup>

`validate()` attaches the node to the last `extract()` you called. Pass `branch=` (Python) or `{ branch }` (TypeScript) to attach it to the Extract on one Classify category or Split rule instead.

A check names a field by the **name** you gave it on the Extract node. Studio names fields by their persistent id instead; both forms resolve at run time. An **expression** check is the exception: it reads names only.

## In Studio

Click the Validate node on the canvas to open its panel. Each rule is a card. Pick **AI validation** or **Deterministic validation** at the top of the card: an AI rule takes a sentence, a deterministic rule takes a subject (a field value, field math, or an expression) and the assertion that completes "Check that the field…". Set the severity and click **Save rule**.

<img src="https://mintcdn.com/anyformat/G2lOO-2_Ah2kKl9r/images/studio-validate-config.webp?fit=max&auto=format&n=G2lOO-2_Ah2kKl9r&q=85&s=4a9ad7ffb2242a40776322c7c7a36c32" alt="Configuring the Validate node in Studio" width="512" height="266" data-path="images/studio-validate-config.webp" />

## Options

| Field   | Type          | Default                | What it does                                                                                   |
| ------- | ------------- | ---------------------- | ---------------------------------------------------------------------------------------------- |
| `rules` | list of rules | required, at least one | The rules to evaluate. Every rule runs on every extraction; a failed rule never stops the run. |

Each rule:

| Field           | Type                    | Default  | What it does                                                                                                                                                                |
| --------------- | ----------------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `id`            | string                  | required | Stable rule id. It names the rule in results, in an [If/Else](/guides/nodes/if-else) `validation` condition, and in a [Slack alert](/guides/nodes/slack-alert) placeholder. |
| `name`          | string                  | `null`   | Human-readable name shown in the app.                                                                                                                                       |
| `kind`          | `ai` \| `deterministic` | `ai`     | How the rule is checked.                                                                                                                                                    |
| `description`   | string                  | `null`   | The plain-language rule a model judges. Required when `kind` is `ai`; must be absent otherwise.                                                                             |
| `check`         | check object            | `null`   | The structured check (table below). Required when `kind` is `deterministic`; must be absent otherwise.                                                                      |
| `severity`      | `error` \| `warning`    | `error`  | How a failed rule is labelled in results. Display only: it never blocks the run.                                                                                            |
| `source_fields` | list of strings         | `[]`     | Persistent ids of the fields the rule reads. Studio fills it in; the API does not need it.                                                                                  |

The schema the API accepts is generated from the same source: [Validate node schema](/api-reference-v3/node-schemas#validate).

### Checks

Every check kind below is deterministic and free. Two are combinators that nest other checks.

| Check               | `type`       | What it asserts                                                                                      | Example                                         |
| ------------------- | ------------ | ---------------------------------------------------------------------------------------------------- | ----------------------------------------------- |
| **Required**        | `required`   | The field is present and not empty.                                                                  | `vendor_name` is filled in                      |
| **Number in range** | `range`      | A numeric field falls within `min` and `max` (inclusive; either may be `null`).                      | `amount` between `0` and `1000000`              |
| **Date in range**   | `date`       | A date field falls within `earliest` and `latest`: each an ISO date, the literal `today`, or `null`. | `expiry_date` on or after `today` (not expired) |
| **Arithmetic**      | `arithmetic` | A `sum`, `subtract`, or `product` of fields equals another field, within `tolerance`.                | `subtotal + tax = total` (±0.01)                |
| **Comparison**      | `comparison` | A field compares (`==`, `!=`, `>`, `>=`, `<`, `<=`) to a fixed value or another field.               | `end_date >= start_date`                        |
| **One of**          | `one_of`     | The field's value is in an allowed set. `case_sensitive` defaults to `false`.                        | `currency` is one of `EUR`, `USD`, `GBP`        |
| **Pattern**         | `regex`      | The field matches a regular expression (RE2 syntax, linear time).                                    | `iban` matches `^[A-Z]{2}\d{2}`                 |
| **Confidence**      | `confidence` | The field's extraction confidence (0 to 100) compares to `threshold`.                                | `total` has confidence `>= 80`                  |
| **Expression**      | `expression` | A [CEL](https://cel.dev/) expression over the whole extraction answers true or false.                | the line items add up to the total              |
| **All of**          | `all_of`     | Every nested check passes.                                                                           | required **and** in range                       |
| **Any of**          | `any_of`     | At least one nested check passes.                                                                    | matches pattern A **or** pattern B              |

Each check resolves to **pass**, **fail**, or **inconclusive**. A check is inconclusive when the field it names is missing, or cannot be read as the expected type. Numbers are read leniently: `"€1.234,56"` and `"1,234.56"` both parse. Dates accept the common written formats.

`all_of` fails if any child fails, otherwise it is inconclusive if any child is inconclusive, otherwise it passes. `any_of` passes if any child passes, otherwise it is inconclusive if any child is inconclusive, otherwise it fails. A combinator holds up to 32 checks. Studio nests one level; the API accepts deeper nesting.

<Note>
  A rule's check cannot contain a `validation` check (the outcome of another rule). The API rejects it. To act on a rule's verdict, route on it with an [If/Else](/guides/nodes/if-else) node instead.
</Note>

### Check payloads

```jsonc theme={null}
{ "type": "required", "field": "vendor_name" }
{ "type": "range", "field": "amount", "min": 0, "max": null }
// Date bounds: an ISO date "YYYY-MM-DD", "today" (relative), or null.
{ "type": "date", "field": "expiry_date", "earliest": "today", "latest": null }   // not expired
{ "type": "date", "field": "invoice_date", "earliest": "2024-01-01", "latest": "2024-12-31" }
{ "type": "arithmetic", "operands": ["subtotal", "tax"], "operator": "sum", "equals": "total", "tolerance": 0.01 }
{ "type": "comparison", "left": "end_date", "op": ">=", "right": { "source": "field", "field": "start_date" } }
{ "type": "comparison", "left": "year", "op": ">=", "right": { "source": "literal", "value": 2000 } }
{ "type": "one_of", "field": "currency", "allowed": ["EUR", "USD", "GBP"], "case_sensitive": false }
{ "type": "regex", "field": "iban", "pattern": "^[A-Z]{2}\\d{2}" }
{ "type": "confidence", "field": "total", "op": ">=", "threshold": 80 }
{ "type": "expression", "expression": "sum(data.lines.map(l, num(l.amount))) == num(data.total)" }
{ "type": "all_of", "checks": [
  { "type": "required", "field": "iban" },
  { "type": "regex", "field": "iban", "pattern": "^[A-Z]{2}\\d{2}" }
] }
{ "type": "any_of", "checks": [
  { "type": "one_of", "field": "currency", "allowed": ["EUR"] },
  { "type": "range", "field": "total", "min": null, "max": 10000 }
] }
```

`operator` defaults to `sum`; `tolerance` to `0`; `case_sensitive` to `false`. Unset bounds are `null`. `threshold` is an integer from 0 to 100.

### Expressions

An **expression** check runs a [CEL](https://cel.dev/) expression over the whole extraction. Use it for a condition the other checks cannot name: a total over a list of line items, a per-row comparison, a conditional.

```
abs(sum(data.lineas.map(l, l.importe)) - num(data.ibruto)) <= 1.0
```

That is the rule this check was built for: the line items of an invoice must add up to the gross amount, within a tolerance of 1.0. The names are the ones the schema defines, in whatever language it uses.

* The expression reads one root variable, `data`, which holds the whole extraction.
* Fields use their **extracted name**: `data.ibruto`, `data.lineas`. An expression names no persistent id, so **renaming a field in the schema breaks every expression that names it**. The rule then reports **inconclusive**.
* The [CEL builtins](https://github.com/google/cel-spec/blob/master/doc/langdef.md) all work: `map`, `filter`, `all`, `exists`, `size`, `has`, `startsWith`.
* Three helpers come on top of them, because CEL coerces no number and folds no list, and an extracted value is frequently a string such as `"1.234,56"`:
  * `num(v)` reads one value as a number, with the same lenient parse the other checks use.
  * `sum(list)` adds a list, and reads each element the same way. Bare `+` does not: on two strings it joins them end to end.
  * `abs(x)`.
* `has(data.x)` asks whether the key is there, not whether it holds a value. A field that was extracted as null answers `true`. A rule that means "was extracted" reads `data.x != null`.
* Every evaluation error resolves to **inconclusive**: an expression that does not parse, that names a field the extraction does not carry, that divides by zero, or that answers with something other than a boolean. An expression never fails the run.

## What it returns

One verdict per rule per extraction. The run results carry them as `extractions[].validations[]`, on the entry whose fields the rules judged, so in a split workflow each piece carries its own. The key is always present: `[]` when the workflow has no Validate node.

| Field             | Meaning                                                                                 |
| ----------------- | --------------------------------------------------------------------------------------- |
| `rule_id`         | The rule's `id`.                                                                        |
| `description`     | The rule's description, as shown in the app. Empty for a deterministic rule.            |
| `severity`        | `error` or `warning`, copied from the rule.                                             |
| `status`          | `pass`, `fail`, or `inconclusive`.                                                      |
| `detail`          | A sentence that says why: the values compared, or the model's reasoning for an AI rule. |
| `compared_values` | The values the check read.                                                              |
| `source_fields`   | The `persistent_id`s of the fields the rule read.                                       |

In the app, the verdicts appear in the **Validation** tab of the results, next to the extracted data. See [Verification and review](/guides/workflows/verification-review). A failed rule marks the document; it never stops the run or hides the extraction.

Downstream nodes read the verdicts too. An [If/Else](/guides/nodes/if-else) node routes on a rule's outcome with a `validation` condition, and a [Slack alert](/guides/nodes/slack-alert) prints it with `${validation.<rule_id>.status}`.

<Note>
  On the public API the verdicts sit in each `extractions[]` entry of [`GET /v3/runs/{run_id}/`](/api-reference-v3/runs/get). The Python SDK exposes them as `result.validations` (per partition, `result.partitions[i].validations`); the TypeScript SDK as `extraction.validations` on each entry of `result.extractions`.
</Note>

## Connects to

| Direction | Nodes                                                                                                            |
| --------- | ---------------------------------------------------------------------------------------------------------------- |
| Fed by    | [Extract](/guides/nodes/extract). One Validate reads exactly one Extract.                                        |
| Feeds     | [If/Else](/guides/nodes/if-else), [Slack alert](/guides/nodes/slack-alert), [Knowledge](/guides/nodes/knowledge) |

Validate passes the extraction through unchanged, so a node after it sees the same fields the Extract produced.

## Billing

An **AI** rule costs **5 credits**, billed once per rule per extraction, not per page. A **deterministic** rule, including an expression, costs **0 credits**. A three-rule Validate with one AI rule costs 5 credits whether the document is 1 page or 100. Full price list: [How credits work](/concepts/how-credits-work).

## Examples

* [Invoice processing](/examples/invoice-processing): the Extract the rules on this page check. Add the `totals-add-up` and `vendor-legit` rules after it.
* [Bank statement processing](/examples/bank-statement-processing): an `expression` check that sums the transactions against the closing balance.
