API Reference

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.

Not a compliance programOnchainDiligence returns factual checks and signed attestations. It is not legal or compliance advice, and is not a substitute for a full compliance program. Results are never cached.

Base URL

All API requests go to the api subdomain over HTTPS:

base url
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:

terminal · no setupCopy
# 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:

terminalCopy
# 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:

terminal · no key requiredCopy
# 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:

terminal · paid commandsCopy
# 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.

test vectorsCopy
# 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.

sandbox sanctioned responseCopy
{
  "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:

CI exampleCopy
# 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.

402 response · headers
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.

FieldValue
NetworkTempo mainnet · chain id 4217
CurrencypathUSD · 0x20c0…0000 (predeployed)
SettlementOn-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.

GET /screen/:address$0.01 per call
ParameterDescription
addressThe wallet address to screen, or an ENS name. 0x + 40 hex, or e.g. vitalik.eth
Copy
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" }
200 response
{
  "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 decisionPASS 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.

GET /verdict/:address$0.01 per call
ParameterDescription
addressThe wallet address to evaluate, or an ENS name. 0x + 40 hex, or e.g. vitalik.eth
VerdictMeaning
BLOCKThe address is sanctioned (OFAC, via the Chainalysis on-chain oracle). A hard legal line — do not transact.
PASSNo 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.

Copy
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
200 response — sanctioned address
{
  "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.

GET /screen-name?name=$0.02 per call
ParameterDescription
namePerson or company name to screen. min 2 characters
thresholdOptional match cutoff, 0.5–1.0. default 0.85
Copy
const r = await od.screenName('Vladimir Putin')
// optional: { threshold: 0.9 }
npx mppx 'https://api.onchaindiligence.com/screen-name?name=Vladimir Putin'
200 response
{
  "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,  }
}
A match is a candidate, not a verdict.Fuzzy name matching surfaces possible hits to investigate using secondary identifiers (date of birth, nationality, ID numbers). It is a screening aid, not a determination, and not a complete compliance program.

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.

GET /company/:companyNumber$0.05 per call
ParameterDescription
companyNumberUK Companies House registration number. e.g. 00000006
Copy
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" }
200 response
{
  "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.

GET /us-company?q=$0.05 per call
ParameterDescription
qTicker, SEC CIK, or company name. e.g. AAPL, 0000320193, Apple Inc
Copy
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" }
200 response
{
  "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.

GET /diligence?wallet=&company=$0.05 per call
QueryDescription
walletWallet address to screen. 0x + 40 hex
companyUK company registration number.
Copy
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" }
200 response · abridged
{
  "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.

POST /anchor$0.01 per call

Anchors the hash of an attestation you hold. Pass the attestation signature in the JSON body. Idempotent — re-anchoring sends no new transaction.

Copy
await od.anchor(r.attestation.signature)
POST /anchor
{ "signature": "wFRfDRSiU5pMOG9y…yqHYfDQ" }
GET /anchored?signature=free

Checks whether an attestation has been anchored on-chain, and when — so anyone can verify a record independently.

200 response
{
  "anchor_hash": "0xadb12c9f…401d",
  "anchored": true,
  "anchored_at": "2026-06-24T14:44:21.000Z",
  "chain": "Tempo",
  "contract": "0x3B4B…99cF"
}
Privacy by design.The chain stores only a hash. You can prove a check occurred at a point in time without revealing who was screened or what the result was. The anchoring contract is open source.

Discovery & health

These endpoints are free and require no payment — they describe the service and report its status for humans and machines.

GET /healthfree

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.

200 responseCopy
{
  "status": "ok",
  "upstreams": {
    "sanctions_oracle": "reachable",
    "companies_house": "reachable"
  },
  "attestation": "configured"
}
GET /free

Service information: the routes, prices, and attestation key URL.

GET /openapi.jsonfree

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.

FieldMeaning
signedWhether the response is cryptographically signed.
key_idIdentifier of the signing key. e.g. ed25519-D8wfc7civVNG05Ds
algorithmAlways ed25519.
signaturebase64url signature over the signing input.
issued_atISO-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:

public keyCopy
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:

verify.mjsCopy
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
Tamper-evidentChange a single field of a stored result and verification fails. The signature only holds for the exact bytes the API returned.

Errors

Errors use standard HTTP status codes. Bad input is rejected before any payment is requested.

402Payment Required — expected on first call. The WWW-Authenticate header carries the payment challenge.
400Bad Request — malformed address or company number. No payment requested.
404Not Found — unknown route, or company number with no record.
429Too Many Requests — rate limit exceeded. See rate limits.
502Bad Gateway — an upstream data source returned an error after payment.
503Service Unavailable — an upstream source is unreachable. Returned before payment, so you are not charged. Retry shortly.

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.

MCP https://mcp.onchaindiligence.com/mcpStreamable HTTP

Connecting the server surfaces three tools, each priced and payment-gated:

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.

FieldValue
Protocolx402 over MCP · payment in tools/call _meta
CurrencyUSDC
NetworkBase mainnet
SettlementOn-chain, per call, non-custodial
Two payment railsOnchainDiligence settles two ways by design: the HTTP API above takes pathUSD on Tempo via the Machine Payments Protocol, while the MCP server takes USDC on Base via x402. Same checks, same signed results — different rails for different agent ecosystems.

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.