Detect insider threats in employee account access
Grayson triages insider risk alerts: whether an employee opened customer accounts without a business reason, why, and whether to close, refer or suspend.
Grayson triages alerts on employee access to customer accounts: whether the employee opened or changed accounts without a business reason, how likely they are to be helping outside fraudsters, what best explains the activity, and whether to close the alert, refer it to your insider risk team or suspend the employee's access now. It reads your access logs, the calls, chats and tickets around each lookup, and what happened to the accounts afterward, and costs about $0.06 per 1,000 alerts.
- Decides: Decide whether an employee's account access is work, snooping or insider fraud, and close, refer or suspend.
- Call it: When access monitoring flags an employee's activity on customer accounts
- Questions: 1 yes/no, 1 score, 2 choice
- Cost: $0.000060 per decision, $0.06 per 1,000, for this example's 1,691 input tokens
- Latency: 181 ms for this example, the median of 5 calls through api.finic.ai from US-West
Example
A card services agent opened two coworkers' accounts three times in one week with no call, chat or ticket; two other unlinked lookups, including a $412,880 premier account, line up with a chat transferred from another queue and a dropped call that was written up late.
| Question | Grayson's answer |
|---|---|
access_without_business_reason | Yes, P(yes) 75% |
enabling_fraud_likelihood | Very unlikely (under 10%), 93% |
explanation | snooping, 87% |
action | refer, 54% |
Each percentage is Grayson's probability for the answer shown; for a yes/no question it's the probability of yes. A multiple-choice answer lists the options at 50% or more.
{
"model": "grayson-1",
"context": {
"alert": {
"id": "IR-ALR-20417",
"source": "Employee access monitoring over core banking and CRM access logs",
"created": "2026-10-05T13:00:00Z",
"window_start": "2026-09-28T00:00:00Z",
"window_end": "2026-10-04T23:59:59Z",
"rules": [
"IR-04: 3 or more account lookups in 7 days with no call, chat, ticket or case linked within 30 minutes (5 in this window)",
"IR-09: Lookup of an account held by an employee (3 lookups of 2 accounts)",
"IR-12: Lookup of an account with a balance over $250,000 that is outside the employee's queue (1)"
]
},
"employee": {
"employee_id": "E-30582",
"role": "Contact center agent, consumer card services",
"hired": "2023-02-13",
"queue": "Inbound card services calls and chats: card declines, lost and stolen cards, replacements",
"scheduled_shift": "Monday to Friday, 15:00 to 23:30 UTC",
"assigned_workstation": "WS-CC2-0418",
"entitlements": "View customer profiles, balances and transaction history; block, unblock and replace debit cards; change phone, email and mailing address after identity verification; reverse card fees up to $50",
"privacy_training": "Annual privacy and acceptable-use training completed 2026-03-10. It states that employees must not open their own account, or a coworker's, relative's or household member's account, in work systems.",
"prior_alerts": [
{
"id": "IR-ALR-11873",
"created": "2025-05-19",
"rule": "IR-04",
"outcome": "Closed as legitimate: callback tickets logged after the 30-minute window"
}
]
},
"activity_summary": {
"account_lookups": 431,
"lookups_linked_to_a_contact_or_case": 426,
"account_changes": 38,
"account_changes_linked_to_a_contact": 38,
"sessions_outside_scheduled_shift": 0,
"sessions_from_other_workstations": 0,
"exports_prints_or_screen_captures": 0
},
"unlinked_lookups": [
{
"ts": "2026-09-29T15:51:00Z",
"account": "Premier checking x8812",
"balance_usd": 412880.17,
"account_holder": "Customer, not an employee",
"viewed": "Profile, balance, debit card status and pending card transactions",
"duration_minutes": 3,
"changes": "None",
"contacts_with_this_customer": "Chat CH-55120 started 2026-09-29T15:46:00Z in the premier banking queue about a pending card charge at a hotel, transferred to E-30582 at 15:50:00Z, ended 16:02:00Z. The chat system records it under the premier banking queue."
},
{
"ts": "2026-09-29T17:42:00Z",
"account": "Checking x7720",
"balance_usd": 2318.4,
"account_holder": "Employee E-30611, contact center agent on the same card services team",
"viewed": "Profile, balance, 30 days of transaction history, and the details of 2 transactions",
"duration_minutes": 4,
"employee_phone_status": "Not on a call (after-call work)",
"changes": "None",
"contacts_with_this_customer": "None in the window; no ticket or case"
},
{
"ts": "2026-09-30T20:14:00Z",
"account": "Checking x2290",
"balance_usd": 1406.92,
"account_holder": "Customer, not an employee",
"viewed": "Debit card status and replacement card shipping",
"duration_minutes": 2,
"changes": "None",
"contacts_with_this_customer": "Inbound call 20:09:00Z to 20:12:00Z from the phone number on file, answered by E-30582, dropped before a CRM contact record was created. Ticket TK-88213 created by E-30582 at 20:57:00Z: 'Cust called re replacement card, call dropped. Card shipped 9/28, delivery by 10/3. Tried callback, no answer.'"
},
{
"ts": "2026-10-02T18:20:00Z",
"account": "Checking x7720",
"balance_usd": 1960.15,
"account_holder": "Employee E-30611 (same account as 2026-09-29)",
"viewed": "30 days of transaction history",
"duration_minutes": 2,
"employee_phone_status": "Not on a call (available)",
"changes": "None",
"contacts_with_this_customer": "None in the window; no ticket or case"
},
{
"ts": "2026-10-02T18:23:00Z",
"account": "Savings x6047",
"balance_usd": 8950,
"account_holder": "Employee E-29877, contact center agent on another card services team at the same site",
"viewed": "Profile and balance",
"duration_minutes": 1,
"employee_phone_status": "Not on a call (available)",
"changes": "None",
"contacts_with_this_customer": "None in the window; no ticket or case"
}
],
"after_the_lookups": "Through 2026-10-05, none of the 5 accounts has had a contact-detail change, password reset, new device, new payee, card replacement request or fraud claim."
},
"questions": {
"access_without_business_reason": {
"type": "noul",
"instructions": "Did the employee open or change any customer account in this alert without a business reason, meaning that no customer contact, ticket, assigned case or other work item explains it?"
},
"enabling_fraud_likelihood": {
"type": "score",
"instructions": "How likely is it that the employee is helping people outside the institution commit fraud against customer accounts, for example by passing on account details or changing accounts so that others can take them over?",
"levels": [
"Very unlikely (under 10%)",
"Unlikely (10-40%)",
"Uncertain (40-60%)",
"Likely (60-90%)",
"Very likely (over 90%)"
]
},
"explanation": {
"type": "choice",
"instructions": "What is the most likely explanation for the employee's activity flagged in this alert?",
"options": {
"legitimate_work": "Legitimate work: customer contacts, tickets, assigned cases, training or supervisory review explain the activity",
"snooping": "Curiosity or snooping: the employee viewed accounts with no business reason, without changing them or using what they saw for gain",
"self_dealing": "Self-dealing: the employee used their access to benefit themselves or someone linked to them, for example by reversing fees or raising limits on their own or a household member's account",
"enabling_fraud": "Enabling outside fraud: the employee looked up or changed accounts so that people outside the institution could take them over or take money from them",
"policy_mistake": "Policy mistake: the employee was handling a real customer's real request but skipped a required step, such as logging the contact or verifying identity"
}
},
"action": {
"type": "choice",
"instructions": "What should happen with this alert now?",
"options": {
"no_action": "Close the alert as legitimate activity, with no action against the employee",
"refer": "Refer the employee to the insider risk team for investigation, leaving their system access in place for now",
"suspend": "Suspend the employee's system access now and escalate to the insider risk team and HR"
}
}
}
}Probabilities are shortened to four decimals here; responses carry full precision.
{
"id": "dec_da49149c26e34792af648047a786b7e1",
"model": "grayson-1",
"answers": {
"access_without_business_reason": {
"type": "noul",
"value": true,
"probability": 0.7549
},
"enabling_fraud_likelihood": {
"type": "score",
"value": 0.111,
"level": "Very unlikely (under 10%)",
"probabilities": [
0.9319,
0.0464,
0.0081,
0.0055,
0.0081
]
},
"explanation": {
"type": "choice",
"value": "snooping",
"probabilities": {
"legitimate_work": 0.0492,
"snooping": 0.8727,
"self_dealing": 0.0434,
"enabling_fraud": 0.0141,
"policy_mistake": 0.0205
}
},
"action": {
"type": "choice",
"value": "refer",
"probabilities": {
"no_action": 0.136,
"refer": 0.5378,
"suspend": 0.3262
}
}
},
"usage": {
"input_tokens": 1691
}
}access_without_business_reason: below a threshold chosen on your past alerts, close the alert and keep the answers with it; most alerts should end here.enabling_fraud_likelihood: when "Likely" plus "Very likely" is high, protect the customers first by restoring changed contact details and ending the accounts' sessions.explanationandaction: send snooping and policy mistakes to the manager and HR, self-dealing and enabling fraud to an investigator; a person confirms every suspension.
Call it from your code
Save request.json and send it with your API key in GRAYSON_API_KEY:
curl https://api.finic.ai/v1/decide \
-H "Authorization: Bearer $GRAYSON_API_KEY" \
-H "Content-Type: application/json" \
--data @request.jsonThe problem
Contact-center, branch and operations staff need wide access to do their jobs, so a lookup that passes an account's details to a fraud ring looks like hundreds of ordinary ones. Access rules fire mostly on legitimate work, such as transferred chats and late tickets, and can't see that two accounts a contractor opened overnight were taken over the next day.
What to send
Send the alert with the records an insider risk analyst would pull, identifying the employee by ID, role and access rather than by name:
- The employee's job. Role, queue, shift and entitlements; an out-of-queue lookup means something only against these.
- Each flagged lookup and change. Reading a coworker's transactions is a different act from changing a stranger's phone number.
- The contacts around each event, from every channel. Most unlinked lookups are explained by a transferred chat, dropped call or late ticket.
- Who holds each account. Coworkers' and restricted accounts, and the high-balance or dormant ones fraud rings ask insiders to find.
- What happened to the accounts afterward. A takeover a day after an unrequested phone number change is the strongest evidence.
- Data leaving the workstation. Screen captures during an unlinked session suggest the information is being passed on.
Add your own criteria
Each institution decides in advance where referral ends and same-day suspension begins, and many treat accounts held by employees and directors as restricted records, so put your standard in the context in your team's words. This one makes any unlinked lookup of an employee's account a Category 1 violation with same-day suspension, whatever reason the employee gives and however briefly the record was open.
Your restricted-records rule adds this to the context:
{
"insider_risk_standard": "Insider Risk Standard IR-1, section 5 (restricted records), revised 2026-04-01. Accounts held by employees, contractors and directors are restricted records. Opening a restricted record with no linked call, chat, ticket or assigned case is a Category 1 access violation, whatever reason the employee gives, however briefly the record was open and whether or not anything was changed. For a Category 1 violation, suspend the employee's system access the same day and escalate to Insider Risk and HR; access stays suspended until the investigation closes. Unlinked lookups of other customers' accounts are Category 2: refer them to Insider Risk without suspending access."
}| Question | Without | With your restricted-records rule |
|---|---|---|
access_without_business_reason | Yes, P(yes) 75% | Yes, P(yes) 73% |
enabling_fraud_likelihood | Very unlikely (under 10%), 93% | Very unlikely (under 10%), 89% |
explanation | snooping, 87% | snooping, 82% |
action | refer, 54% | suspend, 96% |
The coworkers' accounts are restricted records under this standard, so the action should move from referring the employee to suspending their access now, while the explanation and the fraud likelihood stay the same.
Where to call it
- On each access-monitoring alert, before it reaches an analyst, and again when a fraud claim, takeover or DLP event hits an account they touched.
- Before a sensitive change takes effect, such as a phone number or email change with no linked contact.
- When the answer is uncertain, send the alert to an analyst, and don't contact the employee before you decide on their access.
Cost and latency
This example is 1,691 input tokens, so a decision costs $0.000060: $0.06 per 1,000 decisions, or $60.00 per million. You pay only for input tokens, at $0.035 per million, and each request is rounded up to the next millionth of a dollar. A larger context costs proportionally more; every response reports its size in usage.input_tokens.
Grayson answered this example in 181 ms, the median of 5 calls through api.finic.ai from US-West. Latency grows with the number of input tokens. Add your own network time to api.finic.ai.
Evaluate on your own data
Score Grayson on your own past cases before you use it: a CSV with one row per case and a column with the right answer to each question. Every other column is sent as the case.
pipx install https://docs.finic.ai/downloads/grayson_cli-0.2.2-py3-none-any.whl
grayson eval my-cases.csv --questions https://docs.finic.ai/recipes/insider-threat-detection/questions.json --label access_without_business_reason=<column> --label enabling_fraud_likelihood=<column> --label explanation=<column> --label action=<column>Each --label names the column with that question's right answer:
access_without_business_reason:trueorfalseenabling_fraud_likelihood: a level, such as "Very likely (over 90%)"explanation:legitimate_work,snooping,self_dealing,enabling_fraud,policy_mistakeaction:no_action,refer,suspend
Or run grayson on its own to set up your questions step by step. You get each question's accuracy and a CSV with Grayson's answer next to yours for every case.
FAQ
Can Grayson tell snooping from an employee working with fraudsters?
It can when the evidence is in the context. Snooping usually looks like brief views of accounts the employee has a personal interest in, during their shift, with no changes and nothing exported; insiders working with fraud rings look up high-balance or dormant accounts they have no connection to, change contact details without a request, capture screens and work outside their shift, and the accounts are taken over soon after. explanation returns a probability for each, so you can see when the evidence is mixed.
What if our systems don't link every lookup to a contact?
Most don't, which is why most unlinked-access alerts turn out to be legitimate. Send the raw contact records near each lookup and any IT incidents that affected linking, and Grayson matches them itself, as with the transferred chat and late ticket in the example. If you can only send the link check's result, expect more alerts to come back uncertain.
Should we include HR data or personal details about the employee?
No. Send conduct and access records, and leave out personal characteristics and circumstances such as age, nationality, health, union activity or personal finances: they say nothing about what the employee did, and using them to decide who to investigate can be discriminatory or unlawful. Identify the employee by ID and role, and keep the link to their name in your case system.
Related recipes
Triage dark web credential and card alerts
Match a dark web alert to the customer, judge whether the exposure still works, and choose the response.
Detect account opening fraud and synthetic identities
Approve, step up or decline a deposit account application at risk of stolen or synthetic identity fraud.
Recipes
Every use case, with its questions and cost per decision.
Incoming payments
Decide whether an incoming ACH credit is the proceeds of fraud at the sender, and whether to release, hold or return it, from account, login and transfer data.
Money mules
Decide whether an account is a money mule, whether the holder is complicit or was deceived by a job or romance scam, and whether to restrict or close it.