# TrustIn DART API — complete reference

> TrustIn DART is a blockchain AML screening API. Give it a wallet address (KYA) or a transaction hash (KYT) and it traces the funds up to five hops through labeled counterparties, matches every fund-flow path against a ruleset, and returns a 0–100 fund-attribution score with a verdict — accept, review, edd or block — and the exact rules behind it. Every screening is saved as an immutable snapshot. The same API lets a customer manage the rulesets and rules that drive the verdicts.

Machine-readable: OpenAPI 3.1 at https://v2.trustin.bond/openapi.json · this file at https://v2.trustin.bond/llms-full.txt · index at https://v2.trustin.bond/llms.txt · agent skill at https://v2.trustin.bond/agent/SKILL.md · MCP at https://api.trustin.bond/mcp

## Authentication, base URLs and errors

Authentication: every endpoint takes an API key created in the console under Account › API Keys. Send it as the X-Api-Key header (preferred), as ?apikey=<key> in the query string, or as Authorization: Bearer <JWT> for a console session token. Anonymous calls are rejected with HTTP 401.

Base URLs: production https://api.trustin.bond; test environment https://test-api.trustin.bond (separate database — ruleset ids differ between the two).

Response envelope: every response is JSON {"code": 0, "msg": "success", "data": …}. Errors carry code -1 and a msg that names the problem, and use HTTP status codes: 400 invalid input (the msg names the field), 401 no or invalid key, 403 a builtin ruleset is read-only (clone it first), 404 the id does not exist or is not yours, 409 a rule_code already exists in the ruleset, 429 too many concurrent screenings (back off, or use mode=async).

## The two rulesets

Two rulesets shape a screening. The path-matching ruleset (ruleset_type kya for address screening, kyt_in / kyt_out for the two sides of a transaction) holds rules; each rule fires when a fund-flow path — or, for a rule with max_hops -1, the screened address's own labels — meets all of its conditions, and a hit carries the rule's risk_level and action into the result. The scoring ruleset (ruleset_type scoring) holds a scoring_config: for each hop band and direction a base, per-severity weights, and verdict bands; the score sums, over the distinct inbound edges that carried tainted funds, edge amount ÷ total inflow × base × severity weight; an address whose own labels hit a self-hit rule overrides the sum with the self-hit score (100 by default).

Builtin rulesets — AML KYA Default v1.3, AML KYT-IN Default, AML KYT-OUT Default, the fund-attribution scoring default — are visible to everyone and read-only. Anything a customer creates or clones is theirs to edit. The normal way to customise: clone the builtin, edit the copy, pass the copy's id as ruleset_id (or scoring_ruleset_id) when screening. Screening with ruleset_id 0 uses the builtin default.

## Rules and conditions

