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

# Fraud risk

> How applications are scored for fraud, what each signal checks, and where it is turned on today

<Warning>
  **Restricted Access**: This documentation is only accessible to @tenzo.ai and @salv.ai email addresses.
</Warning>

## What it is

Fraud risk is a set of independent checks that run over a single application. Each check either finds
something suspicious, runs cleanly, or cannot run at all because the data it needs is missing. The
findings are combined into one number, that number is bucketed into a level (Low, Medium, High,
Extreme), and the level is stored on the application. A job can be configured to close an application
automatically once its level reaches a chosen threshold.

The unit of evaluation is an **application**: one candidate on one job. Some checks look only at the
candidate in isolation (their phone number, their resume, their address). Others look across
applications, to find several candidates sharing a contact detail.

<Note>
  In backend code an application is a `CandidateCampaignInfo` row, and `campaign` means job. Neither
  word appears in anything a customer sees.
</Note>

## When it runs

Evaluation covers every inbound applicant on a job whose fraud risk setting is on; the setting is
off until someone turns it on, in bulk with `enable_fraud_risk_evaluation`. Being evaluated is
separate from being shown: see Who sees it below. It runs at three points:

* **A new application arrives.** The checks run inline, before the application continues to resume
  review, so an auto-reject happens before any outreach. Sourced candidates and alternative-role
  additions are skipped, since neither is an inbound application.
* **A candidate's other applications are refreshed.** After the inline run, the candidate's other
  non-terminal applications are re-evaluated in the background so they pick up the same findings.
* **A shared-contact cluster changes.** When a new applicant joins a cluster (same email, phone or
  address as existing candidates), every other member of that cluster is queued for re-evaluation, so
  their scores reflect the new member. A background worker drains that queue every 30 seconds.

## The signals

The catalog contains fifteen signals. The first eight look at the candidate alone; the last seven
compare the candidate against other applicants. Resume history runs in the background after a new
application is created and remains advisory, so it does not delay the default inline evaluation or
affect the aggregate score.

| Signal                     | What it checks                                                                                                              | Levels it can emit                                                                                                                     | External call                    |
| -------------------------- | --------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------- |
| VOIP phone                 | Whether the application phone number is a VOIP line                                                                         | Medium                                                                                                                                 | Twilio Lookup (paid)             |
| Name match                 | The application name against the name on the resume, judged by an LLM                                                       | Medium, High                                                                                                                           | LLM (paid)                       |
| GitHub profile             | A GitHub profile claimed on the resume: does it exist, and how new is it                                                    | Medium (under 30 days old), High (does not exist)                                                                                      | GitHub API (free)                |
| LinkedIn profile           | A claimed LinkedIn profile, looked up through a web search, and the name on it                                              | Medium, High (name mismatch), High (profile not found)                                                                                 | Web search (paid), LLM (paid)    |
| Identity match             | The application name against the carrier's owner-of-record for that phone number                                            | Medium (one name differs), High (both differ)                                                                                          | Twilio (paid)                    |
| Address validation         | The resume address against Google's address validation: does it exist, does it agree with the application                   | Low (street unresolved, undeliverable, mail drop), Medium (city, ZIP, or ZIP-format problem), High (address not found, state mismatch) | Google Address Validation (paid) |
| Address consistency        | The distance between the resume address and the address already on file                                                     | Medium (over 100 miles), High (over 500 miles)                                                                                         | Google Geocoding (paid)          |
| Resume history changes     | Material changes to employment titles, companies, or historical dates across the ten most recent unique ATS resume versions | Medium (advisory)                                                                                                                      | LLM (paid)                       |
| Shared email               | How many other candidates in the same org use this email                                                                    | Medium (4 others), High (8 others)                                                                                                     | None                             |
| Shared phone               | How many other candidates in the same org use this phone number                                                             | Medium (5 others), High (10 others)                                                                                                    | None                             |
| Shared address             | How many other candidates in the same org use this address                                                                  | Medium (4 others), High (8 others)                                                                                                     | None                             |
| Shared IP                  | How many other candidates in the same org applied from this IP                                                              | Medium (6 others), High (15 others)                                                                                                    | None                             |
| Shared email (cross-org)   | Different-looking candidates using the same email at other organizations                                                    | Medium (4 others), High (8 others)                                                                                                     | None                             |
| Shared phone (cross-org)   | Different-looking candidates using the same phone at other organizations                                                    | Medium (5 others), High (10 others)                                                                                                    | None                             |
| Shared address (cross-org) | Different-looking candidates using the same address at other organizations                                                  | Medium (4 others), High (8 others)                                                                                                     | None                             |

