Agent payment & evidence infrastructure

Build against proof.

Evaluate a proposed agent payment, observe what actually settled, and get a signed record either side can verify independently — across HTTP, MCP, and a TypeScript SDK. No accounts, no API keys.

Agent EvidenceThis is the first production surface of the larger Agent Evidence architecture. See the v0 specification, public schema catalog, and Python reference package.

1 · Agent payments

Overview

OnchainDiligence is a pay-per-call HTTP API. A client calls an endpoint, receives a 402 Payment Required challenge, pays a few cents of stablecoin, and retries the request with proof of payment attached. The server verifies payment, runs the check against live data or an on-chain observation, and returns a signed result.

Each response carries an Ed25519 signature over the data and the signer’s timestamp assertion. Store the response with independently trusted key records to verify it later without contacting this service. A separately verified anchor is required for an external time bound.

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. --trust/--fetch-keys and offline-by-default verification require @onchaindiligence/cli@0.4.0 or later — check with npx @onchaindiligence/cli --version if a command below reports an unknown flag.

terminal · no key requiredCopy
# check the API + upstream data sources
npx @onchaindiligence/cli health

# genuinely offline against caller-trusted key records
npx @onchaindiligence/cli verify result.json --trust keys.json

# is an attestation anchored on Tempo?
npx @onchaindiligence/cli anchored <signature>

verify exits 0 for VALID, 3 for INVALID, 4 for UNVERIFIABLE, and 2 for usage errors. Online discovery is available only with explicit --fetch-keys.

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

2 · Action evidence

Overview

Beyond a simple payment, an agent's on-chain action can be a token approval, a swap, a cross-chain bridge, or a stake. Each of these has a dedicated preflight route on the MCP server (Base mainnet, x402): it validates the proposed action against your policy and returns a signed PREFLIGHT artifact before anything executes. A matching evidence check independently observes the on-chain settlement afterward and reports whether it matches what was preflighted — the same before/after distinction as payments.

These are MCP-surface tools, not routes on the api.onchaindiligence.com HTTP API documented above. See MCP server for the connection details.

ERC-20 allowance

POST /x402/preflight-allowance$0.01 per call

Validates a proposed ERC20_ALLOWANCE action (network, token, spender, amount) against a caller policy before the agent submits an on-chain approve. Schema: onchaindiligence.erc20-allowance-action.v1.

Swap

POST /x402/preflight-swap$0.01 per call

Validates a proposed swap (input/output asset, amounts, router, recipient) on Base against policy before execution. Schema: onchaindiligence.swap-action.v1.

Bridge (Circle CCTP)

POST /x402/preflight-bridge$0.01 per call

Validates a proposed Circle CCTP V2 bridge from Base to Ethereum mainnet (eip155:8453eip155:1) — source/destination network, asset, and amount bounds. Schema: onchaindiligence.bridge-action.v1.

Lido staking

POST /x402/preflight-staking$0.01 per call

Validates a proposed Lido stETH submit (stake) on Ethereum mainnet against policy before execution. Schema: onchaindiligence.staking-action.v1.

Not yet in Bazaar discoveryAllowance preflight is live and callable but intentionally not yet advertised as a Bazaar resource pending a real agent integration; call it directly against the path above.

3 · Provider evidence

Executors & providers

The @onchaindiligence/sdk/commerce package orchestrates an agent's merchant payment lifecycle across six payment rails. OCD evaluates policy and independently observes settlement; each executor's own authorization stays fully independent from OCD's decision — an OCD ALLOW never overrides a provider's own denial, and a provider approval never implies OCD ALLOW.

