Screening rule fields
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.
The shape of a condition
Section titled “The shape of a condition”A condition is either a single field comparison or a group of conditions combined with AND or OR.
// 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:
{ "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
Section titled “Operators”| 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. |
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
Section titled “Fields”Applicant and finding fields
Section titled “Applicant and finding fields”| 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
Section titled “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. |
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
Section titled “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:
| 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. |
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
Section titled “Actions”| 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 types
Section titled “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 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 |
“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
Section titled “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.
| 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 |
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
Section titled “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 shows them in full. The examples here show only the fields that differ per refusal.
An action the rule type may not carry
Section titled “An action the rule type may not carry”The offending rule is identified by its index in the array you sent.
{ "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:
{ "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).
A field the engine cannot resolve
Section titled “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:
{ "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
Section titled “Related”- Add fraud signals to a workflow for the console editor.
- Choose the right check types for what each
checkTypedoes. - Credit history for the credit step, including what its findings look like.
- Modes and regions for exercising rules in test mode.