Counts are of *other* candidates sharing the value; the applicant being scored is excluded. Internal
test emails (tenzo.ai, salv.ai) and an allowlist of test phone numbers are never fingerprinted, so
they cannot form a cluster.

Cross-org findings never name the other organization or the matching candidate. They report a count
and nothing else. Before counting, Tenzo applies the same contact-plus-name matching used when a new
candidate is deduplicated inside one organization. A matching contact record whose name similarity
is above the dedupe threshold in either comparison direction is treated as the same person and
excluded; records at or below the threshold in both directions count as different candidates. The
cross-org check does not compare resumes.

**Shared IP is advisory.** It is graded and shown to the reviewer, but excluded from the score, so it
can never cause an auto-reject. Kiosks, shared offices, NAT and VPNs all produce legitimately shared
IPs.

Every check ends in one of three states, and the three are distinct:

* **Fail**: it ran and found something.
* **Pass**: it ran and found nothing. This is a real result and is stored.
* **Not evaluated**: it could not run, because the data it needs was absent (no resume, no phone
  number, no claimed profile). Nothing is stored.

The distinction between pass and not-evaluated matters internally: a stored pass marks the check
cached, so it never runs again for that candidate, and it feeds the all-clear into the score. A check
that never compared anything must not be recorded as one that compared and cleared.

## Scoring

Each finding contributes a score based on its level:

| Level   | Score |
| ------- | ----- |
| Low     | 10    |
| Medium  | 20    |
| High    | 40    |
| Extreme | 100   |

Those scores are summed across every finding, and the total is bucketed into the application's
overall level:

| Total score | Overall level |
| ----------- | ------------- |
| 100 or more | Extreme       |
| 80 to 99    | High          |
| 40 to 79    | Medium        |
| under 40    | Low           |

All signals currently weigh the same. A per-signal weight table exists as the tuning surface, but it
is empty.

Signals that mostly report the same fact are grouped, and a group counts once, at its strongest
finding. The one group today is the VOIP phone check and the phone-record identity match. Most VOIP
numbers have no subscriber record, so the identity match fails on them far more often than on mobile
numbers (71% against 7% in production). Counted separately, a VOIP number alone scored 60, and one
more High finding reached Extreme. Both findings still appear to the reviewer.

A consequence of these cutoffs is that **a single signal rarely escalates an application on its own**.
One High finding scores 40, which buckets to Medium. One Medium scores 20, which buckets to Low.
Levels are designed to be reached by accumulation, not by any one check. This matters most for orgs
whose auto-reject threshold is High: two High findings, or one High plus two Mediums, is what it
takes.

The numbers are preliminary and expected to be tuned against live data.

## What a reviewer sees

When the `fraud_signals_tab` flag is on, the UI offers an overall-risk badge beside the candidate's
name and a **Fraud Risk** column on both a job's **Candidates** table and the organization-wide
**Interviews** table. The server only populates those surfaces when the job setting, org default
setting and flag are all on (or for a support user in Full Access mode). Otherwise the badge is
omitted and the column shows a dash.

A detailed fraud signals panel appears in the application's Fraud Signals tab only when the job
setting, the org default setting and the `fraud_signals_tab` flag are all on (support users in Full
Access mode see it for any org). The tab does not depend on ID verification: it appears for phone
screens and for video interviews that never ran an identity check, and any identity-verification
result is listed below the signals. It has three states:

