The Agent Compliance Playbook

Seven patterns for putting OFAC sanctions screening on an AI agent's payment path — and the two mistakes that quietly switch it off. Every example runs against the live API.

No gate, no signup. Print it, paste it into your repo, send it to whoever reviews your payment code.

PATTERN 1

Screen before you sign, not after you send

The only screen that prevents a violation runs between the decision to pay and the signature. Screening after settlement produces a report, not a defence. Put the call on the path that constructs the transaction, so there is no code route that pays without passing it.

r = requests.get("https://sanctionsai.dev/sanctions",
                 params={"wallet": counterparty})
if not r.json()["clean"]:
    raise Halt("counterparty on OFAC SDN")
sign_and_send(counterparty, amount)
PATTERN 2

Fail closed, never open

This is the pattern that most often goes wrong. A timeout, a 500, or a network blip must halt the payment — not wave it through. An except: pass around the screen turns your compliance layer into a decoration that silently disables itself exactly when the network is having a bad day.

# WRONG — the fine arrives on the day the API is slow
try:
    clean = screen(w)
except Exception:
    clean = True          # <-- fails OPEN

# RIGHT
try:
    clean = screen(w)
except Exception:
    clean = False         # <-- fails CLOSED, payment halts
PATTERN 3

Screen the resolved address, not the alias

ENS names, handles, invoice fields and address books all resolve at signing time. Screen the bytes you are actually going to pay. An alias that pointed somewhere clean yesterday can point at an SDN address today, and the resolution step is where that switch happens.

addr = resolve(invoice.payee)   # resolve FIRST
assert screen(addr)["clean"]    # then screen the result
pay(addr, invoice.amount)
PATTERN 4

Screen at settle time, not at quote time

In an x402 round-trip the 402 challenge and the settlement can be separated by minutes or hours. The SDN list changes on its own schedule. Screen inside the settle step, where the transfer is authorised — a screen attached to the quote is a screen against stale state.

# x402: the check belongs between 402 and settle
challenge = client.get(url)            # 402 Payment Required
if not screen(challenge.pay_to)["clean"]:
    raise Halt("pay_to is sanctioned")
client.settle(challenge)
PATTERN 5

Cache, but never longer than the data refreshes

Screening every payment is correct; screening the same counterparty 400 times a minute is wasteful. Cache the result, and set the TTL below the list refresh interval — this data syncs hourly, so an hour is the ceiling. Cache clean:false aggressively and clean:true conservatively: a false negative costs $377,700, a false positive costs one retry.

CLEAN_TTL   = 900    # 15 min — expires well inside the sync window
BLOCKED_TTL = 86400  # a listing rarely disappears within a day
PATTERN 6

Keep the receipt for every payment

If OFAC ever asks, the answer you want is a stored response, not a memory. Log the full JSON body, the timestamp, and the exact identifier you screened, keyed to the payment. Screening without retaining proof means doing the work and keeping none of the credit.

audit.write({
  "payment_id": pid,
  "screened":   counterparty,
  "response":   r.json(),   # store the WHOLE body
  "at":         time.time(),
})
PATTERN 7

Sanctions is binary; risk is graded — use both

/sanctions answers one question: is this counterparty on a list. It will not tell you that an agent which has moved $40 all month is suddenly sending $9,000 to a wallet created yesterday. /risk grades that. Gate on sanctions, route on risk: decline the match, queue the anomaly for a human.

if not screen(w)["clean"]:
    return DECLINE                       # binary, no override
score = risk(counterparty_id=cid, amount=amt, rail="x402")
if score["recommendation"] == "review":
    return queue_for_human(score)        # graded, escalates

Pattern 1 takes about thirty seconds to try

The free tier needs no key and no signup: 5 checks a day, forever. Run it against a wallet you are about to pay.

Screen a wallet now → See the plans

Shipping to production? Dev is $19/mo for 10,000 checks — the same volume costs $500 at our published $0.05 pay-as-you-go rate.