Build against proof.
Sanctions screening, OFAC name checks, and UK company verification, paid per call over the Machine Payments Protocol on Tempo. Every response is a cryptographically signed attestation you can verify yourself.
Overview
OnchainDiligence is a pay-per-call HTTP API. There are no accounts, API keys, or subscriptions. A client calls an endpoint, receives a 402 Payment Required challenge, pays a few cents of stablecoin on Tempo, and retries the request with proof of payment attached. The server verifies payment, runs the check against live data, and returns a signed result.
Each response carries an Ed25519 signature over the data and a strict timestamp, so a result can be stored as evidence and verified later by anyone holding the public key — without trusting or even contacting this service again.
- Sanctions data comes from the Chainalysis on-chain sanctions oracle, a free public smart contract read on Ethereum mainnet. No key, no relationship required.
- Company data comes from UK Companies House open government data.
- The fee covers infrastructure only — not the underlying data, which is free and public.
Base URL
All API requests go to the api subdomain over HTTPS:
https://api.onchaindiligence.com
The bare domain onchaindiligence.com serves this site, not the API — point integrations at api.onchaindiligence.com.
Quickstart
See a live 402 challenge right now — no wallet, no key, just curl:
# returns 402 Payment Required with the MPP challenge curl -i https://api.onchaindiligence.com/screen/0x7f268357A8c2552623316e2562D90e642bB538E5
To run the check and get a signed result, the mppx CLI handles the entire challenge-pay-retry flow for you. Set the private key of a Tempo wallet holding pathUSD, then call any endpoint:
# key of a wallet holding a little pathUSD on Tempo export MPPX_PRIVATE_KEY=0xYOUR_KEY npx mppx https://api.onchaindiligence.com/screen/0x7f268357A8c2552623316e2562D90e642bB538E5
The CLI receives the 402, builds and submits the payment on Tempo, and prints the signed JSON result. To call the API from your own code instead, see how payment works below.
Command line
The @onchaindiligence/cli package is the fastest way to try the API. Some commands are free and need no key — run them straight from npx:
# check the API + upstream data sources npx @onchaindiligence/cli health # verify a signed attestation locally (Ed25519, against the published key) npx @onchaindiligence/cli verify result.json # is an attestation anchored on Tempo? npx @onchaindiligence/cli anchored <signature>
verify exits 0 if valid, 3 if the signature fails, and 2 if the response was unsigned — so it drops straight into a CI step.
The paid checks settle a real per-call payment, so they read a funded payer key from the PAYER_KEY environment variable:
# a viem private key funded on the payment rail export PAYER_KEY=0xYOUR_KEY npx @onchaindiligence/cli screen 0x7f26…38E5 npx @onchaindiligence/cli screen-name "Vladimir Putin" npx @onchaindiligence/cli company 00000006 npx @onchaindiligence/cli us-company AAPL npx @onchaindiligence/cli diligence 0x7f26… 00000006
Add --json for raw output you can pipe into jq. Install globally with npm i -g @onchaindiligence/cli to get the onchaindiligence and ocd commands. Source: github.com/Qazza1/onchaindiligence-cli.
Sandbox / test mode
Build and CI-test your full integration — including a positive sanctions hit — without paying or touching production. Sandbox screening is free, takes no payment, and never calls the live oracle.
It lives on its own path, /sandbox/screen/:address, and only accepts the documented test-vector addresses below. A real address is deliberately refused, so you can never screen a real counterparty against test data by mistake.
# always SANCTIONED — test your block/deny path GET /sandbox/screen/0x00000000000000000000000000000000000000ba # always CLEAN — test your allow path GET /sandbox/screen/0x00000000000000000000000000000000c1ea0000 GET /sandbox/screen/0x0000000000000000000000000000000000000001 # list the vectors any time GET /sandbox
A sandbox response mirrors the shape of a real one so you can code against the same structure — but it is always flagged and never signed. The attestation block carries signed: false and sandbox: true with an explicit note, and the body carries "source": "sandbox". A sandbox result can never be mistaken for a real determination or pass verification.
{
"data": {
"address": "0x…ba",
"sanctioned": true,
"source": "sandbox"
},
"attestation": { "signed": false, "sandbox": true },
"sandbox": true
}
Because it's free and deterministic, it drops straight into CI — assert that your code blocks the sanctioned vector and allows the clean one, on every build:
# the sanctioned vector must be blocked by your code curl -s .../sandbox/screen/0x…ba | jq -e '.data.sanctioned == true' # the clean vector must pass curl -s .../sandbox/screen/0x…c1ea0000 | jq -e '.data.sanctioned == false'
When you're ready for real screening, switch the path from /sandbox/screen/:address to the production /screen/:address — same response shape, now paid and signed.
How payment works
Payment rides on the HTTP 402 standard over the Machine Payments Protocol (MPP). The exchange is three steps:
1 — Call with no payment
The server responds 402 Payment Required with a WWW-Authenticate: Payment header. The challenge encodes the amount, the stablecoin contract, the Tempo chain id, and the recipient.
HTTP/1.1 402 Payment Required WWW-Authenticate: Payment id="...", method="tempo", intent="charge", request="<base64 challenge>" Content-Type: application/problem+json
2 — Pay and retry
The client pays the requested stablecoin amount to the recipient on Tempo, then retries the same request with the payment proof attached in the Authorization header. The mppx CLI and SDK do this automatically.
3 — Get a signed result
The server verifies the payment, runs the check, and returns 200 OK with the result and an attestation. If the upstream data source is unreachable, the server returns 503 before requesting payment — you are never charged for a check that cannot complete.
| Field | Value |
|---|---|
| Network | Tempo mainnet · chain id 4217 |
| Currency | pathUSD · 0x20c0…0000 (predeployed) |
| Settlement | On-chain, per call |
Sanctions screen
Checks a single wallet address against the Chainalysis on-chain sanctions oracle (US / EU / UN lists). Returns a boolean flag — sanctioned or not — signed and timestamped.
| Parameter | Description |
|---|---|
| address | The wallet address to screen, or an ENS name. 0x + 40 hex, or e.g. vitalik.eth |
const r = await od.screen('0x7f26…38E5') // or pass an ENS name: od.screen('vitalik.eth')
npx mppx https://api.onchaindiligence.com/screen/0x7f26…38E5
# MCP tool (Base / x402) Tool: screen_wallet Args: { "address": "0x7f26…38E5" }
{
"data": {
"address": "0x7f268357A8c2552623316e2562D90e642bB538E5",
"sanctioned": false,
"identifications": [],
"source": "Chainalysis on-chain sanctions oracle",
"checked_at": "2026-06-20T06:44:15.164Z"
},
"attestation": {
"signed": true,
"key_id": "ed25519-D8wfc7civVNG05Ds",
"algorithm": "ed25519",
"signature": "wFRfDRSiU5pMOG9y…yqHYfDQ"
}
}
A sanctioned address returns "sanctioned": true with one honest identification. The oracle returns a match flag only — it does not return rich case detail.
Counterparty verdict
Returns a single, signed decision — PASS or BLOCK — with human-readable reasons, rather than raw screening data for you to interpret. Built for agents that need to act, not analyse. Every response is signed with the same key as every other route, so the decision itself is verifiable.
| Parameter | Description |
|---|---|
| address | The wallet address to evaluate, or an ENS name. 0x + 40 hex, or e.g. vitalik.eth |
| Verdict | Meaning |
|---|---|
| BLOCK | The address is sanctioned (OFAC, via the Chainalysis on-chain oracle). A hard legal line — do not transact. |
| PASS | No sanctions match found. |
This endpoint never returns a false PASS. If an upstream check fails, it returns an error rather than passing the address through.
npx mppx https://api.onchaindiligence.com/verdict/0x7f26…38E5
# unpaid requests return 402 with a payment challenge curl https://api.onchaindiligence.com/verdict/0x7f26…38E5
{
"data": {
"verdict": "BLOCK",
"reasons": [
"Address is on the sanctions list (OFAC via Chainalysis on-chain oracle)."
],
"address": "0x8576acc5c05d6ce88f4e49bf65bdf0c62f91353c",
"signals": {
"sanctions": { "checked": true, "sanctioned": true },
"direct_counterparty_exposure": {
"checked": true,
"transfers_scanned": 200,
"counterparties_found": 112,
"counterparties_screened": 25,
"sanctioned_counterparties": [],
"scope": "Direct (one-hop) counterparties observed on Tempo mainnet only…"
}
},
"verdict_basis": {
"live_signals": ["sanctions", "direct_counterparty_exposure"],
"not_yet_evaluated": ["risk_score", "mixer_exposure", "wallet_age", "sanctions_proximity"],
"note": "BLOCK = this address is sanctioned. WARN = it is not, but a direct counterparty is. PASS = neither, within the stated scope — not a full risk clearance."
},
"checked_at": "2026-07-07T14:22:08.441Z"
},
"attestation": {
"signed": true,
"key_id": "ed25519-D8wfc7civVNG05Ds",
"algorithm": "ed25519",
"signature": "UN4TzBvkRsf0eGm4…ZFyElhq1Cg"
}
}
Three verdicts, and they mean different things. BLOCK means the address itself is sanctioned — a hard legal line. WARN means it is not sanctioned but transacted directly with an address that is: a counterparty risk signal, never a designation of the address you asked about. We do not designate by association. PASS means neither was found within the stated scope.
Read verdict_basis before you trust a PASS. live_signals lists the signals that actually ran for that request. If a signal could not be evaluated it is reported as not evaluated — never silently assumed clean — and the reasons say so explicitly. Direct counterparty exposure is deliberately narrow: one hop only (not a multi-hop proximity score), Tempo mainnet only (an address active elsewhere shows no counterparties here), and a bounded recent window. Those limits ride in signals.direct_counterparty_exposure.scope on every response. No sanctioned counterparty found does not mean none exists.
OFAC name screen
Screens a person or company name against the official U.S. Treasury OFAC Specially Designated Nationals (SDN) list — public-domain government data. Uses transparent fuzzy matching (token overlap + edit distance) against primary names and strong aliases, returning confidence-scored candidates. Weak AKAs are not screened, per OFAC guidance.
| Parameter | Description |
|---|---|
| name | Person or company name to screen. min 2 characters |
| threshold | Optional match cutoff, 0.5–1.0. default 0.85 |
const r = await od.screenName('Vladimir Putin') // optional: { threshold: 0.9 }
npx mppx 'https://api.onchaindiligence.com/screen-name?name=Vladimir Putin'{
"data": {
"query": "Vladimir Putin",
"hit": true,
"matches": [
{
"ent_num": 306,
"matched_name": "PUTIN, Vladimir Vladimirovich",
"matched_on": "primary",
"program": "RUSSIA-EO14024",
"score": 0.9
}
],
"threshold": 0.85,
"source": "U.S. Treasury OFAC SDN list (public domain)"
},
"attestation": { "signed": true, … }
}
Company check
Looks up a UK company by its registration number: status, type, incorporation date, registered address, and the people with significant control (PSC) behind it.
| Parameter | Description |
|---|---|
| companyNumber | UK Companies House registration number. e.g. 00000006 |
const r = await od.verifyCompany('00000006')
npx mppx https://api.onchaindiligence.com/company/00000006
# MCP tool (Base / x402) Tool: verify_uk_company Args: { "companyNumber": "00000006" }
{
"data": {
"profile": {
"companyNumber": "00000006",
"companyName": "MARINE AND GENERAL MUTUAL LIFE ASSURANCE SOCIETY",
"status": "dissolved",
"incorporatedOn": "1862-10-25",
"registeredAddress": "Cms Cameron Mckenna Llp Cannon Place, London, EC4N 6AF"
},
"pscList": [],
"source": "UK Companies House (open government data)"
},
"attestation": { "signed": true, "key_id": "ed25519-D8wfc7civVNG05Ds", … }
}
US company check
Looks up an SEC-registered US public company via EDGAR by ticker, CIK, or name: registered entity, CIK, industry (SIC), state of incorporation, exchanges, business address, and most recent filing. Covers public companies and funds only — private US companies register at the state level and are not in EDGAR, so the result carries an explicit coverage note.
| Parameter | Description |
|---|---|
| q | Ticker, SEC CIK, or company name. e.g. AAPL, 0000320193, Apple Inc |
const r = await od.verifyUsCompany('AAPL')
npx mppx https://api.onchaindiligence.com/us-company?q=AAPL
# MCP tool (Base / x402) Tool: verify_us_company Args: { "query": "AAPL" }
{
"data": {
"source": "SEC EDGAR",
"cik": "0000320193",
"name": "Apple Inc.",
"sic_description": "Electronic Computers",
"state_of_incorporation": "CA",
"exchanges": ["Nasdaq"],
"latest_filing": { "form": "4", "filing_date": "2026-06-17" },
"coverage_note": "EDGAR covers SEC-registered (public) companies only…"
},
"attestation": { "signed": true, "key_id": "ed25519-D8wfc7civVNG05Ds", … }
}
Combined diligence
Runs both checks in one call, in parallel. Returns independent results plus an explicit disclaimer that no link between the wallet and the company is established by the data.
| Query | Description |
|---|---|
| wallet | Wallet address to screen. 0x + 40 hex |
| company | UK company registration number. |
const r = await od.diligence({ wallet: '0x7f26…38E5', company: '00000006', })
npx mppx 'https://api.onchaindiligence.com/diligence?wallet=0x7f26…38E5&company=00000006'# MCP tool (Base / x402) Tool: diligence Args: { "wallet": "0x7f26…38E5", "company": "00000006" }
{
"data": {
"wallet_check": { "sanctioned": false, … },
"company_check": { "profile": { … } },
"link_disclaimer": "These are independent checks against separate data sources. No verified link between the wallet and the company is established by this data."
},
"attestation": { "signed": true, … }
}
On-chain anchoring
Any signed attestation can be anchored on Tempo for an immutable, timestamped, tamper-evident record that a check happened. Only the keccak256 hash of the attestation signature is written on-chain — never the wallet, name, company, or result — so records stay private while remaining provable by anyone. Anchoring is decoupled from checks: it never blocks or delays a paid response.
Anchors the hash of an attestation you hold. Pass the attestation signature in the JSON body. Idempotent — re-anchoring sends no new transaction.
await od.anchor(r.attestation.signature)
POST /anchor
{ "signature": "wFRfDRSiU5pMOG9y…yqHYfDQ" }Checks whether an attestation has been anchored on-chain, and when — so anyone can verify a record independently.
{
"anchor_hash": "0xadb12c9f…401d",
"anchored": true,
"anchored_at": "2026-06-24T14:44:21.000Z",
"chain": "Tempo",
"contract": "0x3B4B…99cF"
}
Discovery & health
These endpoints are free and require no payment — they describe the service and report its status for humans and machines.
Reports whether each upstream data source is reachable and whether response signing is configured. Returns 200 when healthy and 503 when any upstream is degraded, so automated monitors can key off the status code.
{
"status": "ok",
"upstreams": {
"sanctions_oracle": "reachable",
"companies_house": "reachable"
},
"attestation": "configured"
}
Service information: the routes, prices, and attestation key URL.
A complete, machine-readable OpenAPI 3.1 document describing every route, parameter, response schema, and the per-call payment terms — for automatic agent and tooling discovery.
Attestation
Every paid response includes an attestation object. The signature is computed over the exact response data plus the issue timestamp and key id, using an Ed25519 key held only by the server.
| Field | Meaning |
|---|---|
| signed | Whether the response is cryptographically signed. |
| key_id | Identifier of the signing key. e.g. ed25519-D8wfc7civVNG05Ds |
| algorithm | Always ed25519. |
| signature | base64url signature over the signing input. |
| issued_at | ISO-8601 timestamp the response was signed. |
The public key is published at a well-known URL, so anyone can verify a stored response without contacting the API:
curl https://api.onchaindiligence.com/.well-known/attestation-key
Verify a response
The signature is over JSON.stringify({ data, issued_at, key_id }) using those exact field values. To check a stored attestation, fetch the public key and verify — no call back to the API:
import crypto from 'node:crypto' const input = JSON.stringify({ data: envelope.data, issued_at: envelope.attestation.issued_at, key_id: envelope.attestation.key_id, }) const ok = crypto.verify( null, Buffer.from(input), publicKeyPem, // from /.well-known/attestation-key Buffer.from(envelope.attestation.signature, 'base64url'), ) // ok === true -> clean at that exact moment, provably
Errors
Errors use standard HTTP status codes. Bad input is rejected before any payment is requested.
WWW-Authenticate header carries the payment challenge.Rate limits
Requests are rate limited per client. When you exceed the limit the API returns 429 with a Retry-After header and an X-RateLimit-Remaining count on every response. Back off and retry after the indicated interval.
MCP server
All five checks are also exposed as a paid Model Context Protocol server, so an AI agent can discover them as tools and pay for them autonomously — no API key, no account. It is a live remote server over Streamable HTTP, listed in the official MCP Registry as com.onchaindiligence/compliance.
Connecting the server surfaces three tools, each priced and payment-gated:
screen_wallet— sanctions-screen a wallet address. $0.01screen_name— OFAC SDN name screening. $0.02verify_uk_company— look up a UK company. $0.05verify_us_company— look up a US public company (SEC EDGAR). $0.05diligence— run wallet + company together. $0.05
How payment works
The MCP server settles with x402 — the open agent-payment standard built on HTTP 402. A tool call carries no payment on first attempt; the server returns the payment requirements, the agent signs a USDC authorization, and the call is retried with the payment attached. The server verifies and settles before running the check. The flow is non-custodial: payment goes directly from the agent's wallet to the recipient, and the server never holds funds.
| Field | Value |
|---|---|
| Protocol | x402 over MCP · payment in tools/call _meta |
| Currency | USDC |
| Network | Base mainnet |
| Settlement | On-chain, per call, non-custodial |
Discovery
The server is published in the official MCP Registry, the canonical source that downstream MCP clients and marketplaces draw from. An agent that speaks x402 can find it, read its tool schemas, pay, and call — with no manual configuration.
The full source for both the HTTP API and the MCP server lives on GitHub.