Developer docs

Kertivo is the trust layer for bot-to-bot payments. Grades are earned, never bought.

Every route below is read from /openapi.json. A paid route shows its price per call. Grades are earned, never bought.

Generated from /openapi.json. OpenAPI 1.0.0.

For agents

These files carry the same facts as this page.

This page as JSON: /docs.json.

Check

Did a Base USDC payment settle, and did the recipient and amount match?

POST /check

Paid · $0.01 USDC per call · x402 · eip155:8453

Check a Base USDC payment (paid, $0.01)

GET, same price

GET /check?txHash=&expectedPayTo=&expectedAmount=&selfAddresses=a,b (or ?from=&nonce=&validBefore= for an EIP-3009 authorization) with the x402 payment header: same price, same result as POST. Unpaid GET returns the same 402.

Request

Provide txHash, or authorization (EIP-3009) when you have no tx hash, or both.

{
  "txHash": "0xabababababababababababababababababababababababababababababababab",
  "expectedPayTo": "0xcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd",
  "expectedAmount": "0.01",
  "minAmount": "0.01",
  "expectedPayer": "0xcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd",
  "authorization": {
    "from": "0xcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd",
    "nonce": "0xabababababababababababababababababababababababababababababababab",
    "validBefore": 1893456000,
    "validAfter": 1893456000
  }
}

Responses

StatusWhat comes back
200Check result
400Bad input
402Payment required (x402 PAYMENT-REQUIRED header)
502Upstream RPC failure

POST /check/deep

Paid · $0.05 USDC per call · x402 · eip155:8453

Did this payment settle, and what is the payer's funding source? Deep scan: same as /check but reads the payer's Base USDC funding history from the Blockscout index (latest 500 transfers before the payment, then up to 5 more pages; coverage.historyComplete is true when that covered the full history), plus coverage stats and a recheckable sha256 receipt (paid, $0.05; not charged if the index is down)

GET, same price

GET /check/deep?txHash= with the x402 payment header: same price, same result as POST. Unpaid GET returns the same 402.

Request

