A workflow template can carry screening rules. A rule watches one value from the verification, compares it with an operator, and takes an action when the comparison holds. You send rules on the `screening_rules` array of `POST /v1/workflow-templates` and `PATCH /v1/workflow-templates/{id}`.

`condition.field` is a string on the wire, so this page is the discovery path: it lists every field name the rule engine can resolve, every operator, every action, and every rule type. A field that is not on this page is refused at write time, and the refusal names the field.

Rules replace, they do not merge. Sending `screening_rules` on a PATCH replaces the template's whole rule set. Omitting it leaves the existing rules untouched, and sending `[]` clears them. A template holds at most 100 rules.

For the no-code version of the same feature, see [Add fraud signals to a workflow](/guides/workflows/workflow-templates-fraud-rules).

## The shape of a condition

A condition is either a single field comparison or a group of conditions combined with `AND` or `OR`.

```jsonc
// A field condition.
{ "kind": "field", "field": "fraud.botScore", "operator": "lte", "value": 10 }

// A group. `children` holds 1 to 20 conditions and groups can nest.
{
  "kind": "group",
  "op": "AND",
  "children": [
    { "kind": "field", "field": "fraud.asnIsDatacenter", "operator": "eq", "value": true },
    { "kind": "field", "field": "fraud.ipIsTorExit", "operator": "eq", "value": true },
  ],
}
```

`field` is 1 to 120 characters. `value` is a string, a number, a boolean, an array of strings, or an array of numbers, and an array holds at most 1000 entries.

A whole rule looks like this:

```json
{
  "name": "Datacenter connection",
  "ruleType": "ip_reputation",
  "condition": {
    "kind": "field",
    "field": "fraud.asnIsDatacenter",
    "operator": "eq",
    "value": true
  },
  "action": "review",
  "priority": 100,
  "isActive": true
}
```

`priority` is an integer from 0 to 10000 and defaults to 100. `isActive` defaults to `true`. `scoreAdjustment` is a number from -1 to 1 and is only meaningful with the `adjust_score` action.

## Operators

<!-- rule-operators:start -->

| Operator   | Holds when                                                      |
| ---------- | --------------------------------------------------------------- |
| `eq`       | The resolved value equals `value`.                              |
| `neq`      | The resolved value does not equal `value`.                      |
| `gt`       | The resolved value is greater than `value`.                     |
| `gte`      | The resolved value is greater than or equal to `value`.         |
| `lt`       | The resolved value is less than `value`.                        |
| `lte`      | The resolved value is less than or equal to `value`.            |
| `in`       | `value` is an array and it contains the resolved value.         |
| `not_in`   | `value` is an array and it does not contain the resolved value. |
| `contains` | The resolved value contains `value`.                            |
| `exists`   | The field resolved to a value at all. `value` is ignored.       |

<!-- rule-operators:end -->

Two notes on `contains` and array-valued fields. When the field resolves to an array (`applicant.givenNames` is the one to watch), `contains` tests exact membership of one element, not a substring: `applicant.givenNames contains "Maria"` matches `["Jose", "Maria"]` and does not match `["Mariana"]`. For substring matching on a name, use `applicant.name`. On the same array field, `eq` and `in` compare by reference and therefore never hold, and `neq` and `not_in` hold for every applicant who has given names, which makes them always-true rules rather than useful ones.

A field that resolves to nothing (the applicant never supplied it, or the signal was not collected) makes the rule not fire, except under `exists`, which reports `false`.

## Fields

### Applicant and finding fields

<!-- rule-field-catalog:start -->

