Ozone Screening API
Batch-screen addresses on every THORChain chain. Each verdict names its sources, provenance and — for traces — the THORChain transactions, and every answer is signed with Ozone's Ed25519 key. Nodes can instead download the signed snapshot and screen locally. No auth, nothing about the request is stored.
| Endpoint | What it does |
|---|---|
| POST /api/v1/screen | Batch screening (≤ 100). Body {"addresses": ["…" | {"address","chain"}], "policy": {"flagAt": "high"}}. Signed ozone.screen.v1 answer. |
| GET /api/v1/screen?address=&chain= | Same for one address. |
| GET /api/screen?address= | Original endpoint, kept compatible: flagged + matches[].source, plus the signed v1 answer as "attestation". |
| GET /api/v1/address/<address> | Everything about one address: verdict, every listing with provenance (history included), THORChain flows received and sent, and the small transfers (each under $50) that count toward a TRACE_SMALL_TRANSFERS total. |
| GET /api/v1/snapshot | Newest signed snapshot manifest (version, build time, SHA-256 of the payload). |
| GET /api/v1/snapshot/<version> | Snapshot payload (gzip JSON of every listed and traced address with its reasons). |
| GET /api/v1/keys | Public keys that sign snapshots and answers — pin them. |
| GET /api/health | Database, snapshot age, per-source sync state, real-time trace cursor. |
| POST /api/v1/submissions | Report an address or appeal a flag ({kind, address, chain?, message, evidence?, contact?}). |
| GET /api/flagged | The flagged counters: all flagged addresses, flagged thor1 addresses, monitored thor1 accounts flagged (+ breakdown). |
| GET /api/flagged?list=all|thor|users | Each list (JSON, paged with offset/limit ≤ 1000; filter q, source, chain; format=csv for all rows). |
Chains: THOR, BTC, ETH, BSC, BASE, AVAX, GAIA, LTC, BCH, DOGE, TRON, XRP, SOL (+ ZEC, XMR, DASH, BSV, BTG, ETC, ARB, BNB for listed addresses). Without a chain hint every valid reading of the address is checked. Risk levels and reasons: methodology. Node integration: INTEGRATION.md in the repository.
{
"type": "ozone.screen.v1",
"id": "5f0c…",
"issuedAt": "2026-09-27T10:00:00.000Z",
"policy": { "flagAt": "high" },
"snapshot": { "version": 1790500000, "builtAt": "…", "sha256": "…" },
"results": [{
"input": "19qs8FPxK6VfkqHL7TPZDhw3r2rJPU6yhE",
"keys": ["btc:19qs8FPxK6VfkqHL7TPZDhw3r2rJPU6yhE"],
"valid": true,
"status": "flagged",
"risk": "high",
"reasons": [{
"code": "TRACE_SWAP",
"source": "thorchain_trace",
"category": "traced",
"risk": "high",
"text": "Received 1.2133 BTC.BTC (~$111,000) from 0xb21e…69f4, listed by Bybit Exploiter 65 (ethlabels) via THORChain swap 6F708AFC… on 2025-02-25",
"ref": "https://runescan.io/tx/6F708AFC…",
"trace": { "hop": 1, "action": "swap", "txid": "6F708AFC…", "from": "0xb21e…69f4", "originSource": "ethlabels" }
}],
"reference": "oz:v1790500000:flagged:TRACE_SWAP"
}],
"signature": { "alg": "ed25519", "keyId": "oz…", "sig": "…" }
}{
"input": "1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa",
"keys": ["btc:1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa",
"bch:qp3wjpa3tjlj042z2wv7hahsldgwhwy0rq9sywjpyy",
"bsv:1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa"],
"valid": true,
"status": "clean",
"risk": "none",
"reasons": [],
"snapshot": { "version": 1790500000, "ageSeconds": 212,
"stale": false, "sha256": "…" }
}Integration Examples
curl -s https://ozone.redacted.gg/api/v1/screen \
-H 'content-type: application/json' \
-d '{"addresses":["0x098B716B8Aaf21512996dC57EB0615e2383E2f96",
{"address":"qpm2qsznhks23z7629mms6s4cwef74vcwvy22gdx6a","chain":"BCH"}],
"policy":{"flagAt":"high"}}'// Online, with signature verification
import { verifyScreenResponse } from '@redacted/ozone-client';
const res = await fetch('https://ozone.redacted.gg/api/v1/screen', {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify({ addresses: [address] })
});
const body = verifyScreenResponse(await res.json(), [OZONE_API_KEY]);
if (!body) throw new Error('unsigned or forged answer');
const [verdict] = body.results; // status, risk, reasons, snapshot// Node-local: no request to Ozone at screening time
import { OzoneClient } from '@redacted/ozone-client';
const oz = new OzoneClient({
trustedKeys: [OZONE_SNAPSHOT_KEY], // pin it (see /api/v1/keys)
cacheDir: '/var/lib/node/ozone', // survives restarts and outages
});
await oz.init(); // verified cache, then refresh
oz.start(); // refresh every 10 min
const v = oz.screen(address, 'BTC'); // → status, risk, reasons,
// snapshot.version / ageSecondsTry it live
POSTs to /api/v1/screen and shows the signed answer.
/api/flaggedTwo separate numbers, each with its list: all flagged addresses (every key in the newest signed snapshot at risk ≥ high, any chain; ?list=all) and the flagged thor1 addresses among them (?list=thor). Rows carry only what the snapshot publishes: address, chain, risk, reason codes, sources, incident. ?list=users is the separate count of monitored thor1 accounts that are flagged themselves or through a linked L1 address (formerly "Flagged THORChain Users"; the legacy fields totalFlagged, flaggedThorUsers and flaggedAddresses of the plain call keep that meaning).