ScreeningPolicies endpoints in the API reference.
Use it when you need a condition the editor cannot express: a negation, a nested combination of conditions, or a screening provider field the editor does not offer.
Prerequisites
- Your own provider key for TRM Labs or Chainalysis. See bring your own screening key.
- The Admin role on the environment.
- Screening policies enabled for the environment. They are in Beta.
Shape
A policy holds two ordered lists of rules:preScreen decides whether the provider is called. postScreen decides the verdict from the provider’s response. Each list holds at most 200 rules. Both are optional, and a policy with neither behaves exactly like an environment with no policy.
Every rule is one condition and one action:
Actions
then in preScreen is SCREEN or SKIP. If no pre-screen rule matches, the address is screened.
then in postScreen is BLOCK, ALERT, or ALLOW. If no post-screen rule matches, the address is allowed.
Conditions
A condition is one of these. Each object holds exactly one form.all and any take 1 to 200 conditions. Conditions nest up to 5 levels deep.
Comparing one field
op is one of eq, neq, in, nin, gte, lte, gt, lt, or contains.
inandnintake an array of 1 to 200 values. Every other operator takes a single value.containsis a substring test. It is never a regular expression.
400.
Ordering operators do not work on free text.
contains works only on free text.
Quantifying over a provider array
Both providers report signals as arrays.exists matches when one element of the array satisfies every condition in where:
where binds to the same element. That is the point of exists. Two unrelated signals cannot jointly satisfy one rule, so the example above matches an address carrying a high-scoring Mixer signal, not an address that carries a Mixer signal and separately carries something else scoring 10.
Inside where, a bare field name such as category reads the bound element. An absolute path such as chainalysis.risk still reads the response.
Matching every screen
{ "always": true } is how you turn a surface off:
- It is accepted only in
preScreen, and only as a rule’s whole condition. Nested anywhere, or used inpostScreen, it is a400. falseis a400. The form exists to match, not to be switched off.- Nothing may follow it. A later rule is unreachable and the write is rejected.
Fields
Both phases, both providers
tx.amountUsd, tx.asset, chain, and address. These need no provider response, so they work in both phases, and they are the only fields a preScreen rule may use.
addressis compared exactly, with no case folding, because Base58 and Base32 chains are case significant.chainis compared exactly against the chain Dynamic recorded for the wallet, such asEVMorSOL.tx.assetis compared case-insensitively, sousdcmatchesUSDC.tx.amountUsdandtx.assetare present only where the request established them. Neither is available on the signing path.
TRM Labs
in for an exists is trm.addressRiskIndicators or trm.entities.
riskType is OWNERSHIP, COUNTERPARTY, or INDIRECT. Category strings are compared case sensitively, in the TitleCase TRM returns, such as Mixer. Risk score levels are numbers and are compared numerically.
Chainalysis
in for an exists is chainalysis.addressIdentifications, chainalysis.exposures, or chainalysis.triggers.
chainalysis.risk is an ordered level, so gte works on it: Low, Medium, High, Severe. category, chainalysis.cluster.category, direction, and the ruleTriggered fields are compared case-insensitively. chainalysis.cluster.name and chainalysis.riskReason are compared exactly. cluster and ruleTriggered are nullable, so a field beneath either reads as unknown when the provider omits the parent.
Unknown values
A field the response does not carry is unknown, not absent and not false. A rule fires only on a definite match.- A condition on an unknown field does not match, and it does not make the rule match either.
not,neq, andnindo not fire on an unknown value. They are not a way to test for absence.existsover an empty array does not match.existsover a field the response omits is unknown.
Writing a policy
A write replaces the whole rule blob for one provider’s sign-in policy or transaction policy.- Send
revisionexactly as the read returned it. A stalerevisionis rejected with409rather than applied, so a concurrent edit cannot be silently overwritten. - Omit
revisiononly when creating that policy for the first time. - Rules are validated before they are stored. A field the provider’s schema does not define, an operator the field does not accept, an operand of the wrong type, a list longer than 200, and a condition nested too deep are each a
400. - A TRM policy naming a
chainalysis.*field is a400, and the reverse is too. The provider comes from the path, and the rules must match it. - A saved policy is live everywhere within 60 seconds.
Neither the sanctions baseline nor the trailing allow appears in
rules. Both are generated on every screen, so there is nothing to read back and no request body that can weaken them. The API calls the baseline the sanctions floor.Rules the policy editor writes
The policy editor writes ordinary rules in this format. A rule you send in the same shape is shown in the editor as editable, not as an advanced rule. See Configure a screening policy for the editor itself.When to screen
Each When to screen control is stored as aSKIP rule on everything it excludes. Screening only Solana, and only transfers of $1,000 or more:
A category rule
A category rule is oneexists over the provider’s signal array. Blocking Mixer on the address itself at a TRM Labs risk score of 10 (high) or above:
How the policy editor treats an API-authored rule
The policy editor recognizes a rule by its structure. A rule outside the shape it writes is shown read-only, at its real position in the evaluation order, with a delete control.- Saving from the policy editor does not rewrite an advanced rule and does not move it.
- An advanced rule does not disable the editor. Rules the editor does recognize stay editable.
- Reading a policy and saving it back unchanged leaves it unchanged.
Address exceptions
An address exception is an entry on the provider, not a rule in the policy. It applies to both the sign-in policy and the transaction policy unless you narrow it, and each entry is written on its own:dispositionisDENYto block the address orALLOWto exempt it from your rules.- Omit
chainto apply the entry to every chain. - Omit
appliesToScopeto apply the entry to both policies. - An address already listed for that provider and chain is rejected with
409. Remove the existing entry to change its decision. - An exempted address is still blocked if it is sanctioned. The sanctions baseline is evaluated before address exceptions.
Screening an address yourself
Two endpoints return a screening verdict on demand. Both are policy aware, and both gained an additiveaction of allow, alert, or block alongside the field they already returned. An alert never blocks, so an integration that reads only isBlocked or decision behaves exactly as it did before policies existed.
They take different inputs, so which one you want follows from what you have:
Reach for the POST before signing from a wallet you do not control. Do not build it out of the GET: extracting destinations from a raw payload means reimplementing per-chain decoding for EVM, Solana, Bitcoin, Tron, Sui, and Stellar, and the verdict over a set of addresses is not the same guarantee as a verdict over each one in turn.
The
wallet/sanctions path predates screening policies. The endpoint is policy aware and returns the full allow, alert, or block verdict, not a sanctions yes or no. The path is kept because it is released.GET /sdk/{environmentId}/wallet/sanctions:
scopechooses which policy to evaluate, and defaults toTRANSACTION. This is the only surface where the caller chooses the policy. Every other surface always uses the policy listed for it on screening policies.amountUsdandassetsupply values to whichever policyscopeselected. They are independent ofscope: passing an amount does not move you to the transaction policy. They also never trigger a provider call and are not part of the cache key, so screening an address as a $10,000 transfer is free.applyPolicies: falsereturns the baseline verdict alone. It does not reduce the number of provider calls.
POST /sdk/{environmentId}/wallet/screenTransaction:
- The request carries the transaction payload and takes no
scope, no amount, and no policy bypass. It always evaluates the transaction policy. This is what an SDK blocks a signature on, so it accepts nothing from the caller that could weaken the verdict. - It screens at most 100 addresses in one request. A payload that decodes to more is not screened at all, rather than screened in part.
- It is rate limited per credential and environment.