* **Findings.** Only the signals that flagged are listed, each with its specific finding ("4 different
  candidates are using this same phone number", "Name on application differs greatly from resume").
* **All checks passed.** Checks ran and none flagged.
* **Nothing at all.** If no check was evaluated for this candidate, the section is not rendered.

The panel is a list of findings, not a checklist. Signals that passed are never drawn individually, so
a reviewer cannot tell from it which specific checks ran and cleared. Worth knowing before demoing it.

Findings are written in plain language. Raw provider verdicts, LLM reasoning, and internal identifiers
are deliberately not surfaced.

Two candidate filters are available when the flag is on (or to a support user in Full Access mode).
The server rewrites unauthorized filters to match nothing, so a filter cannot read out grades a page
does not show:

* **Fraud risk**, on a job's candidate list. Picks a minimum level, and defaults to High when added.
* **Application Fraud Risk**, on notifications. Selects specific levels, optionally including
  applications with no score.

There is no reviewer-facing way to override a signal. The signal editor that exists is a developer
tool, reachable only from the internal fraud data page, and it writes seeded values for local testing.

## What it can do automatically

* **Auto-reject.** A job can set a minimum level. An application reaching it moves to the
  `FRAUD_REJECTED` stage, which is terminal, and an explanatory note is written to the ATS. An
  application already in a terminal stage is left alone.
* **Gate ID verification.** A job running ID verification on web calls can require it only for
  applications at or above a fraud level, rather than for everyone. An application with no stored
  level is skipped rather than verified. Shared links and test calls ignore the threshold, since
  neither has an applicant to assess.

## What it does not do

* It does not gate SMS. Whether a number can receive a text is decided purely by whether the line
  physically supports messaging, and that decision is deliberately kept separate from fraud scoring.
* It does not block a call or an interview on its own. Only the auto-reject threshold ends an
  application, and only when the job configures one.
* It does not share candidate identity across organizations. Cross-org matching reports counts only.

## Who sees it

Three switches must all be on before a recruiter sees a finding: the job's fraud risk setting, the
org default fraud risk setting, and the `fraud_signals_tab` flag. A support user in Full Access mode
sees an org's findings whether or not that org has opted in, which is how support reads an org
exactly as that org will once the flag goes on.

Because grading and showing are separate switches, an org can be graded first and shown the results
later. Nothing is re-evaluated when the flag flips, so whatever was collected in the meantime
appears immediately and costs no further provider calls.

## Where it is on today

Signal evaluation follows each job's fraud risk setting. The flags gate what a recruiter is shown
and the cross-candidate signals.

| Flag                       | What it gates                                                                                                                                                                                                                                                                                          | Orgs with it on      |
| -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------------------- |
| `fraud_signals_tab`        | Everything a recruiter sees of fraud risk: the signals panel, the overall badge beside the candidate's name, the **Fraud Risk** columns in the per-job **Candidates** and organization-wide **Interviews** tables, the two candidate filters, the shared-contact drill-down, and the grades in AI chat | Tenzo, WD-40 Company |
| `cross_org_fraud_matching` | The three cross-org shared-contact signals                                                                                                                                                                                                                                                             | none                 |

Both flags default to off, and no global default row is set, so an org without an explicit row inherits
off.

Cross-org matching is reciprocal by design: an org only matches against other orgs that have also
turned it on. Since no org has, the three cross-org signals do not fire anywhere today.

Each new application schedules a background comparison of the ten most recent unique resume
versions for supported ATSes. It does not delay application creation, does not run on generic fraud
re-evaluations, and cannot change risk or trigger auto-close. Tenzo parses the files locally and
sends the extracted resume text to the configured LLM provider. It retains structured changes and a
manifest fingerprint, not the files.

Roughly 63,000 applications across 11 orgs carry a stored level:

| Org                 | Scored | Low    | Medium | High  | Extreme |
| ------------------- | ------ | ------ | ------ | ----- | ------- |
| Game 7              | 40,665 | 35,441 | 555    | 4,669 | 0       |
| Visionaire Partners | 14,943 | 12,319 | 1,243  | 1,373 | 8       |
| 4 Corner Resources  | 6,246  | 5,618  | 477    | 148   | 3       |
| Innova              | 774    | 415    | 25     | 334   | 0       |
| CH Robinson         | 251    | 244    | 5      | 2     | 0       |
| Others (6 orgs)     | 217    | 180    | 16     | 19    | 2       |

Because almost none of these orgs have the cross-candidate flag on, essentially every one of those
scores comes from the seven scoring candidate-scoped signals alone. Resume history is an eighth
candidate-scoped signal, but it is advisory and cannot affect those scores.

<Note>
  More High than Medium is not the shape the cutoffs above predict, and the High population is
  currently unreliable. See Known gaps in
  [Fraud risk operations](/internal/fraud-risk-operations).
</Note>

## Deeper reading

The engineer-facing documentation lives with the code and goes considerably further than this page:

* `server/fraud_risk/README.md`: the scoring model, the evaluator contract, the caching mechanism,
  and how to add a signal.
* `server/fraud_risk/EVALUATORS.md`: one section per signal, covering every outcome it can return.
* `server/fraud_risk/OPERATIONS.md`: the full runbook.
* `ui/src/components/transcript/FRAUD_SIGNALS.md`: the reviewer panel.

For rollout, tuning and troubleshooting, see [Fraud risk operations](/internal/fraud-risk-operations).