ProviderWhat it is
x402The base production executor for a local signer: Base mainnet, USDC, x402 v2 exact settlement.
PayBoxIndependent, non-custodial agent payment vault (paybox.sh). Submits a caller-reported provider claim to OCD after a terminal status — a checked claim, never independent settlement evidence.
TurnkeyTurnkey Server Wallets. Turnkey's own signed transaction:status webhook, verified server-side by OCD, is the primary evidence path.
CrossmintCrossmint Agent Wallets. Crossmint's own signed wallet-transfer webhook, verified server-side, is the primary evidence path.
CDPCoinbase Developer Platform Server Wallet v2 EOA accounts. No wallet webhook exists, so this executor itself submits the caller-reported provider claim, same as PayBox.
CircleCircle Developer-Controlled Wallets. Circle's own signed transactions.outbound webhook, verified server-side, is the primary evidence path.

Provider claim vs. independent settlement. Turnkey, Crossmint, and Circle each deliver a provider-signed webhook that OCD verifies server-side — that is independently-sourced evidence. PayBox and CDP have no such webhook, so their executor instead submits a caller-reported claim to POST /operations/:operationId/provider-evidence — recorded and shown as a checked claim, never elevated to independent settlement evidence. The distinction always travels with the record.

See the SDK coverage summary and the full executor documentation for integration details.

4 · Receipts & verification

Signed receipts

A receipt records what was decided (decision), what executed (execution), and what independently settled on-chain (settlement) — each reported separately, never merged into one summary judgment. Every check on this record resolves to one of three states:

StateMeaning
VALIDThe signature verifies against a known, correctly-scoped key and the content is unmodified.
INVALIDThe signature does not verify, or the content has been tampered with.
UNVERIFIABLEVerification could not be completed — e.g. the key is unknown or the trust registry is unreachable. Never treated as a pass or a failure.

Explore a real signed receipt at /r/OCD-RCP-NB51-QG4S-VCAN-Y57F or in the Explorer.

Attestation

Every paid response includes a versioned attestation object. Version 2 signs RFC 8785 canonical JSON containing the exact data, timestamp, key ID, issuer, purpose, and schema version.

FieldMeaning
signedWhether the response is cryptographically signed.
schema_versiononchaindiligence.attestation.v2.
issuer / purposeDomain separation for the signer and claim type.
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 exact key and its active, retired, revoked, or compromised status are published by key ID:

key registryCopy
curl https://api.onchaindiligence.com/.well-known/attestation-keys

Offline verification

Verification has two modes, and the choice is always explicit — never a silent default.

ModeWhat happens
OnlineOCD retrieves current key material for you, as a convenience — verifyAttestationOnline(envelope) in the SDK, or ocd verify --fetch-keys in the CLI. A network call is made at verification time.
OfflineYou supply key material you already obtained and trust, and no network call is madeverifyAttestationOffline(envelope, trustMaterial) in the SDK, or ocd verify --trust keys.json in the CLI. The result does not depend on OCD's server being reachable, or honest, at the moment you check.

The trust snapshot is the live key registry, unmodified

There is no separate offline key format to learn or convert. The key registry response at /.well-known/attestation-keys is already shaped exactly like the SDK's TrustedAttestationKeySet input ({ keys: [...], issuer, registry_version }) — save that JSON once while you have a connection, and pass the file straight to --trust later with zero transformation.

An explicit trust decisionPassing a key set to --trust/verifyAttestationOffline is a deliberate statement that you trust those exact records. A key embedded inside the artifact you're verifying, or fetched from an arbitrary URL, must never be copied into your trust file automatically — that would make verification trust whatever the artifact claims about itself.

A real copy-paste flow

terminalCopy
# obtain/save trusted public keys while online
curl -s https://api.onchaindiligence.com/.well-known/attestation-keys -o keys.json

# disconnect / disable network here if you want to prove it to yourself

# verify a saved result completely offline
npx @onchaindiligence/cli verify artifact.json --trust keys.json

Run against a free, real, signed fixture (GET /sandbox/attestation-sample — no counterparty screened, no payment) with the real published @onchaindiligence/cli@0.4.0:

output
✓ VALID  Attestation is valid under the supplied trust policy.
  key: ed25519-P2jIwhCn-Af6pTz4  code: valid