| Field                           | Type     | What it resolves to                                                                                  |
| ------------------------------- | -------- | ---------------------------------------------------------------------------------------------------- |
| `applicant.legalName`           | string   | The captured legal name, verbatim. Empty for an applicant whose name arrived as separate components. |
| `applicant.name`                | string   | The name the platform holds: the captured legal name, or the composition of the name components.     |
| `applicant.familyName`          | string   | The primary identifier from the document. Can be more than one word, and holds a mononym in full.    |
| `applicant.givenNames`          | string[] | The ordered secondary identifiers from the document.                                                 |
| `applicant.email`               | string   | The applicant's email address.                                                                       |
| `applicant.email.domain`        | string   | The lowercased domain part of the applicant's email address.                                         |
| `applicant.dateOfBirth`         | string   | The applicant's date of birth.                                                                       |
| `applicant.age`                 | number   | The applicant's age in whole years.                                                                  |
| `applicant.address.country`     | string   | The two-letter country code of the applicant's address.                                              |
| `applicant.phone`               | string   | The applicant's phone number.                                                                        |
| `finding.count`                 | number   | How many findings the verification has produced.                                                     |
| `finding.severity.high.count`   | number   | How many of those findings are high severity.                                                        |
| `finding.severity.medium.count` | number   | How many are medium severity.                                                                        |
| `finding.severity.low.count`    | number   | How many are low severity.                                                                           |

### Device and network fields

These resolve only when the workflow template has device intelligence switched on. On a workflow without it they resolve to nothing, so a rule built on them does not fire.

| Field                               | Type    | What it resolves to                                                                            |
| ----------------------------------- | ------- | ---------------------------------------------------------------------------------------------- |
| `fraud.botScore`                    | number  | How human the connection looks. Lower means more automated.                                    |
| `fraud.threatScore`                 | number  | The reputation score of the connecting address.                                                |
| `fraud.country`                     | string  | The two-letter country code of the connecting address.                                         |
| `fraud.asn`                         | number  | The autonomous system number the connection came from.                                         |
| `fraud.asOrganization`              | string  | The name of the network that owns that autonomous system.                                      |
| `fraud.asnIsDatacenter`             | boolean | The connection came from a datacenter network rather than a consumer one.                      |
| `fraud.verifiedBotCategory`         | string  | The category of a recognized, declared bot. Absent for ordinary traffic.                       |
| `fraud.ja4`                         | string  | The TLS fingerprint of the connection.                                                         |
| `fraud.ipReputation`                | boolean | The connecting address is rated poor: datacenter, proxy, exit node, or a bad reputation score. |
| `fraud.ipIsTorExit`                 | boolean | The connecting address is a known anonymizing exit node.                                       |
| `fraud.repeatApplicant`             | boolean | This person has been seen applying before.                                                     |
| `fraud.emailDisposable`             | boolean | The email address belongs to a disposable-address service.                                     |
| `fraud.emailDomainDga`              | boolean | The email domain looks machine-generated.                                                      |
| `fraud.emailUsernameGrammarInvalid` | boolean | The part before the `@` does not read like a real name.                                        |
| `fraud.geoTimezoneMismatch`         | boolean | The browser's timezone does not match the timezone of the connecting address.                  |
| `fraud.deviceTouchPointMismatch`    | boolean | The device claims a form factor its touch capability contradicts.                              |
| `fraud.countryMismatch`             | boolean | The country of the connecting address differs from the country the applicant declared.         |
| `fraud.impossibleTravel`            | boolean | Two attempts from locations too far apart, too fast, for one person.                           |
| `fraud.ringSize`                    | number  | How many verifications link together as one coordinated group.                                 |
| `fraud.ringTruncated`               | boolean | The group was larger than the walk limit, so `fraud.ringSize` is a floor rather than a total.  |
| `fraud.captureAbsent`               | boolean | No connection context was captured at all for this verification.                               |
| `fraud.networkContextChanged`       | boolean | The network at submit differs materially from the network the applicant started on.            |
| `device.velocity.distinctIps`       | number  | How many distinct addresses this device has been seen on.                                      |
| `device.velocity.sessionCount`      | number  | How many verifications this device has started.                                                |
| `device.behavior.pasteCount`        | number  | How many times the applicant pasted into the form.                                             |
| `device.behavior.totalSeconds`      | number  | How long the applicant spent on the form.                                                      |
| `device.behavior.minStepSeconds`    | number  | The shortest time spent on any one step.                                                       |
| `device.behavior.headless`          | boolean | The browser is running under automation control.                                               |
| `device.behavior.collectionAbsent`  | boolean | No behavioral measurements arrived for this verification.                                      |

