{"openapi":"3.1.0","info":{"title":"TrustIn DART API","version":"3","summary":"Blockchain AML screening: address (KYA) and transaction (KYT) screening with a fund-attribution score, plus the rulesets and rules behind it.","description":"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.\n\nAuthentication: 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.\n\nBase URLs: production https://api.trustin.bond; test environment https://test-api.trustin.bond (separate database — ruleset ids differ between the two).\n\nResponse 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).\n\nTwo 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).\n\nBuiltin 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.\n\nRule fields:\n- rule_code — your identifier, unique within the ruleset; names the hit in results and snapshots.\n- name, category, description, reference — free text.\n- risk_level — low | medium | high | critical (drives the score's severity weight).\n- action — alert | review | edd | block | allow (the disposition a hit recommends).\n- direction — inbound | outbound | both.\n- min_hops / max_hops — path length the rule applies to; max_hops -1 = the address's own labels, no path.\n- conditions — array of {parameter, operator, value}; all must hold.\n- is_highlighted — highlighted (★) rules are surfaced first in results; presentation only.\n- sort_order — display order.\n\nCondition parameters:\n- Counterparty labels: primary_category (e.g. Sanctions, Obfuscation, Cybercrime, Public Freezing Action), secondary_category, risk_level (low | medium | high | critical), risk_score (number).\n- 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.\n\nCondition operators: ==, !=, IN, NOT_IN, CONTAINS, >, <, >=, <=. The spellings =, eq, in, not_in, contains, gt, gte, lt, lte are accepted and normalised.\n\nCommon workflows:\n\n1. Screen an address synchronously: POST /api/v3/screen/kya {\"chain_name\":\"Tron\",\"address\":\"T…\"} → data.score.verdict, data.hits, data.exposures.\n2. 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.\n3. 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>.\n4. 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.\n5. Highlight what matters: POST /api/v3/rulesets/rule/highlight {\"ruleset_id\": <id>, \"rule_ids\": [...], \"is_highlighted\": true}.\n\nPitfalls: 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.\n\nMCP: 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.","contact":{"name":"TrustIn","url":"https://v2.trustin.bond"}},"servers":[{"url":"https://api.trustin.bond","description":"Production"},{"url":"https://test-api.trustin.bond","description":"Test environment (separate data; ruleset ids differ)"}],"tags":[{"name":"v3 Screening (Chainalysis)"},{"name":"v3 Rulesets & Rules"}],"security":[{"ApiKeyHeader":[]},{"ApiKeyQuery":[]},{"BearerJWT":[]}],"paths":{"/api/v3/chains":{"get":{"operationId":"v3_chains","summary":"Supported Chains (v3)","description":"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.","tags":["v3 Screening (Chainalysis)"],"security":[{"ApiKeyHeader":[]},{"ApiKeyQuery":[]},{"BearerJWT":[]}],"responses":{"200":{"description":"Success envelope; the payload is in data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Envelope"},"example":{"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"}]}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"}},"x-response-fields":[{"name":"chains[].chain_name","type":"string","description":"Value to send as chain_name on the screening endpoints"},{"name":"chains[].tokens","type":"array","description":"Canonical assets screenable on this chain"},{"name":"chains[].default_token","type":"string","description":"Asset used when a screening request omits token"},{"name":"chains[].address_family","type":"string","description":"Address form this chain uses: evm (0x… hex) or tron (base58)"}],"parameters":[]},"post":{"operationId":"v3_chains_post","summary":"Supported Chains (v3)","description":"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.","tags":["v3 Screening (Chainalysis)"],"security":[{"ApiKeyHeader":[]},{"ApiKeyQuery":[]},{"BearerJWT":[]}],"responses":{"200":{"description":"Success envelope; the payload is in data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Envelope"},"example":{"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"}]}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"}},"x-response-fields":[{"name":"chains[].chain_name","type":"string","description":"Value to send as chain_name on the screening endpoints"},{"name":"chains[].tokens","type":"array","description":"Canonical assets screenable on this chain"},{"name":"chains[].default_token","type":"string","description":"Asset used when a screening request omits token"},{"name":"chains[].address_family","type":"string","description":"Address form this chain uses: evm (0x… hex) or tron (base58)"}],"parameters":[]}},"/api/v3/screen/kya":{"post":{"operationId":"v3_screen_kya","summary":"KYA Screen (v3)","description":"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).","tags":["v3 Screening (Chainalysis)"],"security":[{"ApiKeyHeader":[]},{"ApiKeyQuery":[]},{"BearerJWT":[]}],"responses":{"200":{"description":"Success envelope; the payload is in data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Envelope"},"example":{"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}]}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"x-response-fields":[{"name":"address","type":"string","description":"The queried wallet address"},{"name":"chain","type":"string","description":"Blockchain network — see the chain_name options in the request panel for the current list"},{"name":"risk","type":"string","description":"Our risk vocabulary: critical / high / medium / low"},{"name":"riskScore","type":"number","description":"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."},{"name":"riskReason","type":"string","description":"Human-readable summary of the highest-severity finding"},{"name":"cluster","type":"object","description":"Cluster attribution for the address (Chainalysis-style). Empty name/category when unresolved"},{"name":"addressType","type":"string","description":"Address entity type (e.g. exchange, mixer). Empty string if unresolved"},{"name":"addressIdentifications","type":"object[]","description":"What the address IS — identity-level findings derived from self-hits (e.g. the address itself is sanctioned)","children":[{"name":"[].category","type":"string","description":"Identification category (e.g. \"Sanctions\")"},{"name":"[].name","type":"string","description":"Short identification name"},{"name":"[].description","type":"string","description":"Human-readable description"}]},{"name":"exposures","type":"object[]","description":"Aggregated exposure amounts by category and direction, derived from path hits","children":[{"name":"[].category","type":"string","description":"Exposure category (e.g. \"Sanctions\", \"Cybercrime\")"},{"name":"[].direction","type":"string","description":"Fund flow direction: inflow or outflow"},{"name":"[].value","type":"number","description":"Exposure amount in token units"}]},{"name":"hits","type":"object[]","description":"Per-hit rule match detail with path evidence (our extension — not part of the Chainalysis spec)","children":[{"name":"[].ruleCode","type":"string","description":"Unique rule code (e.g. \"KYA_SANCTION_EXPOSURE\")"},{"name":"[].ruleName","type":"string","description":"Rule name"},{"name":"[].category","type":"string","description":"Rule category (e.g. \"Sanctions\", \"Cybercrime\")"},{"name":"[].riskLevel","type":"string","description":"Rule risk level: low / medium / high / critical"},{"name":"[].action","type":"string","description":"Recommended action: block / review / alert / monitor"},{"name":"[].direction","type":"string","description":"Reserved direction field (empty for KYA self/path hits)"},{"name":"[].pathFlow","type":"string","description":"Path flow direction: inflow or outflow"},{"name":"[].hops","type":"number","description":"Number of hops from the risk entity to the target address"},{"name":"[].opponentAddress","type":"string","description":"The risk entity address at the other end of the path"},{"name":"[].maxAmount","type":"number","description":"Maximum transfer amount on this path"},{"name":"[].pathNodes","type":"string[]","description":"Ordered address list from opponent → target"},{"name":"[].highlighted","type":"boolean","description":"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"}]},{"name":"highlightedHits","type":"object[]","description":"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"},{"name":"highlightSummary","type":"object","description":"How many of the ruleset's ★ highlighted rules were checked vs. hit for this screen. Absent/0 rulesChecked ⇒ the ruleset has no highlighted rules","children":[{"name":".rulesChecked","type":"number","description":"Highlighted rules evaluated"},{"name":".rulesHit","type":"number","description":"Of those, how many fired"}]},{"name":"rulesTriggered","type":"string[]","description":"Rule codes triggered across all hits"},{"name":"rulesetId","type":"number","description":"The ruleset ID used for detection"},{"name":"totalPaths","type":"number","description":"Total number of fund paths analyzed"},{"name":"hitPaths","type":"number","description":"Number of paths that matched at least one rule"},{"name":"inflowRiskAmount","type":"number","description":"Contaminated inflow amount across unique hit inbound paths (token units)"},{"name":"inflowRiskRate","type":"number","description":"inflowRiskAmount / total inflow path amount, range 0–1"},{"name":"outflowRiskAmount","type":"number","description":"Risk outflow amount across unique hit outbound paths (token units)"},{"name":"outflowRiskRate","type":"number","description":"outflowRiskAmount / total outflow path amount, range 0–1"},{"name":"score","type":"object","description":"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.","children":[{"name":".score","type":"number","description":"0–100 (null when denominators unavailable)"},{"name":".verdict","type":"string","description":"accept / review / edd / block (banded: 0-20 accept, 20-50 review, 50-80 edd, 80-100 block)"},{"name":".selfHit","type":"boolean","description":"True when the subject itself is flagged (score = self-hit score × severity weight)"},{"name":".components[]","type":"object[]","description":"Per-cell breakdown: each = base × weight × ratio = points"},{"name":".r1 / .r2 / .rOut","type":"number","description":"Direct-in / indirect-in / out flow ratios (0–1)"},{"name":".totalIn / .totalOut","type":"number","description":"The on-chain token volume used as the denominator"},{"name":".hitPaths / .riskyEdges","type":"number","description":"Hit path count / deduped risky edge count"}]},{"name":"scoreOverview","type":"object","description":"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.","children":[{"name":".address","type":"string","description":"The screened address"},{"name":".token","type":"string","description":"Token (usdt / usdc)"},{"name":".inTotal / .outTotal","type":"number","description":"Total token inflow / outflow"},{"name":".inCount / .outCount","type":"number","description":"Transfer counts in / out"},{"name":".balance","type":"number","description":"Current token balance (null when unavailable)"},{"name":".firstTs / .lastTs","type":"number","description":"Earliest / latest transfer timestamp (ms)"},{"name":".truncated","type":"boolean","description":"True when the volume hit the provider page cap — the ratio is then conservative"}]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"chain_name":{"description":"Blockchain network. Any chain_name returned by GET /api/v3/chains","type":"string","default":"Tron"},"address":{"description":"Wallet address to screen","type":"string"},"token":{"description":"Token type","type":"string","enum":["usdt","usdc"],"default":"usdt"},"inflow_hops":{"description":"Inflow tracing depth (0–5)","type":"number","default":3},"outflow_hops":{"description":"Outflow tracing depth (0–5)","type":"number","default":1},"min_timestamp":{"description":"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 (Unix time in milliseconds)","type":"integer"},"max_timestamp":{"description":"End time filter (Unix ms). Default: current time (Unix time in milliseconds)","type":"integer"},"min_amount":{"description":"Minimum transfer amount to consider","type":"number","default":10},"max_nodes_per_hop":{"description":"Max nodes expanded per hop","type":"number","default":200},"force_time_sequence":{"description":"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","type":"boolean","default":true},"ruleset_id":{"description":"0 = default builtin KYA ruleset","type":"number","default":0},"scoring_ruleset_id":{"description":"Scoring ruleset driving the fund-attribution score (ruleset_type=scoring). 0 = the user's own scoring ruleset, else the builtin default","type":"number","default":0},"cex_immune":{"description":"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","type":"boolean","default":true},"force_run":{"description":"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","type":"boolean","default":false},"mode":{"description":"sync returns the full result immediately; async returns a job_id to poll via Get Screen Result","type":"string","enum":["sync","async"],"default":"sync"}},"required":["chain_name","address"]}}}}}},"/api/v3/screen/kyt":{"post":{"operationId":"v3_screen_kyt","summary":"KYT Screen (v3)","description":"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).","tags":["v3 Screening (Chainalysis)"],"security":[{"ApiKeyHeader":[]},{"ApiKeyQuery":[]},{"BearerJWT":[]}],"responses":{"200":{"description":"Success envelope; the payload is in data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Envelope"},"example":{"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}]}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"x-response-fields":[{"name":"transaction","type":"string","description":"Transaction hash"},{"name":"risk","type":"string","description":"Our risk vocabulary: critical / high / medium / low"},{"name":"riskScore","type":"number","description":"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."},{"name":"alerts","type":"object[]","description":"Chainalysis KYT-compatible alert list","children":[{"name":"[].alertLevel","type":"string","description":"Alert severity: low / medium / high / critical"},{"name":"[].category","type":"string","description":"Alert category (e.g. \"Sanctions\")"},{"name":"[].service","type":"string","description":"Originating service, always \"KYT\" for this endpoint"},{"name":"[].exposureType","type":"string","description":"DIRECT (hops=0/1) or INDIRECT (hops>1), derived from hop count"},{"name":"[].alertAmount","type":"number","description":"Amount associated with this alert (token units)"},{"name":"[].categoryId","type":"string","description":"Rule code that produced this alert (e.g. \"KYT_IN_SANCTION_SOURCE\")"},{"name":"[].direction","type":"string","description":"Our extension: inbound or outbound"},{"name":"[].hops","type":"number","description":"Our extension: number of hops from the risk entity"},{"name":"[].opponentAddress","type":"string","description":"Our extension: the risk entity address at the other end of the path"},{"name":"[].action","type":"string","description":"Our extension: recommended action — block / review / alert / monitor"},{"name":"[].highlighted","type":"boolean","description":"Our extension, purely presentational (no effect on risk/action/score): true when the rule that produced this alert was flagged ★ by the ruleset owner"}]},{"name":"hits","type":"object[]","description":"Per-hit rule match detail with path evidence (our extension — not part of the Chainalysis spec)","children":[{"name":"[].ruleCode","type":"string","description":"Unique rule code (e.g. \"KYT_IN_SANCTION_SOURCE\")"},{"name":"[].ruleName","type":"string","description":"Rule name"},{"name":"[].category","type":"string","description":"Rule category (e.g. \"Sanctions\")"},{"name":"[].riskLevel","type":"string","description":"Rule risk level: low / medium / high / critical"},{"name":"[].action","type":"string","description":"Recommended action: block / review / alert / monitor"},{"name":"[].direction","type":"string","description":"Path direction: inbound or outbound"},{"name":"[].pathFlow","type":"string","description":"Path flow direction: inflow or outflow"},{"name":"[].hops","type":"number","description":"Number of hops from the risk entity to the target address"},{"name":"[].opponentAddress","type":"string","description":"The risk entity address at the other end of the path"},{"name":"[].maxAmount","type":"number","description":"Maximum transfer amount on this path"},{"name":"[].pathNodes","type":"string[]","description":"Ordered address list from opponent → target"},{"name":"[].highlighted","type":"boolean","description":"Purely presentational (no effect on risk/action/score): true when the rule that produced this hit was flagged ★ by the ruleset owner"}]},{"name":"highlightedHits","type":"object[]","description":"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)"},{"name":"highlightSummary","type":"object","description":"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","children":[{"name":".rulesChecked","type":"number","description":"Highlighted rules evaluated"},{"name":".rulesHit","type":"number","description":"Of those, how many fired"}]},{"name":"inHighlightSummary","type":"object","description":"screen_direction=both only: the in/source side's highlight summary. Same shape as highlightSummary"},{"name":"outHighlightSummary","type":"object","description":"screen_direction=both only: the out/destination side's highlight summary. Same shape as highlightSummary"},{"name":"rulesTriggered","type":"string[]","description":"Rule codes triggered across all hits"},{"name":"rulesetId","type":"number","description":"The ruleset ID used for detection (KYT-IN or KYT-OUT depending on screen_direction)"},{"name":"totalPaths","type":"number","description":"Total number of fund paths analyzed"},{"name":"hitPaths","type":"number","description":"Number of paths that matched at least one rule"},{"name":"score","type":"object","description":"Fund-attribution risk score (资金占比评分), the higher of the two sides for screen_direction=both. Same shape as the KYA score.","children":[{"name":".score","type":"number","description":"0–100 (null when denominators unavailable)"},{"name":".verdict","type":"string","description":"accept / review / edd / block"},{"name":".components[]","type":"object[]","description":"Per-cell breakdown: base × weight × ratio = points"},{"name":".totalIn / .totalOut","type":"number","description":"The on-chain token volume used as the denominator"}]},{"name":"inScore","type":"object","description":"screen_direction=both only: the source (from) endpoint's own score. Same shape as score"},{"name":"outScore","type":"object","description":"screen_direction=both only: the destination (to) endpoint's own score. Same shape as score"},{"name":"scoreOverview","type":"object","description":"The address overview behind the combined score (the higher side's). Same shape as the KYA scoreOverview","children":[{"name":".inTotal / .outTotal","type":"number","description":"Total token inflow / outflow"},{"name":".inCount / .outCount","type":"number","description":"Transfer counts in / out"},{"name":".balance","type":"number","description":"Current token balance (null when unavailable)"},{"name":".firstTs / .lastTs","type":"number","description":"Earliest / latest transfer timestamp (ms)"},{"name":".truncated","type":"boolean","description":"True when the volume hit the provider page cap"}]},{"name":"inScoreOverview","type":"object","description":"screen_direction=both only: the source endpoint's address overview. Same shape as scoreOverview"},{"name":"outScoreOverview","type":"object","description":"screen_direction=both only: the destination endpoint's address overview. Same shape as scoreOverview"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"chain_name":{"description":"Blockchain network. Any chain_name returned by GET /api/v3/chains","type":"string","default":"Tron"},"token":{"description":"Token type","type":"string","enum":["usdt","usdc"],"default":"usdt"},"tx_id":{"description":"Transaction hash to screen","type":"string"},"screen_direction":{"description":"in = incoming/source (KYT-IN), out = outgoing/destination (KYT-OUT), both = screen both endpoints and merge","type":"string","enum":["both","in","out"],"default":"both"},"inflow_hops":{"description":"Inflow tracing depth (0–5)","type":"number","default":3},"outflow_hops":{"description":"Outflow tracing depth (0–5)","type":"number","default":1},"min_timestamp":{"description":"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 (Unix time in milliseconds)","type":"integer"},"max_timestamp":{"description":"End time filter (Unix ms). Default: current time (Unix time in milliseconds)","type":"integer"},"min_amount":{"description":"Minimum transfer amount to consider","type":"number","default":10},"max_nodes_per_hop":{"description":"Max nodes expanded per hop","type":"number","default":200},"force_time_sequence":{"description":"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","type":"boolean","default":true},"in_ruleset_id":{"description":"Ruleset for the in/source screen (used by in + both). 0 = ruleset_id fallback, else KYT-IN builtin","type":"number","default":0},"out_ruleset_id":{"description":"Ruleset for the out/destination screen (used by out + both). 0 = ruleset_id fallback, else KYT-OUT builtin","type":"number","default":0},"ruleset_id":{"description":"Generic fallback for whichever side has no specific id. 0 = per-side builtin","type":"number","default":0},"scoring_ruleset_id":{"description":"Scoring ruleset driving the fund-attribution score (ruleset_type=scoring). 0 = the user's own scoring ruleset, else the builtin default","type":"number","default":0},"cex_immune":{"description":"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","type":"boolean","default":true},"force_run":{"description":"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","type":"boolean","default":false},"mode":{"description":"sync returns the full result immediately; async returns a job_id to poll via Get Screen Result","type":"string","enum":["sync","async"],"default":"sync"}},"required":["chain_name","tx_id"]}}}}}},"/api/v3/screen/result/{jobId}":{"get":{"operationId":"v3_screen_result","summary":"Get Screen Result (async)","description":"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.","tags":["v3 Screening (Chainalysis)"],"security":[{"ApiKeyHeader":[]},{"ApiKeyQuery":[]},{"BearerJWT":[]}],"responses":{"200":{"description":"Success envelope; the payload is in data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Envelope"},"example":{"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}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"}},"x-response-fields":[{"name":"job_id","type":"number","description":"The polled job ID"},{"name":"type","type":"string","description":"Screening type this job was submitted for: kya or kyt"},{"name":"status","type":"string","description":"Job status: PENDING / PROCESSING / COMPLETE"},{"name":"result","type":"object","description":"Present when status=COMPLETE. Same KYA/KYT result object as the sync KYA Screen (v3) / KYT Screen (v3) endpoints"}],"parameters":[{"name":"jobId","in":"path","required":true,"schema":{"type":"string"}}]}},"/api/v3/rulesets/list":{"post":{"operationId":"v3_rulesets_list","summary":"List Rulesets","description":"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.","tags":["v3 Rulesets & Rules"],"security":[{"ApiKeyHeader":[]},{"ApiKeyQuery":[]},{"BearerJWT":[]}],"responses":{"200":{"description":"Success envelope; the payload is in data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Envelope"},"example":{"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"}]}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}},"x-response-fields":[{"name":"[].id","type":"number","description":"Ruleset id — the ruleset_id / scoring_ruleset_id you pass to the screening endpoints"},{"name":"[].ruleset_type","type":"string","description":"kya, kyt_in, kyt_out or scoring"},{"name":"[].is_builtin","type":"boolean","description":"Builtin rulesets are read-only; clone one to edit"},{"name":"[].rules_count","type":"number","description":"Live rules in the ruleset"},{"name":"[].highlighted_rules_count","type":"number","description":"Rules flagged as highlighted (★)"}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"ruleset_type":{"description":"Keep one kind: kya (address screening), kyt_in / kyt_out (transaction source / destination), scoring (fund-attribution score). Empty = all","type":"string","enum":["kya","kyt_in","kyt_out","scoring"]},"include_builtin":{"description":"false returns only the rulesets you created or cloned","type":"boolean","default":true}}}}}}}},"/api/v3/rulesets/get":{"post":{"operationId":"v3_rulesets_get","summary":"Get Ruleset","description":"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). ","tags":["v3 Rulesets & Rules"],"security":[{"ApiKeyHeader":[]},{"ApiKeyQuery":[]},{"BearerJWT":[]}],"responses":{"200":{"description":"Success envelope; the payload is in data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Envelope"},"example":{"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"}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}},"x-response-fields":[{"name":"rules[]","type":"array","description":"The ruleset's rules, in sort_order — see rule/get for the fields"},{"name":"scoring_config","type":"object","description":"Only on a scoring ruleset: formula bases, severity weights and verdict bands"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"ruleset_id":{"description":"Ruleset id","type":"number","default":22}},"required":["ruleset_id"]}}}}}},"/api/v3/rulesets/create":{"post":{"operationId":"v3_rulesets_create","summary":"Create Ruleset","description":"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). ","tags":["v3 Rulesets & Rules"],"security":[{"ApiKeyHeader":[]},{"ApiKeyQuery":[]},{"BearerJWT":[]}],"responses":{"200":{"description":"Success envelope; the payload is in data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Envelope"},"example":{"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"}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"description":"Display name","type":"string","default":"My KYA ruleset"},"ruleset_type":{"description":"kya, kyt_in, kyt_out or scoring","type":"string","enum":["kya","kyt_in","kyt_out","scoring"],"default":"kya"},"jurisdiction":{"description":"Free text, e.g. Global or SG","type":"string","default":"Global"},"scoring_config":{"description":"Scoring rulesets only: the config object (bases, severity weights, verdict bands). Omit to start from the default","type":"array","items":{}}},"required":["name","ruleset_type"]}}}}}},"/api/v3/rulesets/update":{"post":{"operationId":"v3_rulesets_update","summary":"Update Ruleset","description":"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). ","tags":["v3 Rulesets & Rules"],"security":[{"ApiKeyHeader":[]},{"ApiKeyQuery":[]},{"BearerJWT":[]}],"responses":{"200":{"description":"Success envelope; the payload is in data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Envelope"},"example":{"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"}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"ruleset_id":{"description":"Ruleset id (must be your own)","type":"number","default":0},"name":{"description":"New display name","type":"string"},"jurisdiction":{"description":"New jurisdiction","type":"string"},"scoring_config":{"description":"Scoring rulesets only: replaces the whole config","type":"array","items":{}}},"required":["ruleset_id"]}}}}}},"/api/v3/rulesets/delete":{"post":{"operationId":"v3_rulesets_delete","summary":"Delete Ruleset","description":"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). ","tags":["v3 Rulesets & Rules"],"security":[{"ApiKeyHeader":[]},{"ApiKeyQuery":[]},{"BearerJWT":[]}],"responses":{"200":{"description":"Success envelope; the payload is in data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Envelope"},"example":{"code":0,"msg":"success","data":{"id":41,"deleted":true}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"ruleset_id":{"description":"Ruleset id (must be your own)","type":"number","default":0}},"required":["ruleset_id"]}}}}}},"/api/v3/rulesets/clone":{"post":{"operationId":"v3_rulesets_clone","summary":"Clone Ruleset","description":"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). ","tags":["v3 Rulesets & Rules"],"security":[{"ApiKeyHeader":[]},{"ApiKeyQuery":[]},{"BearerJWT":[]}],"responses":{"200":{"description":"Success envelope; the payload is in data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Envelope"},"example":{"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"}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"source_ruleset_id":{"description":"Ruleset to copy (builtin or your own)","type":"number","default":22},"name":{"description":"Name for the copy; default appends \"(Copy)\"","type":"string"}},"required":["source_ruleset_id"]}}}}}},"/api/v3/rulesets/rule/list":{"post":{"operationId":"v3_rules_list","summary":"List Rules","description":"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). ","tags":["v3 Rulesets & Rules"],"security":[{"ApiKeyHeader":[]},{"ApiKeyQuery":[]},{"BearerJWT":[]}],"responses":{"200":{"description":"Success envelope; the payload is in data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Envelope"},"example":{"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"}]}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"ruleset_id":{"description":"Ruleset id","type":"number","default":22}},"required":["ruleset_id"]}}}}}},"/api/v3/rulesets/rule/get":{"post":{"operationId":"v3_rules_get","summary":"Get Rule","description":"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). ","tags":["v3 Rulesets & Rules"],"security":[{"ApiKeyHeader":[]},{"ApiKeyQuery":[]},{"BearerJWT":[]}],"responses":{"200":{"description":"Success envelope; the payload is in data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Envelope"},"example":{"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"}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}},"x-response-fields":[{"name":"rule_code","type":"string","description":"Your identifier, unique within the ruleset; it names the hit in results and snapshots"},{"name":"risk_level","type":"string","description":"low, medium, high or critical — drives the score's severity weight"},{"name":"action","type":"string","description":"alert, review, edd, block or allow — the disposition a hit recommends"},{"name":"direction","type":"string","description":"inbound, outbound or both"},{"name":"min_hops / max_hops","type":"number","description":"Path length the rule applies to; max_hops -1 = the address's own labels, no path"},{"name":"conditions[]","type":"array","description":"{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)"},{"name":"is_highlighted","type":"boolean","description":"Highlighted (★) rules are surfaced first in results; presentation only"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"rule_id":{"description":"Rule id","type":"number","default":0}},"required":["rule_id"]}}}}}},"/api/v3/rulesets/rule/create":{"post":{"operationId":"v3_rules_create","summary":"Create Rule","description":"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). ","tags":["v3 Rulesets & Rules"],"security":[{"ApiKeyHeader":[]},{"ApiKeyQuery":[]},{"BearerJWT":[]}],"responses":{"200":{"description":"Success envelope; the payload is in data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Envelope"},"example":{"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"}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"ruleset_id":{"description":"Your ruleset (not a builtin)","type":"number","default":0},"rule_code":{"description":"Unique code within the ruleset, e.g. MY_MIXER_EXPOSURE_H2","type":"string","default":"MY_MIXER_EXPOSURE_H2"},"name":{"description":"Rule name as shown in results","type":"string","default":"Exposure to a mixer (2 hops)"},"category":{"description":"Risk category label, e.g. Obfuscation","type":"string","default":"Obfuscation"},"description":{"description":"What the rule catches","type":"string"},"risk_level":{"description":"low, medium, high or critical","type":"string","enum":["low","medium","high","critical"],"default":"high"},"action":{"description":"alert, review, edd, block or allow","type":"string","enum":["alert","review","edd","block","allow"],"default":"review"},"direction":{"description":"inbound, outbound or both","type":"string","enum":["both","inbound","outbound"],"default":"inbound"},"min_hops":{"description":"Shortest path length the rule applies to (0 = any)","type":"number","default":2},"max_hops":{"description":"Longest path length; -1 = the address's own labels only","type":"number","default":2},"conditions":{"description":"JSON array of {parameter, operator, value}; every condition must hold","type":"array","items":{}},"is_highlighted":{"description":"Flag as a highlighted (★) rule","type":"boolean","default":false}},"required":["ruleset_id","rule_code","name","risk_level","action","conditions"]}}}}}},"/api/v3/rulesets/rule/update":{"post":{"operationId":"v3_rules_update","summary":"Update Rule","description":"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). ","tags":["v3 Rulesets & Rules"],"security":[{"ApiKeyHeader":[]},{"ApiKeyQuery":[]},{"BearerJWT":[]}],"responses":{"200":{"description":"Success envelope; the payload is in data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Envelope"},"example":{"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"}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"rule_id":{"description":"Rule id (in one of your rulesets)","type":"number","default":0},"name":{"description":"New name","type":"string"},"risk_level":{"description":"low, medium, high or critical","type":"string","enum":["low","medium","high","critical"]},"action":{"description":"alert, review, edd, block or allow","type":"string","enum":["alert","review","edd","block","allow"]},"direction":{"description":"inbound, outbound or both","type":"string","enum":["both","inbound","outbound"]},"conditions":{"description":"Replacement condition list (JSON array)","type":"array","items":{}},"is_highlighted":{"description":"Flag / unflag as highlighted","type":"boolean","default":false}},"required":["rule_id"]}}}}}},"/api/v3/rulesets/rule/delete":{"post":{"operationId":"v3_rules_delete","summary":"Delete Rule","description":"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). ","tags":["v3 Rulesets & Rules"],"security":[{"ApiKeyHeader":[]},{"ApiKeyQuery":[]},{"BearerJWT":[]}],"responses":{"200":{"description":"Success envelope; the payload is in data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Envelope"},"example":{"code":0,"msg":"success","data":{"id":3077,"deleted":true}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"rule_id":{"description":"Rule id (in one of your rulesets)","type":"number","default":0}},"required":["rule_id"]}}}}}},"/api/v3/rulesets/rule/highlight":{"post":{"operationId":"v3_rules_highlight","summary":"Highlight Rules","description":"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). ","tags":["v3 Rulesets & Rules"],"security":[{"ApiKeyHeader":[]},{"ApiKeyQuery":[]},{"BearerJWT":[]}],"responses":{"200":{"description":"Success envelope; the payload is in data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Envelope"},"example":{"code":0,"msg":"success","data":{"affected":2}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"ruleset_id":{"description":"Your ruleset","type":"number","default":0},"rule_ids":{"description":"JSON array of rule ids in that ruleset","type":"array","items":{}},"is_highlighted":{"description":"true to flag, false to unflag","type":"boolean","default":true}},"required":["ruleset_id","rule_ids","is_highlighted"]}}}}}}},"components":{"securitySchemes":{"ApiKeyHeader":{"type":"apiKey","in":"header","name":"X-Api-Key","description":"API key from Account › API Keys"},"ApiKeyQuery":{"type":"apiKey","in":"query","name":"apikey"},"BearerJWT":{"type":"http","scheme":"bearer","bearerFormat":"JWT","description":"A console session token"}},"schemas":{"Envelope":{"type":"object","required":["code","msg"],"properties":{"code":{"type":"integer","description":"0 on success, -1 on error"},"msg":{"type":"string","description":"\"success\", or the error naming the problem"},"data":{"description":"The payload — see each operation's example and x-response-fields"}}},"Error":{"type":"object","required":["code","msg"],"properties":{"code":{"type":"integer","const":-1},"msg":{"type":"string","description":"What went wrong; validation errors name the field"}}}},"responses":{"BadRequest":{"description":"Invalid input; msg names the field","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Unauthorized":{"description":"No or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Forbidden":{"description":"The ruleset is builtin and read-only — clone it first","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"NotFound":{"description":"The id does not exist or is not yours","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Conflict":{"description":"rule_code already exists in this ruleset","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"TooManyRequests":{"description":"Too many concurrent screenings; back off or use mode=async","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}