verify exits 0 for VALID, 3 for INVALID, 4 for UNVERIFIABLE, and 2 if you omit both --trust and --fetch-keys — there is no silent fallback path.

What VALID, INVALID, and UNVERIFIABLE establish

StateMeaning
VALIDThe exact bytes were signed by a key present in your trust material, with a status and validity window that covers the signing time.
INVALIDThe signature does not match, the content was altered after signing, or the signing key's own lifecycle rules (revoked, compromised, outside its valid window) were violated.
UNVERIFIABLENo basis to decide either way — the key is unknown to your trust material, the online registry was unreachable (--fetch-keys only), or the input is malformed. Never treated as a pass or a failure.
VALID is not a safety claimA VALID result establishes only that the signer produced these exact bytes, nothing more. It does not establish that the underlying action executed, settled, was authorized, was delivered, was safe, or was compliant with any law or policy. Those are separate, independently-reported facts (see Signed receipts above) — never implied by signature validity alone.

Signed evidence bundles

A bundle is a portable, signed package: a manifest, a set of existing evidence artifacts — receipts, attestations, allowance/swap/bridge/staking evidence — and a reconciliation summary, sealed together and verifiable completely offline. A bundle does not replace those artifacts or re-issue them; it packages ones that already exist so they travel as one file instead of several.

Bundle integrity and artifact verification are reported separately

Verifying a bundle answers two different questions, and the CLI and SDK keep them visibly distinct rather than collapsing them into one pass/fail:

ResultWhat it answers
bundle_integrityWas the manifest sealed intact — nothing added, removed, or altered since signing?
artifact_verifications[]Does each individual artifact inside the bundle independently verify, on its own terms?

Both use the same VALID / INVALID / UNVERIFIABLE states defined above. A bundle can be genuinely VALID at the manifest level while one artifact inside it is INVALID or UNVERIFIABLE — that distinction is the point, not an edge case to collapse away. An artifact of a family the verifier doesn't recognize is reported UNVERIFIABLE, never silently accepted as valid.

Reconciliation: agreements, contradictions, insufficient evidence

Where a bundle contains more than one artifact bearing on the same fact, its reconciliation summary separates three outcomes: agreements, contradictions, and insufficient_evidence. Evidence that is simply missing or unresolved is insufficient_evidence, not a contradiction — the two are never conflated.

SDK and CLI

verify-bundle.mjsCopy
import { verifyBundleOffline } from '@onchaindiligence/sdk'

const result = await verifyBundleOffline(bundle, trustMaterial)
// result.bundle_integrity  -> 'VALID' | 'INVALID' | 'UNVERIFIABLE'
// result.artifact_verifications -> per-artifact results, same tri-state
terminalCopy
ocd verify bundle.json --trust keys.json

Bundle verification is offline-only by design — there is no --fetch-keys mode for a bundle, only --trust. Example output, produced by the published @onchaindiligence/cli@0.4.0 against a locally-assembled bundle:

output
✓ VALID  bundle integrity: VALID
  artifact sha256:PE__9LR3…: VALID
  artifact sha256:TUYpIKRQ…: VALID
  artifact sha256:cq76zr7N…: VALID
  artifact sha256:qWvhdY8M…: VALID
  reconciliation: none
  limitation: Bundle validity proves cryptographic integrity under the verifier contract only.

verify uses the same exit codes for a bundle as for a single attestation: 0 for VALID, 3 for INVALID, 4 for UNVERIFIABLE, 2 for a usage error.

VALID means cryptographic integrity, not a safety verdictA VALID bundle proves the manifest and its artifacts were sealed exactly as verified, under the verifier contract. It does not prove execution success, settlement, delivery, regulatory compliance, safety, proper authorization, or objective truth — those remain separate, independently-reported facts, exactly as for a single attestation above. A provider-authenticated artifact inside a bundle proves a provider claim, the same checked claim described under Executors & providers, never independent settlement evidence.
The assembler's signature is not OCD's endorsementWhoever seals a bundle signs the manifest binding those exact artifacts together. That signature does not mean OnChainDiligence endorses, re-verifies, or vouches for the contents — each embedded artifact still stands or falls on its own signature and its own artifact_verifications result.