Rule fields:
- rule_code — your identifier, unique within the ruleset; names the hit in results and snapshots.
- name, category, description, reference — free text.
- risk_level — low | medium | high | critical (drives the score's severity weight).
- action — alert | review | edd | block | allow (the disposition a hit recommends).
- direction — inbound | outbound | both.
- min_hops / max_hops — path length the rule applies to; max_hops -1 = the address's own labels, no path.
- conditions — array of {parameter, operator, value}; all must hold.
- is_highlighted — highlighted (★) rules are surfaced first in results; presentation only.
- sort_order — display order.

Condition parameters:
- Counterparty labels: primary_category (e.g. Sanctions, Obfuscation, Cybercrime, Public Freezing Action), secondary_category, risk_level (low | medium | high | critical), risk_score (number).
- Path: hops, amount (USD, the path's smallest transfer — its bottleneck), risk_rate (% of the counterparty's outflow that reached this address, e.g. 10 = 10%), inflow_total_amount, outflow_total_amount, direction, chain_name.

Condition operators: ==, !=, IN, NOT_IN, CONTAINS, >, <, >=, <=. The spellings =, eq, in, not_in, contains, gt, gte, lt, lte are accepted and normalised.

## Workflows and pitfalls

Common workflows:

1. Screen an address synchronously: POST /api/v3/screen/kya {"chain_name":"Tron","address":"T…"} → data.score.verdict, data.hits, data.exposures.
2. Screen asynchronously (recommended for deep hops or busy addresses): add "mode":"async" → data.job_id; then GET /api/v3/screen/result/{job_id}?type=kya until status is COMPLETE; poll no faster than every 2 seconds. The job computes immediately — polling only reads.
3. Customise the rules: POST /api/v3/rulesets/clone {"source_ruleset_id": <builtin kya id>, "name": "Ours"} → data.id; POST /api/v3/rulesets/rule/create {"ruleset_id": <id>, "rule_code": "MY_MIXER_EXPOSURE_H2", "name": "…", "risk_level": "high", "action": "review", "direction": "inbound", "min_hops": 2, "max_hops": 2, "conditions": [{"parameter":"primary_category","operator":"==","value":"Obfuscation"},{"parameter":"risk_rate","operator":">=","value":10}]}; then screen with "ruleset_id": <id>.
4. Read before you change: POST /api/v3/rulesets/list {"ruleset_type":"kya"} to find ids; POST /api/v3/rulesets/rule/list {"ruleset_id": <id>} for the catalogue.
5. Highlight what matters: POST /api/v3/rulesets/rule/highlight {"ruleset_id": <id>, "rule_ids": [...], "is_highlighted": true}.

Pitfalls: builtin rulesets cannot be edited (403) — clone them; ids differ between the test and production environments; a sync screening can take minutes on deep hops — prefer async; a rule needs at least one condition; deleting a ruleset never rewrites past snapshots.

## MCP

MCP: the same API is available to agents as tools over the Model Context Protocol (streamable HTTP) at https://api.trustin.bond/mcp. Connect with the API key as Authorization: Bearer <key> (or the X-Api-Key header). Tools: list_chains, screen_address, screen_transaction, get_screen_result, list_rulesets, get_ruleset, create_ruleset, update_ruleset, delete_ruleset, clone_ruleset, list_rules, get_rule, create_rule, update_rule, delete_rule, highlight_rules. Each tool mirrors the HTTP endpoint of the same name and returns the envelope's data.

## v3 Screening (Chainalysis)

### Supported Chains (v3)

`GET https://api.trustin.bond/api/v3/chains`

The chains and canonical assets screening accepts, read live from the investigation backend — a value returned here is one screen/kya and screen/kyt will take. Use it to render a chain selector or validate input instead of hardcoding a list that goes stale when a chain is added. address_family says which address form a chain uses ("evm" or "tron"): a 0x… address is shared by every EVM chain, so resolving a bare address to a chain needs it. default_token is what the screening endpoints use when a request omits token. Auth: API key, same as the other v3 endpoints; nothing is charged — this is metadata, not a screening. GET and POST both work.

**Request parameters:**

_No parameters._

**Example request:**

```bash
curl 'https://api.trustin.bond/api/v3/chains' -H 'X-Api-Key: $DART_API_KEY'
```

**Example response:**

```json
{
  "code": 0,
  "msg": "success",
  "data": {
    "chains": [
      {
        "chain_name": "Ethereum",
        "tokens": [
          "usdc",
          "usdt"
        ],
        "default_token": "usdt",
        "address_family": "evm"
      },
      {
        "chain_name": "Tron",
        "tokens": [
          "usdt"
        ],
        "default_token": "usdt",
        "address_family": "tron"
      },
      {
        "chain_name": "Base",
        "tokens": [
          "usdc"
        ],
        "default_token": "usdc",
        "address_family": "evm"
      }
    ]
  }
}
```

**Response fields (data):**

- `chains[].chain_name` (string) — Value to send as chain_name on the screening endpoints
- `chains[].tokens` (array) — Canonical assets screenable on this chain
- `chains[].default_token` (string) — Asset used when a screening request omits token
- `chains[].address_family` (string) — Address form this chain uses: evm (0x… hex) or tron (base58)


### KYA Screen (v3)

`POST https://api.trustin.bond/api/v3/screen/kya`

Chainalysis-aligned address screening. Runs the same ruleset detection engine as the Screening page and returns a Chainalysis-compatible response (with our extensions). Sync (default) returns the full result; async returns a job_id to poll — mode=async responds with data:{job_id, type:'kya', status:'PENDING'}. Defaults to the builtin KYA ruleset when ruleset_id=0. Auth (required — anonymous calls are rejected): API key via ?apikey=, the X-Api-Key header, or Authorization: Bearer <JWT>. Rate limit: up to 300 concurrent in-flight requests per endpoint; beyond that a request waits up to 60s for a slot, then returns HTTP 429 {"code":429,"msg":"out of max connections"} — back off and retry, or use mode=async for high concurrency (result polling is not rate-limited).

**Request parameters:**

| Parameter | Type | Required | Description | Default |
|---|---|---|---|---|
| `chain_name` | string | yes | Blockchain network | Tron |
| `address` | string | yes | Wallet address to screen |  |
| `token` | string (usdt \| usdc) | no | Token type | usdt |
| `inflow_hops` | number | no | Inflow tracing depth (0–5) | 3 |
| `outflow_hops` | number | no | Outflow tracing depth (0–5) | 1 |
| `min_timestamp` | integer (Unix ms) | no | Start time filter (Unix ms). Omit for the full history. Narrowing the window makes the upstream investigation noticeably slower, so set it only when you need it | 0 |
| `max_timestamp` | integer (Unix ms) | no | End time filter (Unix ms). Default: current time | 0 |
| `min_amount` | number | no | Minimum transfer amount to consider | 10 |
| `max_nodes_per_hop` | number | no | Max nodes expanded per hop | 200 |
| `force_time_sequence` | boolean | no | Require every hop on a path to be no later than the one before it, so funds can only have moved forward in time. Paths are then read from the time-sequence layer, which is a strict subset of the raw one — expect fewer paths and a slower first run while the layer is built | true |
| `ruleset_id` | number | no | 0 = default builtin KYA ruleset | 0 |
| `scoring_ruleset_id` | number | no | Scoring ruleset driving the fund-attribution score (ruleset_type=scoring). 0 = the user's own scoring ruleset, else the builtin default | 0 |
| `cex_immune` | boolean | no | Treat a plain CEX (exchange) deposit address as clean: returns no hits and score 0 instead of screening its history. A CEX that also carries other high-risk labels (e.g. Sanctioned CEX, Sanctions, Mixers) is still screened normally | true |
| `force_run` | boolean | no | Re-run the investigation instead of reusing the one an identical earlier submit produced. gloomis dedups at the investigation level, so screening the same subject with the same parameters returns the earlier graph and misses anything that moved since. Costs a full investigation, so leave it off unless you need the chain re-read | false |
| `mode` | string (sync \| async) | no | sync returns the full result immediately; async returns a job_id to poll via Get Screen Result | sync |

**Example request:**

```bash
curl -X POST 'https://api.trustin.bond/api/v3/screen/kya' \
  -H 'X-Api-Key: $DART_API_KEY' -H 'Content-Type: application/json' \
  -d '{"chain_name":"Tron","address":"","token":"usdt","inflow_hops":3,"outflow_hops":1,"min_amount":10,"max_nodes_per_hop":200,"force_time_sequence":true,"ruleset_id":0,"scoring_ruleset_id":0,"cex_immune":true,"force_run":false,"mode":"sync"}'
```

**Example response:**

```json
{
  "code": 0,
  "msg": "success",
  "data": {
    "address": "TSz3nv8...",
    "chain": "Tron",
    "score": {
      "score": 26.6,
      "verdict": "review",
      "selfHit": false,
      "components": [
        {
          "direction": "in",
          "hopBucket": "direct",
          "severity": "critical",
          "base": 80,
          "weight": 1,
          "amount": 185434.99,
          "rawAmount": 185434.99,
          "ratio": 0.266,
          "points": 26.6
        }
      ],
      "r1": 0.266,
      "r2": 0,
      "rOut": 0,
      "directAmount": 185434.99,
      "indirectAmount": 0,
      "outflowAmount": 0,
      "totalIn": 696674.38,
      "totalOut": 696674.38,
      "hitPaths": 24,
      "riskyEdges": 6
    },
    "scoreOverview": {
      "address": "TSz3nv8...",
      "token": "usdt",
      "inTotal": 696674.38,
      "outTotal": 696674.38,
      "inCount": 58,
      "outCount": 50,
      "balance": 0,
      "firstTs": 1694818383000,
      "lastTs": 1781415093000,
      "truncated": false
    },
    "subjectTags": [
      {
        "primaryCategory": "Sanctions",
        "tertiaryCategory": "OFAC"
      }
    ],
    "hits": [
      {
        "ruleCode": "KYA_SANCTION_EXPOSURE",
        "ruleName": "Exposure to sanctioned address",
        "category": "Sanctions",
        "riskLevel": "critical",
        "action": "block",
        "direction": "",
        "pathFlow": "inflow",
        "hops": 2,
        "opponentAddress": "T...",
        "maxAmount": 115776,
        "pathNodes": [],
        "highlighted": true
      }
    ],
    "highlightedHits": [
      {
        "ruleCode": "KYA_SANCTION_EXPOSURE",
        "ruleName": "Exposure to sanctioned address",
        "category": "Sanctions",
        "riskLevel": "critical",
        "action": "block",
        "direction": "",
        "pathFlow": "inflow",
        "hops": 2,
        "opponentAddress": "T...",
        "maxAmount": 115776,
        "pathNodes": [],
        "highlighted": true
      }
    ],
    "highlightSummary": {
      "rulesChecked": 3,
      "rulesHit": 1
    },
    "rulesTriggered": [
      "KYA_SANCTION_EXPOSURE"
    ],
    "rulesetId": 1,
    "totalPaths": 8,
    "hitPaths": 8,
    "inflowRiskAmount": 0,
    "inflowRiskRate": 0,
    "outflowRiskAmount": 0,
    "outflowRiskRate": 0,
    "risk": "critical",
    "riskScore": 90,
    "riskReason": "Critical: Exposure to sanctioned address (KYA_SANCTION_EXPOSURE)",
    "cluster": {
      "name": "",
      "category": ""
    },
    "addressType": "",
    "addressIdentifications": [
      {
        "category": "Sanctions",
        "name": "Address is a sanctioned entity",
        "description": "..."
      }
    ],
    "exposures": [
      {
        "category": "Sanctions",
        "direction": "inflow",
        "value": 68223
      },
      {
        "category": "Cybercrime",
        "direction": "inflow",
        "value": 53687
      }
    ]
  }
}
```

**Response fields (data):**

- `address` (string) — The queried wallet address
- `chain` (string) — Blockchain network — see the chain_name options in the request panel for the current list
- `risk` (string) — Our risk vocabulary: critical / high / medium / low
- `riskScore` (number) — Numeric risk score (0–100), mapped one-to-one from riskLevel (higher = riskier): critical = 90, high = 80, medium = 60, low = 10. It is 0 only when no rule is triggered (riskLevel then defaults to "low") — so riskLevel "low" with riskScore 0 means clean / no hits, whereas riskScore 10 means a low-severity rule fired.
- `riskReason` (string) — Human-readable summary of the highest-severity finding
- `cluster` (object) — Cluster attribution for the address (Chainalysis-style). Empty name/category when unresolved
- `addressType` (string) — Address entity type (e.g. exchange, mixer). Empty string if unresolved
- `addressIdentifications` (object[]) — What the address IS — identity-level findings derived from self-hits (e.g. the address itself is sanctioned)
  - `[].category` (string) — Identification category (e.g. "Sanctions")
  - `[].name` (string) — Short identification name
  - `[].description` (string) — Human-readable description
- `exposures` (object[]) — Aggregated exposure amounts by category and direction, derived from path hits
  - `[].category` (string) — Exposure category (e.g. "Sanctions", "Cybercrime")
  - `[].direction` (string) — Fund flow direction: inflow or outflow
  - `[].value` (number) — Exposure amount in token units
- `hits` (object[]) — Per-hit rule match detail with path evidence (our extension — not part of the Chainalysis spec)
  - `[].ruleCode` (string) — Unique rule code (e.g. "KYA_SANCTION_EXPOSURE")
  - `[].ruleName` (string) — Rule name
  - `[].category` (string) — Rule category (e.g. "Sanctions", "Cybercrime")
  - `[].riskLevel` (string) — Rule risk level: low / medium / high / critical
  - `[].action` (string) — Recommended action: block / review / alert / monitor
  - `[].direction` (string) — Reserved direction field (empty for KYA self/path hits)
  - `[].pathFlow` (string) — Path flow direction: inflow or outflow
  - `[].hops` (number) — Number of hops from the risk entity to the target address
  - `[].opponentAddress` (string) — The risk entity address at the other end of the path
  - `[].maxAmount` (number) — Maximum transfer amount on this path
  - `[].pathNodes` (string[]) — Ordered address list from opponent → target
  - `[].highlighted` (boolean) — Purely presentational (no effect on risk/action/score): true when the rule that produced this hit was flagged ★ by the ruleset owner. Absent on old results ⇒ false
- `highlightedHits` (object[]) — Exact subset of hits with highlighted=true, pre-sorted by risk level (desc), then rule sort order, then hops (asc). Purely presentational — use it to surface ★ rules first without re-deriving/re-sorting the filter yourself
- `highlightSummary` (object) — How many of the ruleset's ★ highlighted rules were checked vs. hit for this screen. Absent/0 rulesChecked ⇒ the ruleset has no highlighted rules
  - `.rulesChecked` (number) — Highlighted rules evaluated
  - `.rulesHit` (number) — Of those, how many fired
- `rulesTriggered` (string[]) — Rule codes triggered across all hits
- `rulesetId` (number) — The ruleset ID used for detection
- `totalPaths` (number) — Total number of fund paths analyzed
- `hitPaths` (number) — Number of paths that matched at least one rule
- `inflowRiskAmount` (number) — Contaminated inflow amount across unique hit inbound paths (token units)
- `inflowRiskRate` (number) — inflowRiskAmount / total inflow path amount, range 0–1
- `outflowRiskAmount` (number) — Risk outflow amount across unique hit outbound paths (token units)
- `outflowRiskRate` (number) — outflowRiskAmount / total outflow path amount, range 0–1
- `score` (object) — Fund-attribution risk score (资金占比评分). score = Σ (base × severity weight × fund ratio), where fund ratio is the deduped risky amount ÷ the address's total on-chain flow. Null when the graph had no flow totals.
  - `.score` (number) — 0–100 (null when denominators unavailable)
  - `.verdict` (string) — accept / review / edd / block (banded: 0-20 accept, 20-50 review, 50-80 edd, 80-100 block)
  - `.selfHit` (boolean) — True when the subject itself is flagged (score = self-hit score × severity weight)
  - `.components[]` (object[]) — Per-cell breakdown: each = base × weight × ratio = points
  - `.r1 / .r2 / .rOut` (number) — Direct-in / indirect-in / out flow ratios (0–1)
  - `.totalIn / .totalOut` (number) — The on-chain token volume used as the denominator
  - `.hitPaths / .riskyEdges` (number) — Hit path count / deduped risky edge count
- `scoreOverview` (object) — The address's on-chain activity behind the score: full token volume (the denominator) + tx counts + balance + activity window. Absent when the chain-volume fetch failed.
  - `.address` (string) — The screened address
  - `.token` (string) — Token (usdt / usdc)
  - `.inTotal / .outTotal` (number) — Total token inflow / outflow
  - `.inCount / .outCount` (number) — Transfer counts in / out
  - `.balance` (number) — Current token balance (null when unavailable)
  - `.firstTs / .lastTs` (number) — Earliest / latest transfer timestamp (ms)
  - `.truncated` (boolean) — True when the volume hit the provider page cap — the ratio is then conservative


### KYT Screen (v3)

`POST https://api.trustin.bond/api/v3/screen/kyt`

Chainalysis-aligned transaction screening. screen_direction (In / Out / Both, matching the Screening page) selects which endpoint(s) to screen: in (source-of-funds), out (destination), or both (default — both endpoints, merged). Rulesets are per-direction — in_ruleset_id applies to the in/source side and out_ruleset_id to the out/destination side, so "both" can use a different ruleset on each side (each defaults to its KYT-IN / KYT-OUT builtin). Sync/async like KYA Screen (v3). Rate limit: up to 300 concurrent in-flight requests per endpoint; beyond that a request waits up to 60s for a slot, then returns HTTP 429 {"code":429,"msg":"out of max connections"} — back off and retry, or use mode=async for high concurrency (result polling is not rate-limited).

**Request parameters:**

| Parameter | Type | Required | Description | Default |
|---|---|---|---|---|
| `chain_name` | string | yes | Blockchain network | Tron |
| `token` | string (usdt \| usdc) | no | Token type | usdt |
| `tx_id` | string | yes | Transaction hash to screen |  |
| `screen_direction` | string (both \| in \| out) | no | in = incoming/source (KYT-IN), out = outgoing/destination (KYT-OUT), both = screen both endpoints and merge | both |
| `inflow_hops` | number | no | Inflow tracing depth (0–5) | 3 |
| `outflow_hops` | number | no | Outflow tracing depth (0–5) | 1 |
| `min_timestamp` | integer (Unix ms) | no | Start time filter (Unix ms). Omit for the full history. Narrowing the window makes the upstream investigation noticeably slower, so set it only when you need it | 0 |
| `max_timestamp` | integer (Unix ms) | no | End time filter (Unix ms). Default: current time | 0 |
| `min_amount` | number | no | Minimum transfer amount to consider | 10 |
| `max_nodes_per_hop` | number | no | Max nodes expanded per hop | 200 |
| `force_time_sequence` | boolean | no | Require every hop on a path to be no later than the one before it, so funds can only have moved forward in time. Paths are then read from the time-sequence layer, which is a strict subset of the raw one — expect fewer paths and a slower first run while the layer is built | true |
| `in_ruleset_id` | number | no | Ruleset for the in/source screen (used by in + both). 0 = ruleset_id fallback, else KYT-IN builtin | 0 |
| `out_ruleset_id` | number | no | Ruleset for the out/destination screen (used by out + both). 0 = ruleset_id fallback, else KYT-OUT builtin | 0 |
| `ruleset_id` | number | no | Generic fallback for whichever side has no specific id. 0 = per-side builtin | 0 |
| `scoring_ruleset_id` | number | no | Scoring ruleset driving the fund-attribution score (ruleset_type=scoring). 0 = the user's own scoring ruleset, else the builtin default | 0 |
| `cex_immune` | boolean | no | Treat a plain CEX (exchange) endpoint as clean: when the screened source/destination is a known CEX deposit address with no other high-risk labels, that side returns no hits and score 0 instead of screening its history. A CEX that also carries other high-risk labels is still screened | true |
| `force_run` | boolean | no | Re-run the investigation instead of reusing the one an identical earlier submit produced. gloomis dedups at the investigation level, so screening the same subject with the same parameters returns the earlier graph and misses anything that moved since. Costs a full investigation, so leave it off unless you need the chain re-read | false |
| `mode` | string (sync \| async) | no | sync returns the full result immediately; async returns a job_id to poll via Get Screen Result | sync |

**Example request:**

```bash
curl -X POST 'https://api.trustin.bond/api/v3/screen/kyt' \
  -H 'X-Api-Key: $DART_API_KEY' -H 'Content-Type: application/json' \
  -d '{"chain_name":"Tron","token":"usdt","tx_id":"","screen_direction":"both","inflow_hops":3,"outflow_hops":1,"min_amount":10,"max_nodes_per_hop":200,"force_time_sequence":true,"in_ruleset_id":0,"out_ruleset_id":0,"ruleset_id":0,"scoring_ruleset_id":0,"cex_immune":true,"force_run":false,"mode":"sync"}'
```

**Example response:**

```json
{
  "code": 0,
  "msg": "success",
  "data": {
    "transaction": "a1b2c3...",
    "chain": "Tron",
    "score": {
      "score": 40,
      "verdict": "review",
      "selfHit": false,
      "components": [
        {
          "direction": "in",
          "hopBucket": "hop2",
          "severity": "high",
          "base": 50,
          "weight": 0.8,
          "amount": 4194296.43,
          "rawAmount": 4194296.43,
          "ratio": 0.784,
          "points": 31.4
        }
      ],
      "r1": 0,
      "r2": 1,
      "rOut": 0,
      "directAmount": 0,
      "indirectAmount": 4194296.43,
      "outflowAmount": 0,
      "totalIn": 5349043.05,
      "totalOut": 6035080.35,
      "hitPaths": 33,
      "riskyEdges": 15
    },
    "inScore": {
      "score": 40,
      "verdict": "review",
      "selfHit": false,
      "totalIn": 5349043.05,
      "totalOut": 6035080.35
    },
    "outScore": {
      "score": 26.6,
      "verdict": "review",
      "selfHit": false,
      "totalIn": 696674.38,
      "totalOut": 696674.38
    },
    "inScoreOverview": {
      "address": "TX69w...",
      "token": "usdt",
      "inTotal": 5349043.05,
      "outTotal": 6035080.35,
      "inCount": 2834,
      "outCount": 2166,
      "balance": 0,
      "firstTs": 1680000000000,
      "lastTs": 1780000000000,
      "truncated": true
    },
    "outScoreOverview": {
      "address": "TWTZ...",
      "token": "usdt",
      "inTotal": 696674.38,
      "outTotal": 696674.38,
      "inCount": 58,
      "outCount": 50,
      "balance": 0,
      "firstTs": 1694818383000,
      "lastTs": 1781415093000,
      "truncated": false
    },
    "hits": [
      {
        "ruleCode": "KYT_IN_SANCTION_SOURCE",
        "ruleName": "Funds sourced from sanctioned party",
        "category": "Sanctions",
        "riskLevel": "critical",
        "action": "block",
        "direction": "inbound",
        "pathFlow": "inflow",
        "hops": 2,
        "opponentAddress": "T...",
        "maxAmount": 1234.5,
        "pathNodes": [],
        "highlighted": true
      }
    ],
    "highlightedHits": [
      {
        "ruleCode": "KYT_IN_SANCTION_SOURCE",
        "ruleName": "Funds sourced from sanctioned party",
        "category": "Sanctions",
        "riskLevel": "critical",
        "action": "block",
        "direction": "inbound",
        "pathFlow": "inflow",
        "hops": 2,
        "opponentAddress": "T...",
        "maxAmount": 1234.5,
        "pathNodes": [],
        "highlighted": true
      }
    ],
    "inHighlightSummary": {
      "rulesChecked": 2,
      "rulesHit": 1
    },
    "outHighlightSummary": {
      "rulesChecked": 2,
      "rulesHit": 0
    },
    "rulesTriggered": [
      "KYT_IN_SANCTION_SOURCE"
    ],
    "rulesetId": 2,
    "totalPaths": 5,
    "hitPaths": 5,
    "risk": "critical",
    "riskScore": 90,
    "alerts": [
      {
        "alertLevel": "critical",
        "category": "Sanctions",
        "service": "KYT",
        "exposureType": "INDIRECT",
        "alertAmount": 1234.5,
        "categoryId": "KYT_IN_SANCTION_SOURCE",
        "direction": "inbound",
        "hops": 2,
        "opponentAddress": "T...",
        "action": "block",
        "highlighted": true
      }
    ]
  }
}
```

**Response fields (data):**

- `transaction` (string) — Transaction hash
- `risk` (string) — Our risk vocabulary: critical / high / medium / low
- `riskScore` (number) — Numeric risk score (0–100), mapped one-to-one from riskLevel (higher = riskier): critical = 90, high = 80, medium = 60, low = 10. It is 0 only when no rule is triggered (riskLevel then defaults to "low") — so riskLevel "low" with riskScore 0 means clean / no hits, whereas riskScore 10 means a low-severity rule fired.
- `alerts` (object[]) — Chainalysis KYT-compatible alert list
  - `[].alertLevel` (string) — Alert severity: low / medium / high / critical
  - `[].category` (string) — Alert category (e.g. "Sanctions")
  - `[].service` (string) — Originating service, always "KYT" for this endpoint
  - `[].exposureType` (string) — DIRECT (hops=0/1) or INDIRECT (hops>1), derived from hop count
  - `[].alertAmount` (number) — Amount associated with this alert (token units)
  - `[].categoryId` (string) — Rule code that produced this alert (e.g. "KYT_IN_SANCTION_SOURCE")
  - `[].direction` (string) — Our extension: inbound or outbound
  - `[].hops` (number) — Our extension: number of hops from the risk entity
  - `[].opponentAddress` (string) — Our extension: the risk entity address at the other end of the path
  - `[].action` (string) — Our extension: recommended action — block / review / alert / monitor
  - `[].highlighted` (boolean) — Our extension, purely presentational (no effect on risk/action/score): true when the rule that produced this alert was flagged ★ by the ruleset owner
- `hits` (object[]) — Per-hit rule match detail with path evidence (our extension — not part of the Chainalysis spec)
  - `[].ruleCode` (string) — Unique rule code (e.g. "KYT_IN_SANCTION_SOURCE")
  - `[].ruleName` (string) — Rule name
  - `[].category` (string) — Rule category (e.g. "Sanctions")
  - `[].riskLevel` (string) — Rule risk level: low / medium / high / critical
  - `[].action` (string) — Recommended action: block / review / alert / monitor
  - `[].direction` (string) — Path direction: inbound or outbound
  - `[].pathFlow` (string) — Path flow direction: inflow or outflow
  - `[].hops` (number) — Number of hops from the risk entity to the target address
  - `[].opponentAddress` (string) — The risk entity address at the other end of the path
  - `[].maxAmount` (number) — Maximum transfer amount on this path
  - `[].pathNodes` (string[]) — Ordered address list from opponent → target
  - `[].highlighted` (boolean) — Purely presentational (no effect on risk/action/score): true when the rule that produced this hit was flagged ★ by the ruleset owner
- `highlightedHits` (object[]) — Exact subset of hits with highlighted=true, pre-sorted by risk level (desc), then rule sort order, then hops (asc). screen_direction=both concatenates both sides — tell them apart the same way you split hits (path touches tx_from vs tx_to)
- `highlightSummary` (object) — screen_direction=in/out only: how many of the ruleset's ★ highlighted rules were checked vs. hit. Absent/0 rulesChecked ⇒ no highlighted rules on that side's ruleset
  - `.rulesChecked` (number) — Highlighted rules evaluated
  - `.rulesHit` (number) — Of those, how many fired
- `inHighlightSummary` (object) — screen_direction=both only: the in/source side's highlight summary. Same shape as highlightSummary
- `outHighlightSummary` (object) — screen_direction=both only: the out/destination side's highlight summary. Same shape as highlightSummary
- `rulesTriggered` (string[]) — Rule codes triggered across all hits
- `rulesetId` (number) — The ruleset ID used for detection (KYT-IN or KYT-OUT depending on screen_direction)
- `totalPaths` (number) — Total number of fund paths analyzed
- `hitPaths` (number) — Number of paths that matched at least one rule
- `score` (object) — Fund-attribution risk score (资金占比评分), the higher of the two sides for screen_direction=both. Same shape as the KYA score.
  - `.score` (number) — 0–100 (null when denominators unavailable)
  - `.verdict` (string) — accept / review / edd / block
  - `.components[]` (object[]) — Per-cell breakdown: base × weight × ratio = points
  - `.totalIn / .totalOut` (number) — The on-chain token volume used as the denominator
- `inScore` (object) — screen_direction=both only: the source (from) endpoint's own score. Same shape as score
- `outScore` (object) — screen_direction=both only: the destination (to) endpoint's own score. Same shape as score
- `scoreOverview` (object) — The address overview behind the combined score (the higher side's). Same shape as the KYA scoreOverview
  - `.inTotal / .outTotal` (number) — Total token inflow / outflow
  - `.inCount / .outCount` (number) — Transfer counts in / out
  - `.balance` (number) — Current token balance (null when unavailable)
  - `.firstTs / .lastTs` (number) — Earliest / latest transfer timestamp (ms)
  - `.truncated` (boolean) — True when the volume hit the provider page cap
- `inScoreOverview` (object) — screen_direction=both only: the source endpoint's address overview. Same shape as scoreOverview
- `outScoreOverview` (object) — screen_direction=both only: the destination endpoint's address overview. Same shape as scoreOverview


### Get Screen Result (async)

`GET https://api.trustin.bond/api/v3/screen/result/{jobId}`

Poll an async screening job. The job id is all you need — the submit call's parameters (chain, subject, token, ruleset, KYT direction) are recorded server-side and reused, and the job type is inferred. Returns status PENDING/PROCESSING/COMPLETE with the result under result when complete. This endpoint never blocks: once the upstream investigation finishes, detection is started in the background and the call still answers PROCESSING — keep polling (a few seconds apart) until COMPLETE. Detection runs once per job + parameter set, so polling costs nothing extra.

**Request parameters:**

| Parameter | Type | Required | Description | Default |
|---|---|---|---|---|
| `jobId` | number | yes | The job_id from an async submit (path parameter) |  |

**Example request:**

```bash
curl 'https://api.trustin.bond/api/v3/screen/result/<jobId>' -H 'X-Api-Key: $DART_API_KEY'
```

**Example response:**

```json
{
  "code": 0,
  "msg": "success",
  "data": {
    "job_id": 123,
    "type": "kya",
    "status": "COMPLETE",
    "result": {
      "address": "TSz3nv8...",
      "chain": "Tron",
      "risk": "critical",
      "riskScore": 90,
      "riskReason": "Critical: Exposure to sanctioned address (KYA_SANCTION_EXPOSURE)",
      "cluster": {
        "name": "",
        "category": ""
      },
      "addressType": "",
      "addressIdentifications": [
        {
          "category": "Sanctions",
          "name": "Address is a sanctioned entity",
          "description": "..."
        }
      ],
      "exposures": [
        {
          "category": "Sanctions",
          "direction": "inflow",
          "value": 68223
        }
      ],
      "hits": [
        {
          "ruleCode": "KYA_SANCTION_EXPOSURE",
          "ruleName": "Exposure to sanctioned address",
          "category": "Sanctions",
          "riskLevel": "critical",
          "action": "block",
          "direction": "",
          "pathFlow": "inflow",
          "hops": 2,
          "opponentAddress": "T...",
          "maxAmount": 115776,
          "pathNodes": [],
          "highlighted": true
        }
      ],
      "highlightedHits": [],
      "highlightSummary": {
        "rulesChecked": 3,
        "rulesHit": 1
      },
      "rulesTriggered": [
        "KYA_SANCTION_EXPOSURE"
      ],
      "rulesetId": 1,
      "totalPaths": 8,
      "hitPaths": 8,
      "inflowRiskAmount": 0,
      "inflowRiskRate": 0,
      "outflowRiskAmount": 0,
      "outflowRiskRate": 0
    }
  }
}
```

**Response fields (data):**

- `job_id` (number) — The polled job ID
- `type` (string) — Screening type this job was submitted for: kya or kyt
- `status` (string) — Job status: PENDING / PROCESSING / COMPLETE
- `result` (object) — Present when status=COMPLETE. Same KYA/KYT result object as the sync KYA Screen (v3) / KYT Screen (v3) endpoints


## v3 Rulesets & Rules

### List Rulesets

`POST https://api.trustin.bond/api/v3/rulesets/list`

Every ruleset the caller can see: their own plus the builtins (AML KYA Default, KYT-IN, KYT-OUT, the scoring default). An empty body lists them all; ruleset_type keeps one kind, ids narrows to specific rulesets, include_builtin=false leaves only your own. Each item carries rules_count and highlighted_rules_count; is_builtin tells you which ones are read-only. Auth: API key via the X-Api-Key header or ?apikey= (the same key as the screening endpoints). Nothing is charged.

**Request parameters:**

| Parameter | Type | Required | Description | Default |
|---|---|---|---|---|
| `ruleset_type` | string (kya \| kyt_in \| kyt_out \| scoring) | no | Keep one kind: kya (address screening), kyt_in / kyt_out (transaction source / destination), scoring (fund-attribution score). Empty = all |  |
| `include_builtin` | boolean | no | false returns only the rulesets you created or cloned | true |

**Example request:**

```bash
curl -X POST 'https://api.trustin.bond/api/v3/rulesets/list' \
  -H 'X-Api-Key: $DART_API_KEY' -H 'Content-Type: application/json' \
  -d '{"include_builtin":true}'
```

**Example response:**

```json
{
  "code": 0,
  "msg": "success",
  "data": [
    {
      "id": 22,
      "name": "AML KYA Default v1.3",
      "jurisdiction": "Global",
      "is_builtin": true,
      "generated_by": "kya-v2.1-hilite",
      "status": "ready",
      "ruleset_type": "kya",
      "rules_count": 72,
      "highlighted_rules_count": 5,
      "created_at": "2026-07-16T10:02:11Z"
    },
    {
      "id": 29,
      "name": "AML KYA Default v1.3 (Copy)",
      "jurisdiction": "Global",
      "is_builtin": false,
      "generated_by": "kya-v2.1-hilite",
      "status": "ready",
      "ruleset_type": "kya",
      "rules_count": 72,
      "highlighted_rules_count": 8,
      "created_at": "2026-09-08T18:05:40Z"
    }
  ]
}
```

**Response fields (data):**

- `[].id` (number) — Ruleset id — the ruleset_id / scoring_ruleset_id you pass to the screening endpoints
- `[].ruleset_type` (string) — kya, kyt_in, kyt_out or scoring
- `[].is_builtin` (boolean) — Builtin rulesets are read-only; clone one to edit
- `[].rules_count` (number) — Live rules in the ruleset
- `[].highlighted_rules_count` (number) — Rules flagged as highlighted (★)


### Get Ruleset

`POST https://api.trustin.bond/api/v3/rulesets/get`

One ruleset with all of its rules inline — the same shape rule/list returns per rule, plus scoring_config for a scoring ruleset. You see your own rulesets and the builtins; only your own can be changed — a builtin answers 403, clone it first. Auth: API key via the X-Api-Key header or ?apikey= (the same key as the screening endpoints). 

**Request parameters:**

| Parameter | Type | Required | Description | Default |
|---|---|---|---|---|
| `ruleset_id` | number | yes | Ruleset id | 22 |

**Example request:**

```bash
curl -X POST 'https://api.trustin.bond/api/v3/rulesets/get' \
  -H 'X-Api-Key: $DART_API_KEY' -H 'Content-Type: application/json' \
  -d '{"ruleset_id":22}'
```

**Example response:**

```json
{
  "code": 0,
  "msg": "success",
  "data": {
    "id": 29,
    "name": "AML KYA Default v1.3 (Copy)",
    "jurisdiction": "Global",
    "icon": "",
    "is_builtin": false,
    "generated_by": "kya-v2.1-hilite",
    "status": "ready",
    "ruleset_type": "kya",
    "source_policy_ids": [],
    "rules": [
      {
        "id": 2101,
        "ruleset_id": 29,
        "rule_code": "KYA_SANCTION_SELFHIT",
        "category": "Sanctions",
        "name": "Address is a sanctioned entity",
        "description": "Address itself carries a Sanctions label.",
        "risk_level": "critical",
        "action": "block",
        "direction": "both",
        "min_hops": 0,
        "max_hops": -1,
        "reference": "",
        "conditions": [
          {
            "parameter": "primary_category",
            "operator": "==",
            "value": "Sanctions"
          }
        ],
        "sort_order": 1,
        "is_highlighted": true
      },
      {
        "id": 2140,
        "ruleset_id": 29,
        "rule_code": "KYA_SANCTION_EXPOSURE_H2",
        "category": "Sanctions",
        "name": "Exposure to sanctioned address (2 hops)",
        "description": "",
        "risk_level": "critical",
        "action": "block",
        "direction": "both",
        "min_hops": 2,
        "max_hops": 2,
        "reference": "",
        "conditions": [
          {
            "parameter": "primary_category",
            "operator": "==",
            "value": "Sanctions"
          },
          {
            "parameter": "risk_rate",
            "operator": ">=",
            "value": 5
          }
        ],
        "sort_order": 40,
        "is_highlighted": false
      }
    ],
    "created_at": "2026-09-08T18:05:40Z",
    "updated_at": "2026-09-10T19:30:02Z"
  }
}
```

**Response fields (data):**

- `rules[]` (array) — The ruleset's rules, in sort_order — see rule/get for the fields
- `scoring_config` (object) — Only on a scoring ruleset: formula bases, severity weights and verdict bands


### Create Ruleset

`POST https://api.trustin.bond/api/v3/rulesets/create`

An empty ruleset of your own. ruleset_type is required and decides where it can be used: kya for address screening (ruleset_id on screen/kya), kyt_in / kyt_out for the two sides of a transaction screen, scoring for a fund-attribution scoring ruleset (scoring_ruleset_id) — a scoring ruleset holds a scoring_config instead of rules. Most integrations clone a builtin instead and edit the copy. Returns the created ruleset. Auth: API key via the X-Api-Key header or ?apikey= (the same key as the screening endpoints). 

**Request parameters:**

| Parameter | Type | Required | Description | Default |
|---|---|---|---|---|
| `name` | string | yes | Display name | My KYA ruleset |
| `ruleset_type` | string (kya \| kyt_in \| kyt_out \| scoring) | yes | kya, kyt_in, kyt_out or scoring | kya |
| `jurisdiction` | string | no | Free text, e.g. Global or SG | Global |
| `scoring_config` | array (JSON) | no | Scoring rulesets only: the config object (bases, severity weights, verdict bands). Omit to start from the default |  |

**Example request:**

```bash
curl -X POST 'https://api.trustin.bond/api/v3/rulesets/create' \
  -H 'X-Api-Key: $DART_API_KEY' -H 'Content-Type: application/json' \
  -d '{"name":"My KYA ruleset","ruleset_type":"kya","jurisdiction":"Global","scoring_config":[]}'
```

**Example response:**

```json
{
  "code": 0,
  "msg": "success",
  "data": {
    "id": 41,
    "name": "My KYA ruleset",
    "jurisdiction": "Global",
    "icon": "",
    "is_builtin": false,
    "generated_by": "api",
    "status": "ready",
    "ruleset_type": "kya",
    "source_policy_ids": [],
    "rules": [],
    "created_at": "2026-09-11T09:12:44Z",
    "updated_at": "2026-09-11T09:12:44Z"
  }
}
```



### Update Ruleset

`POST https://api.trustin.bond/api/v3/rulesets/update`

Rename a ruleset, change its jurisdiction or icon, or replace a scoring ruleset's scoring_config. Send only what changes; omitted fields keep their values. Returns the updated ruleset. You see your own rulesets and the builtins; only your own can be changed — a builtin answers 403, clone it first. Auth: API key via the X-Api-Key header or ?apikey= (the same key as the screening endpoints). 

**Request parameters:**

| Parameter | Type | Required | Description | Default |
|---|---|---|---|---|
| `ruleset_id` | number | yes | Ruleset id (must be your own) | 0 |
| `name` | string | no | New display name |  |
| `jurisdiction` | string | no | New jurisdiction |  |
| `scoring_config` | array (JSON) | no | Scoring rulesets only: replaces the whole config |  |

**Example request:**

```bash
curl -X POST 'https://api.trustin.bond/api/v3/rulesets/update' \
  -H 'X-Api-Key: $DART_API_KEY' -H 'Content-Type: application/json' \
  -d '{"ruleset_id":0,"scoring_config":[]}'
```

**Example response:**

```json
{
  "code": 0,
  "msg": "success",
  "data": {
    "id": 41,
    "name": "My KYA ruleset v2",
    "jurisdiction": "SG",
    "icon": "",
    "is_builtin": false,
    "generated_by": "api",
    "status": "ready",
    "ruleset_type": "kya",
    "source_policy_ids": [],
    "rules": [],
    "created_at": "2026-09-11T09:12:44Z",
    "updated_at": "2026-09-11T09:20:10Z"
  }
}
```



### Delete Ruleset

`POST https://api.trustin.bond/api/v3/rulesets/delete`

Removes one of your rulesets and its rules. Snapshots that were screened with it keep their frozen copy of the hits, so history is unaffected; new screenings that still send its id fall back to the builtin default. Builtins cannot be deleted (403). Auth: API key via the X-Api-Key header or ?apikey= (the same key as the screening endpoints). 

**Request parameters:**

| Parameter | Type | Required | Description | Default |
|---|---|---|---|---|
| `ruleset_id` | number | yes | Ruleset id (must be your own) | 0 |

**Example request:**

```bash
curl -X POST 'https://api.trustin.bond/api/v3/rulesets/delete' \
  -H 'X-Api-Key: $DART_API_KEY' -H 'Content-Type: application/json' \
  -d '{"ruleset_id":0}'
```

**Example response:**

```json
{
  "code": 0,
  "msg": "success",
  "data": {
    "id": 41,
    "deleted": true
  }
}
```



### Clone Ruleset

`POST https://api.trustin.bond/api/v3/rulesets/clone`

Copies a ruleset — a builtin or one of your own — into a new ruleset of yours, rules and highlights included. This is the normal way to start: clone the builtin AML KYA Default, then add, change or remove rules on the copy and pass the copy's id as ruleset_id when screening. Returns the new ruleset with its rules. Auth: API key via the X-Api-Key header or ?apikey= (the same key as the screening endpoints). 

**Request parameters:**

| Parameter | Type | Required | Description | Default |
|---|---|---|---|---|
| `source_ruleset_id` | number | yes | Ruleset to copy (builtin or your own) | 22 |
| `name` | string | no | Name for the copy; default appends "(Copy)" |  |

**Example request:**

```bash
curl -X POST 'https://api.trustin.bond/api/v3/rulesets/clone' \
  -H 'X-Api-Key: $DART_API_KEY' -H 'Content-Type: application/json' \
  -d '{"source_ruleset_id":22}'
```

**Example response:**

```json
{
  "code": 0,
  "msg": "success",
  "data": {
    "id": 42,
    "name": "AML KYA Default v1.3 (Copy)",
    "jurisdiction": "Global",
    "icon": "",
    "is_builtin": false,
    "generated_by": "kya-v2.1-hilite",
    "status": "ready",
    "ruleset_type": "kya",
    "source_policy_ids": [],
    "rules": [
      {
        "id": 3001,
        "ruleset_id": 42,
        "rule_code": "KYA_SANCTION_SELFHIT",
        "category": "Sanctions",
        "name": "Address is a sanctioned entity",
        "risk_level": "critical",
        "action": "block",
        "direction": "both",
        "min_hops": 0,
        "max_hops": -1,
        "conditions": [
          {
            "parameter": "primary_category",
            "operator": "==",
            "value": "Sanctions"
          }
        ],
        "sort_order": 1,
        "is_highlighted": true
      }
    ],
    "created_at": "2026-09-11T09:30:00Z",
    "updated_at": "2026-09-11T09:30:00Z"
  }
}
```



### List Rules

`POST https://api.trustin.bond/api/v3/rulesets/rule/list`

The rules of one ruleset, in sort_order. Works for builtins too, so you can read the default catalogue before deciding what to change. Auth: API key via the X-Api-Key header or ?apikey= (the same key as the screening endpoints). 

**Request parameters:**

| Parameter | Type | Required | Description | Default |
|---|---|---|---|---|
| `ruleset_id` | number | yes | Ruleset id | 22 |

**Example request:**

```bash
curl -X POST 'https://api.trustin.bond/api/v3/rulesets/rule/list' \
  -H 'X-Api-Key: $DART_API_KEY' -H 'Content-Type: application/json' \
  -d '{"ruleset_id":22}'
```

**Example response:**

```json
{
  "code": 0,
  "msg": "success",
  "data": [
    {
      "id": 2101,
      "ruleset_id": 29,
      "rule_code": "KYA_SANCTION_SELFHIT",
      "category": "Sanctions",
      "name": "Address is a sanctioned entity",
      "description": "Address itself carries a Sanctions label.",
      "risk_level": "critical",
      "action": "block",
      "direction": "both",
      "min_hops": 0,
      "max_hops": -1,
      "reference": "",
      "conditions": [
        {
          "parameter": "primary_category",
          "operator": "==",
          "value": "Sanctions"
        }
      ],
      "sort_order": 1,
      "is_highlighted": true,
      "created_at": "2026-09-08T18:05:40Z",
      "updated_at": "2026-09-08T18:05:40Z"
    }
  ]
}
```



### Get Rule

`POST https://api.trustin.bond/api/v3/rulesets/rule/get`

One rule. A rule fires when a fund-flow path (or, for max_hops=-1, the screened address itself) meets every condition: risk_level and action are what a hit carries into the result and the score; direction limits it to inbound (source of funds), outbound or both; min_hops / max_hops bound the path length. Auth: API key via the X-Api-Key header or ?apikey= (the same key as the screening endpoints). 

**Request parameters:**

| Parameter | Type | Required | Description | Default |
|---|---|---|---|---|
| `rule_id` | number | yes | Rule id | 0 |

**Example request:**

```bash
curl -X POST 'https://api.trustin.bond/api/v3/rulesets/rule/get' \
  -H 'X-Api-Key: $DART_API_KEY' -H 'Content-Type: application/json' \
  -d '{"rule_id":0}'
```

**Example response:**

```json
{
  "code": 0,
  "msg": "success",
  "data": {
    "id": 2140,
    "ruleset_id": 29,
    "rule_code": "KYA_SANCTION_EXPOSURE_H2",
    "category": "Sanctions",
    "name": "Exposure to sanctioned address (2 hops)",
    "description": "A sanctioned counterparty two hops upstream moved at least 5% of its outflow towards this address.",
    "risk_level": "critical",
    "action": "block",
    "direction": "both",
    "min_hops": 2,
    "max_hops": 2,
    "reference": "OFAC",
    "conditions": [
      {
        "parameter": "primary_category",
        "operator": "==",
        "value": "Sanctions"
      },
      {
        "parameter": "risk_rate",
        "operator": ">=",
        "value": 5
      }
    ],
    "sort_order": 40,
    "is_highlighted": false,
    "created_at": "2026-09-08T18:05:40Z",
    "updated_at": "2026-09-10T19:30:02Z"
  }
}
```

**Response fields (data):**

- `rule_code` (string) — Your identifier, unique within the ruleset; it names the hit in results and snapshots
- `risk_level` (string) — low, medium, high or critical — drives the score's severity weight
- `action` (string) — alert, review, edd, block or allow — the disposition a hit recommends
- `direction` (string) — inbound, outbound or both
- `min_hops / max_hops` (number) — Path length the rule applies to; max_hops -1 = the address's own labels, no path
- `conditions[]` (array) — {parameter, operator, value} triples, all of which must hold. Parameters: primary_category, secondary_category, risk_level, risk_score (the counterparty's labels); hops, amount (USD, the path's smallest transfer), risk_rate (% of the counterparty's outflow), inflow_total_amount, outflow_total_amount, direction, chain_name (the path). Operators: ==, !=, IN, NOT_IN, CONTAINS, >, <, >=, <= (=, in, contains are accepted and normalised)
- `is_highlighted` (boolean) — Highlighted (★) rules are surfaced first in results; presentation only


### Create Rule

`POST https://api.trustin.bond/api/v3/rulesets/rule/create`

Adds a rule to one of your rulesets. Required: rule_code (unique in the ruleset, 409 if taken), name, risk_level, action and at least one condition; direction defaults to both, min_hops / max_hops to 0 (max_hops -1 makes an identity rule that matches the screened address's own labels). Values are validated up front and a 400 names the field. The rule is live for the next screening that uses the ruleset. Returns the created rule. Auth: API key via the X-Api-Key header or ?apikey= (the same key as the screening endpoints). 

**Request parameters:**

| Parameter | Type | Required | Description | Default |
|---|---|---|---|---|
| `ruleset_id` | number | yes | Your ruleset (not a builtin) | 0 |
| `rule_code` | string | yes | Unique code within the ruleset, e.g. MY_MIXER_EXPOSURE_H2 | MY_MIXER_EXPOSURE_H2 |
| `name` | string | yes | Rule name as shown in results | Exposure to a mixer (2 hops) |
| `category` | string | no | Risk category label, e.g. Obfuscation | Obfuscation |
| `description` | string | no | What the rule catches |  |
| `risk_level` | string (low \| medium \| high \| critical) | yes | low, medium, high or critical | high |
| `action` | string (alert \| review \| edd \| block \| allow) | yes | alert, review, edd, block or allow | review |
| `direction` | string (both \| inbound \| outbound) | no | inbound, outbound or both | inbound |
| `min_hops` | number | no | Shortest path length the rule applies to (0 = any) | 2 |
| `max_hops` | number | no | Longest path length; -1 = the address's own labels only | 2 |
| `conditions` | array (JSON) | yes | JSON array of {parameter, operator, value}; every condition must hold | [{"parameter":"primary_category","operator":"==","value":"Obfuscation"},{"parameter":"risk_rate","operator":">=","value":10}] |
| `is_highlighted` | boolean | no | Flag as a highlighted (★) rule | false |

**Example request:**

```bash
curl -X POST 'https://api.trustin.bond/api/v3/rulesets/rule/create' \
  -H 'X-Api-Key: $DART_API_KEY' -H 'Content-Type: application/json' \
  -d '{"ruleset_id":0,"rule_code":"MY_MIXER_EXPOSURE_H2","name":"Exposure to a mixer (2 hops)","category":"Obfuscation","risk_level":"high","action":"review","direction":"inbound","min_hops":2,"max_hops":2,"conditions":[{"parameter":"primary_category","operator":"==","value":"Obfuscation"},{"parameter":"risk_rate","operator":">=","value":10}],"is_highlighted":false}'
```

**Example response:**

```json
{
  "code": 0,
  "msg": "success",
  "data": {
    "id": 3077,
    "ruleset_id": 42,
    "rule_code": "MY_MIXER_EXPOSURE_H2",
    "category": "Obfuscation",
    "name": "Exposure to a mixer (2 hops)",
    "description": "",
    "risk_level": "high",
    "action": "review",
    "direction": "inbound",
    "min_hops": 2,
    "max_hops": 2,
    "reference": "",
    "conditions": [
      {
        "parameter": "primary_category",
        "operator": "==",
        "value": "Obfuscation"
      },
      {
        "parameter": "risk_rate",
        "operator": ">=",
        "value": 10
      }
    ],
    "sort_order": 0,
    "is_highlighted": false,
    "created_at": "2026-09-11T09:41:07Z",
    "updated_at": "2026-09-11T09:41:07Z"
  }
}
```



### Update Rule

`POST https://api.trustin.bond/api/v3/rulesets/rule/update`

Changes one of your rules. Send only the fields to change; conditions, when sent, replace the whole list. The same validation as create applies to whatever is sent; hops are checked against the stored counterpart. Returns the updated rule. Auth: API key via the X-Api-Key header or ?apikey= (the same key as the screening endpoints). 

**Request parameters:**

| Parameter | Type | Required | Description | Default |
|---|---|---|---|---|
| `rule_id` | number | yes | Rule id (in one of your rulesets) | 0 |
| `name` | string | no | New name |  |
| `risk_level` | string (low \| medium \| high \| critical) | no | low, medium, high or critical |  |
| `action` | string (alert \| review \| edd \| block \| allow) | no | alert, review, edd, block or allow |  |
| `direction` | string (both \| inbound \| outbound) | no | inbound, outbound or both |  |
| `conditions` | array (JSON) | no | Replacement condition list (JSON array) |  |
| `is_highlighted` | boolean | no | Flag / unflag as highlighted | false |

**Example request:**

```bash
curl -X POST 'https://api.trustin.bond/api/v3/rulesets/rule/update' \
  -H 'X-Api-Key: $DART_API_KEY' -H 'Content-Type: application/json' \
  -d '{"rule_id":0,"conditions":[],"is_highlighted":false}'
```

**Example response:**

```json
{
  "code": 0,
  "msg": "success",
  "data": {
    "id": 3077,
    "ruleset_id": 42,
    "rule_code": "MY_MIXER_EXPOSURE_H2",
    "category": "Obfuscation",
    "name": "Exposure to a mixer (2 hops)",
    "description": "",
    "risk_level": "critical",
    "action": "block",
    "direction": "inbound",
    "min_hops": 2,
    "max_hops": 2,
    "reference": "",
    "conditions": [
      {
        "parameter": "primary_category",
        "operator": "==",
        "value": "Obfuscation"
      },
      {
        "parameter": "risk_rate",
        "operator": ">=",
        "value": 10
      }
    ],
    "sort_order": 0,
    "is_highlighted": true,
    "created_at": "2026-09-11T09:41:07Z",
    "updated_at": "2026-09-11T09:55:31Z"
  }
}
```



### Delete Rule

`POST https://api.trustin.bond/api/v3/rulesets/rule/delete`

Removes a rule from one of your rulesets. Past snapshots keep the hits it produced. Auth: API key via the X-Api-Key header or ?apikey= (the same key as the screening endpoints). 

**Request parameters:**

| Parameter | Type | Required | Description | Default |
|---|---|---|---|---|
| `rule_id` | number | yes | Rule id (in one of your rulesets) | 0 |

**Example request:**

```bash
curl -X POST 'https://api.trustin.bond/api/v3/rulesets/rule/delete' \
  -H 'X-Api-Key: $DART_API_KEY' -H 'Content-Type: application/json' \
  -d '{"rule_id":0}'
```

**Example response:**

```json
{
  "code": 0,
  "msg": "success",
  "data": {
    "id": 3077,
    "deleted": true
  }
}
```



### Highlight Rules

`POST https://api.trustin.bond/api/v3/rulesets/rule/highlight`

Flags or unflags several rules of one ruleset as highlighted (★) in one call. Highlighted rules are surfaced first in screening results and counted in highlightSummary; the flag never changes risk_level, action or the score. Auth: API key via the X-Api-Key header or ?apikey= (the same key as the screening endpoints). 

**Request parameters:**

| Parameter | Type | Required | Description | Default |
|---|---|---|---|---|
| `ruleset_id` | number | yes | Your ruleset | 0 |
| `rule_ids` | array (JSON) | yes | JSON array of rule ids in that ruleset | [] |
| `is_highlighted` | boolean | yes | true to flag, false to unflag | true |

**Example request:**

```bash
curl -X POST 'https://api.trustin.bond/api/v3/rulesets/rule/highlight' \
  -H 'X-Api-Key: $DART_API_KEY' -H 'Content-Type: application/json' \
  -d '{"ruleset_id":0,"rule_ids":[],"is_highlighted":true}'
```

**Example response:**

```json
{
  "code": 0,
  "msg": "success",
  "data": {
    "affected": 2
  }
}
```