<!-- rule-field-catalog:end -->

The two `*Absent` fields report an absence of evidence rather than a bad signal, so treat them as a reason to look, not as a finding.

### Pattern fields

Four more families take a name you fill in. They follow a fixed shape and anything outside that shape is refused.

| Shape                                       | Resolves to                                                                               |
| ------------------------------------------- | ----------------------------------------------------------------------------------------- |
| `check.<checkType>.outcome`                 | The outcome of that check on this verification: `pass`, `fail`, `review`, or `error`.     |
| `any.finding.<subjectType>.<field>`         | The first defined value of `<field>` across the findings with that subject type.          |
| `all.finding.<subjectType>.<field>`         | Every defined value of `<field>` across those findings, as an array.                      |
| `count_where.finding.<subjectType>.<field>` | How many of those findings have a truthy `<field>`.                                       |
| `session.metadata.<key>`                    | The value of that key in the workflow template's metadata. Letters, digits, and `_` only. |

`<checkType>` is a check type, exactly as it appears in the workflow document and in the session read. `<subjectType>` is a finding's `subject_type`, and `<field>` is either a top-level finding field (`id`, `subjectType`, `severity`, `sourceAuthority`, `findingType`) or a key inside that finding's evidence.

Worked examples:

<!-- rule-field-patterns:start -->

| Example                                        | Meaning                                                        |
| ---------------------------------------------- | -------------------------------------------------------------- |
| `check.credit_history.outcome`                 | The outcome of the credit-history check.                       |
| `check.id_verification.outcome`                | The outcome of the identity check.                             |
| `any.finding.credit_record.recordType`         | The type of the first adverse record on the credit file.       |
| `all.finding.person.severity`                  | The severities of every watchlist finding about the person.    |
| `count_where.finding.credit_record.recordType` | How many adverse records the credit file produced.             |
| `session.metadata.regionRiskScore`             | The `regionRiskScore` key on the workflow template's metadata. |

<!-- rule-field-patterns:end -->

`session.metadata.<key>` resolves strings, numbers, booleans, and arrays of strings or numbers. A key whose value is an object, or an array mixing types, fails the verification rather than resolving quietly, so keep template metadata flat.

## Actions

<!-- rule-actions:start -->

| Action         | What the rule does when it holds                                                                          |
| -------------- | --------------------------------------------------------------------------------------------------------- |
| `tag`          | Records a reviewer-visible annotation. It does not route the verification and does not drive its outcome. |
| `review`       | Routes the verification to manual review.                                                                 |
| `adjust_score` | Adds `scoreAdjustment` to the weighing that decides whether the verification needs manual review.         |
| `approve`      | Marks the verification approved.                                                                          |
| `decline`      | Declines the verification. Refused at write time for most rules: see below.                               |

<!-- rule-actions:end -->

## Rule types

`ruleType` labels what a rule is about. It does not restrict which fields the rule may read: any rule type can carry any resolvable field. What it does decide is whether `decline` is allowed.

<!-- rule-types:start -->

| Rule type              | May carry `decline`? |
| ---------------------- | -------------------- |
| `country_block`        | Yes                  |
| `score_threshold`      | Yes                  |
| `age_restriction`      | Yes                  |
| `geolocation_mismatch` | Yes                  |
| `watchlist_hit`        | Yes                  |
| `adverse_media_hit`    | Yes                  |
| `custom`               | Yes                  |
| `repeat_applicant`     | No                   |
| `device_velocity`      | No                   |
| `ip_reputation`        | No                   |
| `bot_signal`           | No                   |
| `impossible_travel`    | No                   |
| `identity_ring`        | No                   |
| `email_risk`           | No                   |
| `behavior_anomaly`     | No                   |
| `geo_mismatch`         | No                   |
| `credit_history`       | No                   |

