Skip to main content
Restricted Access: This documentation is only accessible to @tenzo.ai and @salv.ai email addresses.

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.
In backend code an application is a CandidateCampaignInfo row, and campaign means job. Neither word appears in anything a customer sees.

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. 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: Those scores are summed across every finding, and the total is bucketed into the application’s overall level: 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. 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: 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.
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.

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.