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

# Validation rules

> Check extracted data with AI rules or instant deterministic checks — in Studio, the API, or the SDKs.

A **Validate** step runs a list of rules over the data an Extract step produced and flags anything that fails. Every rule is one of two kinds, and a single Validate step can mix both.

| Rule kind         | How it's checked                                                  | Best for                                                                                                       | Cost            |
| ----------------- | ----------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------- | --------------- |
| **AI**            | A model reads your plain-language description and judges the data | Fuzzy / semantic conditions ("the vendor looks like a real company", "the two names refer to the same person") | Billed per rule |
| **Deterministic** | A structured check runs in plain code — no model                  | Exact, testable conditions (a number in range, `a + b = c`, a value in a set, a pattern, a required field)     | **Free**        |

<Note>
  Reach for a **deterministic** rule whenever the condition is something you could check with a calculator or a lookup — it's instant, free, and never flakes. Use an **AI** rule when the condition needs judgement or language understanding.
</Note>

## Deterministic checks

Each deterministic rule is built from one check. Fields are referenced by name (the field name from the upstream Extract step).

| Check               | What it asserts                                                                   | Example                                         |
| ------------------- | --------------------------------------------------------------------------------- | ----------------------------------------------- |
| **Number in range** | A numeric field falls within bounds                                               | `amount` between `0` and `1000000`              |
| **Date in range**   | A date field falls within bounds — each an ISO date or `today` (relative)         | `expiry_date` on or after `today` (not expired) |
| **Arithmetic**      | A sum / difference / product of fields equals another field, within a tolerance   | `subtotal + tax = total` (±0.01)                |
| **Comparison**      | A field compares (`=`, `≠`, `>`, `≥`, `<`, `≤`) to a fixed value or another field | `end_date ≥ start_date`                         |
| **One of**          | A field's value is in an allowed set                                              | `currency` is one of `EUR`, `USD`, `GBP`        |
| **Pattern**         | A field matches a regular expression                                              | `iban` matches `^[A-Z]{2}\d{2}`                 |
| **Required**        | A field is present and not empty                                                  | `vendor_name` is filled in                      |

