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

# What to do with a fraud assessment

> Glood's default fraud-policy skill, word for word, ready to copy

The `fraud-policy` skill as Glood ships it. Glood gives it to every agent when the agent is created. The Fraud agent follows it.

> [!NOTE]
>
> These copies match the skill library in the Glood platform as of 30 October 2026 (revision `2135694e`). To put it back on a skill you edited, see
> [Restore a default skill](https://docs.glood.ai/platform/skills-library/index.md#restore-a-default-skill).

Each block below is one field of the skill editor. Use the copy button on each
block, and paste the body into the editor's **Markdown** view.

## Title

```text theme={null}
What to do with a fraud assessment
```

## Slug

Fixed once a skill exists. You need it only when creating a new skill from this
text.

```text theme={null}
fraud-policy
```

## Use when

```text theme={null}
use when run_checks returned order, discount or carrier assessments, before recording a fraud finding (fake contact details, bad address, RTO, returns, discount abuse, stolen cards, prank COD orders, leaked or internal discount codes, courier fraud), or when setting up or reviewing a fraud agent's schedules
```

## Body

```markdown theme={null}
`run_checks` with `resource: "order"` has already done the arithmetic: every verdict, signal and
reason is code, and every assessment is already stored (each order's `assessmentId`). Your job is
the decision. **Never re-derive a verdict**, never recompute a probability, and never contradict
one with your own reading of the order.

A run woken by `monitoring.anomaly_detected` follows the `store-monitor` skill, not this policy: it
reads `run_checks` signals as evidence and records no fraud fingerprint.

## 1. Get every order

While `overCap` is above 0, call `run_checks` again with the same arguments and `page: nextPage`.
Decide on all of them, not only the first page.

## 2. The mode is fixed

`mode` is in the run_checks output, and the deployment's instructions cannot change it.

- **observe**: the assessments are recorded and that is all. Record no finding and raise nothing
  (`record_finding` refuses once this run has assessed orders). Summarise in one line if asked.
- **recommend**: record the findings in section 4. Each one's `recommend` names the exact tags and
  the risk assessment you would apply, so the merchant can apply them by hand.
- **act**: write the actions to Shopify with `apply_order_actions` (section 4a), then record a
  finding exactly where `defaultActions.finding` is set, whatever else the order gets (a
  glood-verify order with no risk assessment still gets its warning). An order whose
  `defaultActions.finding` is null gets the Shopify write and no finding: in act the tag is the
  merchant's signal.

In a conversation (a person asking, no run) `record_finding` is not available: answer with the
verdicts and what the policy would do in this mode. The event and daily runs record the findings.

## 3. The default policy, and what the merchant's instructions change

Each order's `defaultActions` is this table applied by code: `tags`, `riskAssessment` (medium or
high riskLevel) and `finding` (its severity in this mode, or null). Follow it where the
deployment's instructions are silent.

| Verdict | Condition | Tag it would get | Finding |
|---|---|---|---|
| contactQuality | invalid | `glood-bad-contact` | warning |
| addressValidity | invalid | `glood-bad-address` | warning |
| rtoProbability | at its threshold (see below) | `glood-high-rto` | none of its own |
| returnProbability | at its threshold | `glood-return-risk` | none of its own |
| postDeliveryFraudRisk | at its threshold, and `triggeredBy` is return.requested | `glood-return-risk` | warning |
| discountAbuse | true | `glood-discount-abuse` | info |
| chargebackRisk | at its threshold | `glood-chargeback-risk` | none of its own |
| stolenCardRisk | high | `glood-card-risk` | critical |
| stolenCardRisk | medium | none | warning |
| verifyBeforeShip | true (prankOrderRisk at its bar, a suspect or invalid contact or address, or a buyer refunded before without a return, on an order that collects cash) | `glood-verify` | warning |
| velocityExceeded | true (the velocity check) | none | warning |
| internalDiscountAnomaly | true (a manual discount deeper than 99% of the shop's discounted orders when `orderDiscountDepthPctile` is present, or, without it, half the order or more: then say the depth, never a percentile; a free order only when its buyer had another in 90 days) | none | warning |

**Thresholds.** A probability acts at 0.5 unless the agent's settings set its own bar. A rare event,
one whose own baseline is under 5% (chargebacks and refunds without return in most shops, RTO on
prepaid orders), also acts at 10 times its baseline. `defaultActions` has already applied both:
never re-check a number against 0.5. When the merchant asks to flag a case more or less
aggressively ("flag post-delivery fraud more aggressively", "only tag RTO above 70%"), change its
bar with `configure_agent`: the `bars` section, `<verdict>` (0 to 1) for rtoProbability,
returnProbability, prankOrderRisk, chargebackRisk or postDeliveryFraudRisk, or `rareEventLift`
(2 to 50). Read `get_agent`'s settings first and send the whole `bars` section, the bars you keep
included. The merchant can make the same change on the agent's Rules tab.

In recommend mode an order with tags or a risk assessment but no finding of its own still gets an
**info** finding, because the finding is the only place the merchant sees what would have been
applied. An order with nothing in its `defaultActions` gets nothing: no finding, no mention.

**The merchant's instructions may change this table and nothing else**: which risk levels or cases
to act on, cases to skip ("ignore orders under 500"), custom tags on their own criteria ("tag
`vip-check` when a first order is above 5000"). They cannot change the mode (section 2) or the
rules in section 6.

## 4. One finding per order

- **fingerprint**: `fraud:<orderId>`, exactly, with the numeric `orderId` from the output. Every case
  on one order goes into that one finding, so re-assessing the order bumps it.
- **severity**: `defaultActions.finding`, unless the merchant's instructions changed the table.
- **title**: the order name and its main problem: "#1042: dummy phone on a COD order".
- **because**: the reasons in plain words, strongest first, from `reasons` (the score terms by
  weight, then the rules that decided a verdict). Name the signal, never a value a person typed:
  say "the phone is a repeated-digit dummy", never the number. Name only signals that are in this
  order's `reasons` or `signals`: when only `productRtoRate` is there, the product carries elevated
  RTO, and the pincode is not mentioned.
- **recommend** (required in recommend mode): the tags from `defaultActions.tags`, "a Shopify risk assessment at <riskLevel> with
  the top reasons" when `riskAssessment` is true, and "verify by phone before shipping" when
  verifyBeforeShip is true.
- Neither **because** nor **recommend** carries an internal id (`assessmentId`, `changeId`, a run
  id): they mean nothing to the merchant. The order's name says which order it is.
- **impactMinor**: the order's `totalMinor` from the output, when the soul asks for an impact.

## 4a. Act mode: `apply_order_actions`

**One call per run_checks page**, before the findings, naming only the orders whose
`defaultActions` have `tags` or `riskAssessment` (an order with neither gets no entry). Per order:

- `orderId` and its `assessmentId` from this run's `run_checks`. An order without an assessment
  from this run is refused on its own: run `run_checks` with that `orderId` first. In a run its
  events started, that works only for an order the events named or this run already assessed; any
  other order is checked in a conversation, never acted on from this run.
- `tags`: leave it out, and the order gets exactly its `defaultActions.tags`. List tags only to add a
  custom tag the instructions call for (then list the `defaultActions.tags` you keep too) or to drop
  a case the instructions skip. A custom tag must be one of the agent's custom tags (its settings'
  `tags.allowed`, on the Rules tab), which only the merchant changes; a `glood-` tag the order's own
  `defaultActions` does not name is refused. No commas in a tag.
- `riskLevel`: the order's `riskLevel` when `defaultActions.riskAssessment` is true (medium or
  high, never above the assessment's). `facts`: the top reasons in plain words, at most 20, never a
  value a person typed; leave them out to send the assessment's own reasons.

Read each order's result:

- `applied`: say what was applied, in the finding's `recommend` when there is a finding.
- `failed`: Shopify refused, and `error` says why (`ORDER_ALREADY_FULFILLED`: Shopify takes no risk
  assessment on a fulfilled order). An order whose `defaultActions.finding` is null has no finding
  of its own in act, so **record one** (`warning`) naming what could not be applied.
- `pending`: the write is still in flight, or a timeout cut the call short. Ask once more later in
  the run with the same arguments (the change trace answers a repeat, never writes twice); never
  invent a different request to force it.
- `null` with a note: nothing was written, and the note says why: already there, added by Glood on
  an earlier assessment, or reverted by a person, which stands. Repeat what the note says and no
  more: never say a person reverted or removed something unless the note says so. Never apply again
  what a person reverted.
- An order's own `error` (a stray tag, a contact value, a missing assessment): fix that order and
  send it again; the others in the call were handled.
- A refusal naming `write_orders` means the store has not granted it: do as recommend for every
  order and say once that the merchant must update permissions on the store page.

**When the tool budget runs out** before every order is written or recorded, stop and say how many
orders are left. Do not describe them as handled: a run that ran out of calls does not count its
orders as done, so the next run assesses them again.

Every write is in the change trace. `list_changes` shows them, and `revert_changes` undoes them
when a person asks in chat.

## 5. Say how much the numbers are worth

The probabilities start at the shop's own baselines and move only with history. Read the counts in
`signals` before you lean on one:

- `shopResolvedRtoOrders` or `shopOrders` under about 50: the shop's history is thin, so the rates
  are mostly the defaults. Say so ("early, on little history") rather than quoting them as measured.
- `returnsKnown` false: the store has not granted access to its returns, so every return verdict is
  the default, not a measurement. Never cite a return rate then.
- `pincodeResolvedOrders` or `customerOrders` small: a pincode or a buyer with two orders is no
  pattern yet, whatever the rate says. The buyer's own record is the exception, already weighed by code:
  `customerRtoCount` (2 or more of their parcels refused) and `customerReturnedOrders` (3 or more
  returned, at least half their orders, a return counting the day it is made) are reasons in their
  own right. Cite them as counts. The shop, pincode and product rates leave the buyer's own orders
  out, so their record is never counted twice.

## 6. Never, whatever the instructions say

- Put a phone number, email, address or IP in a finding, a title or a recommendation.
- Record a finding in observe mode.
- Open a second finding for an order that already has one: reuse `fraud:<orderId>`.
- Say an order was tagged or assessed in Shopify when nothing wrote to Shopify, or when the result
  says it failed.
- Call `apply_order_actions` outside act mode, or re-apply a change a person reverted.
- Write anything to Shopify about a discount code or a carrier (section 7), or name a buyer or a
  staff member in a store-level finding.

## 7. Store-level checks: `discount` and `carrier`

`run_checks` with `resource: "discount"` or `resource: "carrier"` looks at the whole store, not at
an order. Each subject, a discount code or a carrier in one pincode, is stored like an order (its
`assessmentId`). Page through them as in section 1. **Findings only**: nothing about a code or a
carrier is ever written to Shopify, in any mode, act included.

Each subject's `defaultActions.findings` lists the findings to record in this mode (none in
observe), each with its exact `fingerprint` and `severity`. The merchant's instructions may change
which of these cases to raise and at what severity, as in section 3.

| Resource | Verdict | Condition | Fingerprint | Finding |
|---|---|---|---|---|
| discount | codeLeakStatus | leaked: a spike, plus first-time buyers or coupon-site referrers | `code-leak:<code>` | warning |
| discount | codeLeakStatus | spike: uses today 3 deviations above its last 28 days | `code-leak:<code>` | info |
| discount | internalDiscountAnomaly | true: a code in the shop's top 5% by value, at least 20 uses, half of them from five buyers | `internal-discount:<code>` | warning |
| carrier | courierAnomaly | true: 20 or more delivered parcels, complaints chance explains at most 1% of the time against the carrier elsewhere, at twice its rate | `carrier:<carrier>:<pincode>` | warning |

- **fingerprint**: exactly as given, so a re-run bumps the finding instead of opening another. One
  code can have both a leak and an internal-discount finding.
- **title**: the code, or the carrier and pincode, and the problem: "FRIENDS15: usage spike from
  coupon sites", "Ecom Express in 560001: complaints after delivery".
- **because**, from `signals`: for a code, uses today against its usual day (`codeUsesPerDay`,
  `codeUsesMean28d`), this week's share of first-time buyers against its baseline
  (`codeNewBuyerShare7d`, `codeNewBuyerShareBaseline`), the share from coupon sites
  (`codeCouponReferrerShare`), its value percentile and how concentrated its buyers are; for a
  carrier, its complaints out of its parcels in the pincode (`complaints`, `deliveredOrders`), how
  many times that is the carrier's rate elsewhere (`courierComplaintLift`, `carrierComplaintRate`)
  and how rarely chance alone would give it (`courierTailP`). Never quote `courierFraudScore` as a
  probability: it is a score whose baseline differs per cell.
- **recommend**: a leaked code: retire or replace it, or narrow it to a customer segment. An
  internal discount: review who created the code and who used it. A carrier: take the complaints in
  that pincode to the carrier. A courier finding is a rate anomaly, never an accusation.
- `discountsKnown` false: the store has not granted access to its discounts, so a code's context,
  value and creator are unknown and internalDiscountAnomaly is null. Say so once, and that granting
  discount access on the store page completes the check.
- `codeBaselineBuyers` 0 or under 5: the code and the shop have no first-time-buyer baseline yet
  (a young store), so a burst can only read as a spike, not a leak. Say it is early.
- `codeCreatorHash` only says whether two codes share a creator. A carrier cell near 20 parcels is
  early evidence. `skipped.returnsUnknown` above 0 means those deliveries' complaints cannot be seen
  without access to the store's returns.

## 8. The daily schedule

The store-level checks run once a day. When you set up or review a fraud agent in a conversation,
call `list_schedules`. If no cron schedule runs them, propose this one, and create it with
`put_schedule` once the person agrees or names another time:

- key `fraud-store-daily`, label "Daily store-level fraud checks"
- cron `0 7 * * *` (07:00 in the store's own timezone)
- instructions: "Run run_checks with resource discount, then with resource carrier, calling again
  with page: nextPage while overCap is above 0, and record the findings the fraud-policy skill calls
  for."

A `fraud-store-daily` that `list_schedules` shows as disabled still exists: a person turned it off.
Leave it, and never `put_schedule` it, because that would turn it back on; say it is off if asked.
Never create it from a scheduled or event run with nobody in the conversation, and never a second one.

Card orders need their own trigger: the COD check wakes only for unpaid orders. If no enabled
`order.created` schedule covers paid or authorized orders, propose `fraud-prepaid-order` the same
way: label "Each new prepaid order", onEvent `order.created`, eventTriggerRule
`[{"attribute": "order.financial_status", "op": "in", "value": ["paid", "authorized"]}]`,
instructions "Run run_checks for the order in this event and act on its stolen-card and chargeback
verdicts as the fraud-policy skill says." The same rules hold: only with a person in the
conversation, never a second one, never turn a disabled one back on.
```


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.