Legacy key limitation

The key registry publishes an honest, machine-readable trust_warnings list and a strict_offline_verification_ready flag rather than asserting stronger guarantees than the historical record supports. As of this writing, one retired key has no recorded valid_from activation boundary — it was active before this registry began tracking exact activation times, and that boundary has not been invented to make it look known:

excerpt of a live registry response
{
  "strict_offline_verification_ready": false,
  "trust_warnings": [
    "attestation key ed25519-D8wfc7civVNG05Ds has no valid_from activation boundary"
  ]
}

Anything signed with that key still verifies correctly — the signature, content, and its known valid_until upper bound all still apply. What's missing is a hard lower bound on when it started being trustworthy, so strict time-windowed verification of very old records signed with it cannot be fully bounded. Every key issued since — including the current active key — records a real, exact valid_from, so this specific gap does not recur going forward. Check trust_warnings on your own saved snapshot rather than assuming it is empty.

Legacy version 1 records

The manual snippet below is retained specifically for legacy version 1 attestations, which predate the domain-separated v2 signing input:

legacy-v1-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  ->  signature matches; issued_at is the signer's assertion
Tamper-evidentChange a single field of a stored result and verification fails. The signature only holds for the exact bytes the API returned.

5 · MCP

MCP server

The checks and evidence types on this page 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 the checks and action-evidence preflights documented above as tools, each priced and payment-gated — the live server is the source of truth for the exact current tool list, since it changes as new evidence types ship.

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 underlying evidence, 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.

6 · SDK

TypeScript SDK

@onchaindiligence/sdk wraps the HTTP API for typed calls, and its separate /commerce export is the client-side orchestration layer for the provider executors documented above: createCommerceClient opens an operation, preflight() evaluates policy, your executor's execute() authorizes and submits, and observeAndFinalize() reports what independently settled.

quickstart.ts
import { createCommerceClient, MockCommerceExecutor, apiPurchasePolicy, BASE_USDC } from '@onchaindiligence/sdk/commerce'

const ocd = createCommerceClient({ recovery: new NodeFileRecoveryStore('./ocd-recovery') })
const { policy } = apiPurchasePolicy({ maxAmount: '1.00', allowedNetwork: 'eip155:8453', allowedAsset: BASE_USDC })
const op = await ocd.open({ action: proposedPayment, policy })
const evaluation = await op.preflight()
const execution = await op.execute({ executor: myExecutor })
const result = await op.observeAndFinalize()

npm install @onchaindiligence/sdk · Package on npm → · full quickstart and API-only usage on Developers.

7 · Evidence Providers

The checks below are OnchainDiligence's Evidence Providers — sources of external, verifiable fact that the infrastructure signs and delivers. They are inputs to the product, documented here for completeness; they are not the product's identity.

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,  }
}

8 · Anchoring

On-chain anchoring

Any signed attestation can be anchored on Tempo mainnet 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.02 per call

Cryptographically verifies the complete v2 attestation envelope before anchoring its signature hash. Pass the original { data, attestation } response. Idempotent — re-anchoring sends no new transaction.

Copy
await od.anchor(r)
POST /anchor
{ "data": { … }, "attestation": { "signed": true, "schema_version": "onchaindiligence.attestation.v2", … } }
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",
  "network": "mainnet",
  "contract": "0xDe47…ea14"
}
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. Anchoring requires ANCHOR_RPC_URL and ANCHOR_CHAIN_ID to be explicitly configured on the deployment; network and contract above always reflect the deployment's real, currently-configured values rather than an assumed default.

9 · Reference infrastructure

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",
    "sec_edgar": "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.

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, or signing/anchoring is not configured. 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.

The full source for the HTTP API, MCP server, SDK, and anchoring contract lives on GitHub.