Each check resolves to **pass**, **fail**, or **inconclusive** (when the referenced field is missing or can't be read as the expected type). Numbers are read leniently — `"€1.234,56"` and `"1,234.56"` both parse — and dates accept the common written formats.

## Build a rule in Studio

1. Add a **Validate** step after the Extract step whose output you want to check.
2. Open the step, go to the **Rules** tab, and click **Add rule**.
3. Choose **AI** or **Deterministic** with the toggle.
   * **AI** — type the rule in plain language; reference a field with the field picker.
   * **Deterministic** — pick a check type, choose the field(s), and enter the values.
4. Set a **severity** (`error` or `warning`) and save.

Failures appear in the **Validation** tab of the results, next to the extracted data.

## Author rules via the API

Validation rules are part of the `validate` node in the typed-graph create body — no separate endpoint. A deterministic rule carries a `check`; an AI rule carries a `description`. Reference fields by their **name** as defined on the upstream extract.

```json theme={null}
{
  "name": "Invoice with checks",
  "nodes": [
    { "id": "parse_1", "type": "parse" },
    {
      "id": "extract_1",
      "type": "extract",
      "extraction_schema": {
        "fields": [
          { "name": "invoice_date", "description": "Invoice date.", "data_type": "date" },
          { "name": "subtotal", "description": "Subtotal before tax.", "data_type": "float" },
          { "name": "tax", "description": "Tax amount.", "data_type": "float" },
          { "name": "total", "description": "Grand total.", "data_type": "float" },
          { "name": "currency", "description": "ISO currency code.", "data_type": "string" }
        ]
      }
    },
    {
      "id": "validate_1",
      "type": "validate",
      "rules": [
        {
          "id": "not-expired",
          "kind": "deterministic",
          "severity": "error",
          "check": { "type": "date", "field": "invoice_date", "earliest": "2000-01-01", "latest": "today" }
        },
        {
          "id": "totals-add-up",
          "kind": "deterministic",
          "severity": "error",
          "check": { "type": "arithmetic", "operands": ["subtotal", "tax"], "operator": "sum", "equals": "total", "tolerance": 0.01 }
        },
        {
          "id": "known-currency",
          "kind": "deterministic",
          "severity": "warning",
          "check": { "type": "one_of", "field": "currency", "allowed": ["EUR", "USD", "GBP"] }
        },
        {
          "id": "vendor-legit",
          "kind": "ai",
          "severity": "warning",
          "description": "The vendor is a real, named company."
        }
      ]
    }
  ],
  "edges": [
    { "source": "parse_1", "target": "extract_1" },
    { "source": "extract_1", "target": "validate_1" }
  ]
}
```

### Check payloads

```jsonc theme={null}
{ "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": "required", "field": "vendor_name" }
```

`operator` defaults to `sum`; `tolerance` to `0`; `case_sensitive` to `false`. Unset date bounds are `null`.

## Author rules with the SDK

The check classes are exported from the workflow package; pass `ValidationRule`s to `.validate()`.

<CodeGroup>
  ```python Python theme={null}
  from anyformat.workflow import (
      Schema, ValidationRule,
      DateCheck, ArithmeticCheck, OneOfCheck,
  )

  wf = (
      client.workflow("Invoice with checks")
      .parse()
      .extract([
          Schema.date("invoice_date", "Invoice date."),
          Schema.float("subtotal", "Subtotal before tax."),
          Schema.float("tax", "Tax amount."),
          Schema.float("total", "Grand total."),
          Schema.string("currency", "ISO currency code."),
      ])
      .validate(
          ValidationRule(id="not-expired", kind="deterministic", severity="error",
              check=DateCheck(type="date", field="invoice_date", earliest="2000-01-01", latest="today")),
          ValidationRule(id="totals-add-up", kind="deterministic", severity="error",
              check=ArithmeticCheck(type="arithmetic", operands=["subtotal", "tax"], equals="total", tolerance=0.01)),
          ValidationRule(id="known-currency", kind="deterministic", severity="warning",
              check=OneOfCheck(type="one_of", field="currency", allowed=["EUR", "USD", "GBP"])),
          # An AI rule still works the same way:
          ValidationRule(id="vendor-legit", description="The vendor is a real, named company."),
      )
      .create()
  )
  ```

  ```typescript TypeScript theme={null}
  const wf = await client
    .workflow("Invoice with checks")
    .parse()
    .extract([
      Schema.date("invoice_date", "Invoice date."),
      Schema.float("subtotal", "Subtotal before tax."),
      Schema.float("tax", "Tax amount."),
      Schema.float("total", "Grand total."),
      Schema.string("currency", "ISO currency code."),
    ])
    .validate([
      { id: "not-expired", kind: "deterministic", severity: "error",
        check: { type: "date", field: "invoice_date", earliest: "2000-01-01", latest: "today" } },
      { id: "totals-add-up", kind: "deterministic", severity: "error",
        check: { type: "arithmetic", operands: ["subtotal", "tax"], operator: "sum", equals: "total", tolerance: 0.01 } },
      { id: "known-currency", kind: "deterministic", severity: "warning",
        check: { type: "one_of", field: "currency", allowed: ["EUR", "USD", "GBP"] } },
      { id: "vendor-legit", kind: "ai", description: "The vendor is a real, named company." },
    ])
    .create();
  ```
</CodeGroup>

<Note>
  Rules referencing a field by name require that field to exist on the upstream Extract step. The Studio also lets you reference fields you haven't saved yet by their persistent id — both forms resolve at run time.
</Note>

## What's next?

<CardGroup cols={2}>
  <Card title="Workflow steps" icon="diagram-project" href="/guides/studio/nodes">
    The five building blocks, including Validate.
  </Card>

  <Card title="How credits work" icon="coins" href="/concepts/how-credits-work">
    AI rules bill per rule; deterministic rules are free.
  </Card>
</CardGroup>