<!-- rule-types:end -->

"Yes" here means the write is accepted. It does not mean a rule declines an applicant by itself: a fired `decline` is floored to manual review before it reaches the verification's outcome, in both regions, for every rule type. What the write-time refusal adds is that a rule cannot be saved claiming an action the engine would not honor.

## Fields that can never carry a decline

Separately from the rule type, a rule whose condition touches a sensitive attribute cannot carry `decline`, whatever its rule type. Declining on age, country, postal code, gender, or a name pattern carries fair-lending and fair-housing exposure (ECOA and Regulation B, and Fair Housing Act disparate impact), so the platform refuses to save the rule and asks for `tag` or `review` instead. A reviewer's manual decline is then a documented human decision rather than an automated one.

<!-- rule-sensitive-fields:start -->

| Sensitive attribute            | Authorable in a rule? |
| ------------------------------ | --------------------- |
| `applicant.age`                | Yes                   |
| `applicant.address.country`    | Yes                   |
| `applicant.legalName`          | Yes                   |
| `applicant.name`               | Yes                   |
| `applicant.familyName`         | Yes                   |
| `applicant.givenNames`         | Yes                   |
| `applicant.address.postalCode` | No                    |
| `applicant.gender`             | No                    |

<!-- rule-sensitive-fields:end -->

The last two are not resolvable fields at all, so a rule cannot read them. They are listed as sensitive anyway, so that if either ever becomes readable it is already covered.

## What a refused write looks like

Every refusal below is an HTTP `422` carrying the standard error envelope, with `error.code` set to `validation_error`. The envelope also carries `type`, `doc_url`, and `request_id`, which are the same on every error; the [Error reference](/api/errors) shows them in full. The examples here show only the fields that differ per refusal.

### An action the rule type may not carry

The offending rule is identified by its index in the array you sent.

```json
{
  "error": {
    "code": "validation_error",
    "message": "Invalid request",
    "details": {
      "issues": [
        {
          "code": "custom",
          "message": "Decline action is not allowed on a credit history rule. A credit report is a consumer report, so declining on it is an adverse action a person has to take and notice (FCRA 15 U.S.C. 1681m). Use action: 'tag', 'review', or 'adjust_score' instead, then decline from the review queue where the required notices are shown.",
          "path": ["screening_rules", 0, "action"]
        }
      ]
    }
  }
}
```

A fraud rule type refused for the same reason carries the other message, and the wording is deliberately different because the reason is:

```json
{
  "code": "custom",
  "message": "Decline action is not allowed on a fraud rule type (no auto-decline of applicants, EU Art. 22 / D9). Use action: 'tag', 'review', or 'adjust_score' instead.",
  "path": ["screening_rules", 0, "action"]
}
```

A rule refused for touching a sensitive attribute carries a third message, also on `path: ["screening_rules", <index>, "action"]`, naming the attributes listed above and pointing at `tag` or `review`.

In all three cases the fix is the same: change `action`, save, and let a reviewer make the decline from the review queue. For what a decline on a credit report obliges the reviewer to do, see [Background-check decisions (FCRA)](/guides/verification/reviewer-background-checks-fcra).

### A field the engine cannot resolve

A `condition.field` that is not on this page (including inside a nested group) is refused before anything is written, and the response names the field:

```json
{
  "error": {
    "code": "validation_error",
    "message": "Unresolvable screening-rule field",
    "details": { "field": "applicant.creditScore" }
  }
}
```

Only the field path is returned, never an applicant value. Fix the field name against the tables above and send the write again. The refusal is all-or-nothing: no part of the template is saved and the existing rules are untouched.

## Related

- [Add fraud signals to a workflow](/guides/workflows/workflow-templates-fraud-rules) for the console editor.
- [Choose the right check types](/guides/workflows/workflow-templates-check-types) for what each `checkType` does.
- [Credit history](/developers/sdks/credit-history) for the credit step, including what its findings look like.
- [Modes and regions](/developers/modes-and-regions) for exercising rules in test mode.