Provide txHash (required for deep: it reads the payer's funding history before that payment). Authorization-only bodies get 400 and are not charged; use /check for those.

{
  "txHash": "0xabababababababababababababababababababababababababababababababab",
  "expectedPayTo": "0xcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd",
  "expectedAmount": "0.01",
  "minAmount": "0.01"
}

Responses

StatusWhat comes back
200Deep check result: /check fields + coverage + receipt.recheck{payload,hash}
400Bad input, or an authorization-only body (deep needs txHash; use POST /check for settle-only): not charged
402Payment required
502Upstream RPC failure
503Indexer unavailable: not charged, retry later

POST /demo

Free

Free demo on a fixed fixture tx

Responses

StatusWhat comes back
200Check result
400Same 400 paid /check would return for this body (nothing charged on either route)

Shield

Lookalike check before sending USDC.

POST /address/check

Paid · $0.001 USDC per call · x402 · eip155:8453

Address-poisoning check before sending USDC on Base (paid, $0.001; recent-window / RPC coverage)

GET, same price

GET /address/check?from=&to=&contacts=a,b (or contacts-only ?recipient=&known=a,b, or recipient-only ?address=) with the x402 payment header: same price, same result as POST. Unpaid GET returns the same 402.

Request

Two shapes: {from, to, contacts?} scans the sender's history; contacts-only {to, contacts} without from (aliases recipient/address + known_addresses/known) runs an offline lookalike check against contacts only (no RPC; from null, coverage.source contacts_only). Recipient-only {to} alone (alias address, e.g. Verdix {address, chain}) scans that address's OWN recent Base USDC history for poisoning fingerprints (from null, coverage.mode recipient_only, recipient{fingerprint: paid_after_zero_value|zero_value_only|zero_value_plus_activity|sub_cent_only|no_activity|none, counts}).

{
  "to": "0xcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd",
  "from": "0xcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd",
  "contacts": [
    "string"
  ]
}

Responses

StatusWhat comes back
200{from, to, risk: low|medium|high, seenBefore, lookalikes[{address, inbound, outbound, dustTransfers, realOut, lastBlock, sampleTx, pattern}], reasons[], ignoredFields?/ignoredNote? (request fields Shield did not use, e.g. lookback_blocks/dust_threshold/tier), contacts?{checked, inContacts, lookalikes[{contact, prefixMatch, suffixMatch}]} (only when contacts sent), coverage{windowBlocks, scannedFrom, scannedTo, coveragePct, lowCoverage, complete, transfersSeen, counterparties, rpcCalls}}. coverage.lowCoverage=true when coveragePct < 10 (free RPCs scanned almost nothing): the first reason then says risk low is NOT a safety signal; retry, pass contacts, or use /address/check/full. Risk enum stays low|medium|high.
400Bad input
402Payment required
502Upstream RPC failure

POST /address/check/full

Paid · $0.005 USDC per call · x402 · eip155:8453

Full-history address-poisoning check (paid, $0.005; one Blockscout index call, up to 500 transfers; not charged if index down)

GET, same price

GET /address/check/full?from=&to= with the x402 payment header: same price, same result as POST. Unpaid GET returns the same 402.

Request

{
  "from": "0xcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd",
  "to": "0xcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd",
  "contacts": [
    "string"
  ]
}

Responses

StatusWhat comes back
200Same shape as /address/check with coverage.source=blockscout, coverage.indexerMode (tokentx|v2), and historyComplete when the call reaches history start
400Bad input
402Payment required
502Upstream RPC failure
503Indexer unavailable: not charged, retry later or use /address/check

GET /benchmarks/address-poisoning

Free

Free: dated benchmark of POST /address/check on live Base USDC poisoning attempts (summary, per-pair results with tx links, limitations)

Responses

StatusWhat comes back
200Benchmark JSON

POST /demo/address

Free

Free Shield demo: fixed contacts-only lookalike fixture (body ignored; same shape as paid POST /address/check contacts-only + mode:demo, demo:true, priceCharged:0)

Responses

StatusWhat comes back
200{mode:demo, demo:true, priceCharged:0, from:null, to, risk:high, contacts, coverage.source:contacts_only, ...} same fields as contacts-only /address/check

Index

Grades from dated paid runs. Grades are earned, never bought.

GET /badge

Free

Free: same live SVG grade badge as /badge/{id}, with the endpoint in the url query.

Parameters

NameInRequiredTypeNotes
urlqueryoptionalstringEndpoint URL. Empty url returns the not-graded badge.

GET /badge/{id}

Free

Free: live "Kertivo verified" SVG grade badge from the latest Kertivo Index (earned, never sold). id = URL-encoded endpoint URL, optional .svg suffix (also GET /badge?url=<endpoint>). Ungraded/unknown endpoints get a 200 "not graded" badge. Cached 1h.

Parameters

NameInRequiredTypeNotes
idpathrequiredstringencodeURIComponent(endpoint URL), optionally followed by .svg

Responses

StatusWhat comes back
200SVG badge

GET /badge/{id}/evidence

Free

Free: Index history for the endpoint: every published run it appeared in, newest first (date, grade, price, payment tx, HTTP after pay), and its state (graded | not_in_this_run | ungraded_this_run | never_graded). HTML by default; JSON with ?format=json or Accept: application/json

Parameters

NameInRequiredTypeNotes
idpathrequiredstringencodeURIComponent(endpoint URL)
formatqueryoptionaljson | html

Responses

StatusWhat comes back
200History page or JSON {url, state, gradedAs?, current, lastGraded, history[], runs}. gradedAs appears on our own routes when the run paid the same route on another of our hostnames (same payTo).

GET /trust-index

Free

Free: human-readable Kertivo Index HTML scorecard (grades earned, never bought). JSON twin: GET /trust-index.json

GET /trust-index.json

Free

Free: JSON twin of the Trust Index scorecard. Envelope {url, canonical_html, title, updated, license, data}. data.index is the same object as GET /trust-index/latest.json.

GET /trust-index/latest.json

Free

Free: latest Kertivo Index results

GET /trust-index/raw.jsonl

Free

Free: raw Kertivo Index rows

GET /trust-index/runs

Free

Free: every published Kertivo Index run (append-only, never rewritten), newest first, with sha256 and links to /trust-index/runs/{n}.json and .jsonl

Responses

StatusWhat comes back
200{note, runs[{run,title,runAt,date,rows,sha256,json,jsonl}]}

GET /trust-index/runs/{file}

Free

Free: one frozen run, {n}.json (rows + metadata) or {n}.jsonl (frozen bytes; x-kertivo-sha256 header)

Parameters

NameInRequiredTypeNotes
filepathrequiredstring<run number>.json or <run number>.jsonl

Responses

StatusWhat comes back
200Run rows
404No such run

GET /trust-index/schema

Free

Free: human page for the Kertivo Index data schema. JSON: GET /trust-index/schema.json. Markdown: GET /trust-index/schema.md.

GET /trust-index/schema.json

Free

Free: Kertivo Index data schema as JSON.

GET /trust-index/schema.md

Free

Free: Kertivo Index data schema as Markdown.

POST /trust/lookup

Paid · $0.002 USDC per call · x402 · eip155:8453

Kertivo Index grade + evidence for an x402 endpoint (paid, $0.002)

Request

{
  "url": "https://seller.example/resource"
}

Responses

StatusWhat comes back
200{url, status: indexed|not_indexed, grade, runDate, run, rawData, evidence{unpaidStatus, priceSeenUSDC, payTo, paymentTxHash, settled, onchain, httpStatusAfterPay, latencyMs, schemaCheck, deliveryResult, payToRotates?, payToRotatesNote?, payToRotatesEvidence?, payToChanged?, payToChange?{previous, current, basis}, payToChangedNote?}, payToRotates?, payToRotatesNote?}. payToRotates: true = the seller hands out a fresh payTo deposit address on every 402 (multi-probe evidence), so evidence.payTo is one sample, not a fixed address. payToChanged: true = the seller's receiving address differs from the previous Trust Index run (Index #3+; grade unaffected; confirm with the seller if you pinned the old address)
400Bad url
402Payment required

GET /trust/lookup

Free

Free: grade + run date only (omit url for usage help)

Parameters

NameInRequiredTypeNotes
urlqueryoptionalstring

Responses

StatusWhat comes back
200With ?url=: {url, status, grade, runDate, run, rawData, payToRotates?, payToRotatesNote?, payToChanged?, payToChangedNote?, claims…}. Without url: {usage, paid, docs, examples} so directory probes (e.g. x402 Lens) see the route as up.
400Bad url

Claims

Public evidence when a buyer paid and the endpoint did not deliver.

POST /claim

Free

Free claims desk: file evidence that you paid an x402 endpoint but got no or bad response (evidence only; no custody or arbitration)

Request

{
  "endpoint": "https://seller.example/resource",
  "txHash": "0xabababababababababababababababababababababababababababababababab",
  "authorization": {},
  "from": "0xcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd"
}

Responses

StatusWhat comes back
200{claimId, url, status: payment_proven_delivery_disputed|payment_proven_delivered|payment_to_wrong_address|payment_to_unrecognized_address|payment_not_settled, reasons, evidence{endpoint, payment{txHash, settlement, block, from, to, amount}, buyerReport}, filer{payerVerified, signMessage}, seller{trustIndex (indexed|not_indexed|self_listed), grade, payTo, self?, disclosure?, payToRotates?, recipientMatch? (not_possible_rotating_payTo), indexedPayToSample?, note?}. For a seller that uses per-request deposit addresses (payToRotates) the recipient cannot be matched, so it is never marked payment_to_unrecognized_address and seller.payTo is null (a seller response is verified against the payment recipient), recheck{hash}, nextSteps, limits, stored, firstFiledAt, submissions, inputAliases?}. Only settled payments are stored as public claims: payment_not_settled (e.g. an unused EIP-3009 authorization, so no USDC moved) is returned with stored:false
400Bad input
409claim_locked_payer_verified: this claim was filed payer-verified, and an unsigned resubmission cannot change its buyer report. Body = the stored claim (unchanged) + note + signMessage; resubmit with payerSignature from evidence.payment.from to change it.
502Upstream RPC failure

GET /claim/{id}

Free

Free: a stored public claim (HTML for browsers via Accept text/html or ?format=html; JSON otherwise). The JSON record carries a signed `attestation` (see /.well-known/attestation); the HTML page is unsigned.

Parameters

NameInRequiredTypeNotes
idpathrequiredstring
formatqueryoptionalhtml | json

POST /claim/{id}/response

Free

Free: seller's signed response to a claim. Only the seller can respond: its known payTo (claim.seller.payTo), or the settled payment's recipient if the seller isn't indexed; EOA personal_sign or ERC-1271. Message: "x402 claim response <id>\nrespondedAt: <respondedAt>\n<text>". One latest response kept; we record it, we do not arbitrate.

Parameters

NameInRequiredTypeNotes
idpathrequiredstring

Request

{
  "text": "string",
  "respondedAt": "string",
  "signature": "string"
}

Responses

StatusWhat comes back
200Stored
400Bad input
403Signature does not verify for seller payTo
404Claim not found
409Seller not indexed (no known payTo) or a newer response exists

GET /claims

Free

Free: list claims, optionally ?endpoint=<url> (HTML for browsers via Accept text/html or ?format=html; JSON otherwise)

Parameters

NameInRequiredTypeNotes
endpointqueryoptionalstring
formatqueryoptionalhtml | json

Discovery

Files agents read to find routes, prices, and keys.

GET /.well-known/agent.json

Free

Free: ERC-8004 registration-v1 shaped agent file (also GET /agent.json). Kertivo + response signer; on-chain Identity Registry mint on Base (agentId 149639).

Responses

StatusWhat comes back
200ERC-8004 registration JSON (registrations includes Base agentId 149639)

GET /.well-known/attestation

Free

Free: signed responses. Every 200 JSON from /check, /check/deep, /address/check, /address/check/full, paid POST /trust/lookup, JSON GET /claim/{id} (not the HTML page) and /demo, /demo/address carry `attestation` {v, alg eip191-personal_sign, signer, origin, route, issuedAt, sha256 (of the sorted-keys body minus attestation), statement, message, signature}. This route publishes the signer (not a wallet, holds no funds) and verification steps. Also exposes signers[{address, validFrom, validUntil}] for key rotation.

Responses

StatusWhat comes back
200{enabled, signer, signers[], alg, messagePrefix, signedRoutes, statements, howToVerify, agentRegistration}

GET /.well-known/infoharmoni-square.txt

Free

Free: Infoharmoni Square wallet proof. Proves payTo ownership for Infoharmoni Square once the sig line is present: one line `sig mb:hectichustlebot 0x<EIP-191 signature by payTo>`. 404 `not proven` until then.

Responses

StatusWhat comes back
200sig line
404not proven (no signature published yet)

GET /.well-known/llms.txt

Free

Free: same as /llms.txt

Responses

StatusWhat comes back
200llms.txt guide

GET /.well-known/security.txt

Free

Free: security.txt (RFC 9116). Contact, expires, canonical URL, and a policy link to /llms.txt. Also served at /security.txt.

GET /.well-known/x402

Free

Free: x402 discovery manifest (paid resources, prices, payTo)

Responses

StatusWhat comes back
200x402 manifest

GET /agent.json

Free

Free: same as /.well-known/agent.json

Responses

StatusWhat comes back
200ERC-8004 registration JSON

GET /guard.mjs

Free

Free: Kertivo Guard. A zero-dependency module. Pass it to client.onBeforePaymentCreation so an x402 client checks a seller against the Index before paying.

GET /llms.txt

Free

Free: plain-text guide for agents/LLMs (payment terms, every paid route with price + body, free routes)

Responses

StatusWhat comes back
200llms.txt guide

GET /machine.json

Free

Free: index of every human page's JSON twin. New pages use a sibling .json path. Existing JSON URLs are listed and not duplicated.

GET /openapi.json

Free

This document (free)

GET /robots.txt

Free

Free: crawl policy. User-agent *, Allow /, Sitemap, and content signals search=yes, ai-input=yes, ai-train=yes

Responses

StatusWhat comes back
200robots.txt

GET /security.txt

Free

Free: same as /.well-known/security.txt.

GET /sitemap.xml

Free

Free: sitemap of public pages

Responses

StatusWhat comes back
200Sitemap

Pages

Human pages. Each one links a JSON twin with the same facts.

GET /

Free

Docs + pricing (free)

GET /about

Free

Free: About. This section is not published yet. How grades are made is on /trust-index. HTML for browsers; JSON with ?format=json or Accept: application/json.

GET /directory

Free

Free: Directory. This section is not published yet. Graded endpoints are on /trust-index. HTML for browsers; JSON with ?format=json or Accept: application/json.

GET /docs

Free

Free: developer docs for every route, generated from this document. HTML. JSON twin: GET /docs.json.

GET /docs.json

Free

Free: JSON twin of /docs. Envelope {url, canonical_html, title, updated, license, data}. data.groups lists the same operations as this document.

GET /examples

Free

Free: copy-paste buyer recipes (curl 402 handshake, @x402/fetch client paying /check, verifying a payment to another seller)

Responses

StatusWhat comes back
200Integration guide

GET /guides

Free

Free: guides index (HTML). JSON twin: GET /guides.json.

GET /guides.json

Free

Free: JSON twin of the guides index

GET /guides/{slug}

Free

Free: one published guide (HTML). JSON twin: GET /guides/{slug}.json. Unknown or unpublished slugs are 404.

Parameters

NameInRequiredTypeNotes
slugpathrequiredstring

GET /guides/{slug}.json

Free

Free: JSON twin of one published guide (markdown and FAQ from the same source as the HTML). /guides/about.json is the method page.

Parameters

NameInRequiredTypeNotes
slugpathrequiredstring

GET /guides/about

Free

Free: how these guides are written and checked. JSON twin: GET /guides/about.json.

GET /privacy

Free

Free: privacy page (HTML for browsers; JSON with ?format=json or Accept: application/json). What Kertivo stores, that Base payments are public, and that grades are not sold.

Parameters

NameInRequiredTypeNotes
formatqueryoptionalhtml | json

Responses

StatusWhat comes back
200Privacy page or JSON {name, url, updated, contact, summary, sections}

GET /stats

Free

Free: aggregate counts of paid /check, /check/deep, /address/check, /address/check/full (verdicts/risk by source; byPayerClass outside|internal|unknown excludes hectic-spend/Index; deep + addressFull indexerMode + historyComplete, deep indexerPages buckets; per-route ignoredFieldNames / ignoredFieldNamesOutside count unused request field names only, never values; no addresses/txs; /demo excluded)

Responses

StatusWhat comes back
200{note, allTime{since,updatedAt,byPayerClass,outsideSince,check,deep,addressCheck,addressFull}, today{day,...}}; each route may carry ignoredFieldNames{name:count} and ignoredFieldNamesOutside{name:count}
503stats storage not configured

GET /terms

Free

Free: terms page (HTML for browsers; JSON with ?format=json or Accept: application/json). Grades are information from a dated paid run. No warranty. Not financial advice. Corrections and the right of reply use the public contact.

Parameters

NameInRequiredTypeNotes
formatqueryoptionalhtml | json

Responses

StatusWhat comes back
200Terms page or JSON {name, url, updated, contact, summary, sections}

GET /tools

Free

Free: Tools. This section is not published yet. HTML for browsers; JSON with ?format=json or Accept: application/json.