API reference
The verdict object
One record per agent per check.
tieris a summary of the fields under it, and the fields are the part worth reading — several of them exist specifically to say what the verdict does not claim.
A verdict comes back whole from GET /api/verify/{chainId}/{tokenId} and in a deliberately partial
form from POST /api/verdicts and the browse endpoints, where the full evidence is one request away.
This page documents the whole thing.
Identity and provenance#
| Field | Type | Notes |
|---|---|---|
agentId |
string | the registry's own id, falling back to the chainId:tokenId key |
name |
string | null | as registered. null when the registry has none |
avatarUrl |
string | null | a poster image the registration itself names, byte-verified as an image at check time. A 3D-only agent yields null here rather than handing a .glb to an <img> tag |
formulaVersion |
string | the rules this verdict was computed under. Compare this before comparing tiers |
computedAt |
ISO 8601 | when this check ran. On stored rows the same instant is called checkedAt |
cached |
boolean | on /api/verify only: whether this came from the cache rather than a fresh check |
fromStore |
true |
on stored rows only: this came from our records, not a fresh probe |
The tier and what carried it#
| Field | Type | Notes |
|---|---|---|
tier |
"verified_live" | "active" | "registered" |
see Core concepts |
endpointProven |
boolean | an A2A card with substance, or an MCP server that completed initialize and returned a non-empty tools/list |
endpointKind |
"mcp" | "a2a" | null |
which protocol actually proved it. null when neither did |
proofs |
array | every signal found, each {signal, detail} |
corroboratedSignals |
string[] | the subset of proofs that corroborate the endpoint |
proofs and corroboratedSignals are not the same list, and the difference is the formula's central
judgement. wallet_activity can appear in proofs and never in corroboratedSignals, because it
describes an address while the top tier is a claim about an endpoint.
"proofs": [
{ "signal": "capability_integrity", "detail": "serves all 12 tools its registry metadata advertises" },
{ "signal": "wallet_activity", "detail": "318 transactions from agent wallet (threshold 8: wallet owns 2 agents, mint cost discounted)" }
],
"corroboratedSignals": ["capability_integrity"]
| Signal | Corroborates the endpoint? |
|---|---|
capability_integrity |
yes, for this formula — two publisher-controlled records agree; not independent validation |
b402_settlement |
yes — payments settled against it |
registry_feedback |
yes — on-chain feedback was submitted about it |
wallet_activity |
no — reported for context only |
On stored rows proofs is flattened to [{ "signal": "capability_integrity" }] without the detail
string. The detail is regenerated by a fresh check.
capability — declared against served#
null when the agent declares no capabilities at all, which is common and is not a failure.
"capability": {
"kind": "mcp",
"noun": "tools",
"declared": 37,
"matched": 6,
"servedCount": 14,
"coverage": 0.162,
"missing": ["executeSwap", "getPortfolio"],
"extra": ["healthCheck"]
}
| Field | Notes |
|---|---|
kind / noun |
mcp/tools or a2a/skills. Both compare listed capability names; neither establishes correct execution |
declared |
how many capabilities its registry metadata advertises |
matched |
the intersection — how many of those the live endpoint actually served |
servedCount |
how many the endpoint offers in total. Context, never the headline |
coverage |
matched / declared |
missing |
declared but not served, first 20. Published rather than softened |
extra |
served but never declared, first 20. Not a fault — it means the registry record understates the agent |
matched counts the intersection; servedCount can also include unrelated names.
The capability_integrity proof requires missing to be empty and matched >= 3. A ratio
threshold would reward small denominators, and a floor of three stops a one-skill declaration from
earning corroboration for keeping a single trivial promise.
subject — is this card about this agent?#
"subject": { "verified": true, "how": "the card was served from a url addressing this agent" }
A hard bar on the top tier rather than an entry in proofs. A shared fleet endpoint serves a valid,
healthy card describing the service, and every identity pointing at it would otherwise inherit that
liveness for free — a failure the capability comparison is structurally blind to, since a fleet card
matches itself perfectly.
how is a sentence, and the failing ones are as informative as the passing ones: "the served card
could not be shown to be this agent's own", "no card was served".
Checked for A2A only. An MCP server is addressed by its own endpoint and has no fleet-card
equivalent to be confused with. null when the question did not arise.
categories#
"categories": [
{ "category": "yield", "basis": "served" },
{ "category": "health", "basis": "declared" }
]
basis always travels with the placement. served means the live endpoint listed a matching
capability when asked; declared means only the registry metadata or the card says so. The judged
categories are rebalancing, grid, yield and health; an agent that publishes no capability is
placed in nothing.
servedNames#
The capability names the live endpoint actually served, capped at 120. This is what
/api/search matches against, which is why a search hit can name
the tool that matched.
Absent means not-yet-rechecked, never "serves nothing." Agents checked before this field existed simply do not participate in search rather than ranking last.
Hiring#
| Field | Type | Notes |
|---|---|---|
hireable |
boolean | null | whether the agent's own identity file publishes an ERC-8183 menu naming a BNB Chain (eip155:56) provider. null when the card could not be read — unknown, not "no" |
minPriceU |
string | null | the cheapest priced offering on that menu, as an integer string in the payment token's smallest unit. null, never 0, when no offering names a usable price |
Prices are authored by the party being assessed, so anything that is not a clean positive integer is
ignored rather than guessed at or coerced — and minPriceU stays a string all the way to the screen
rather than passing through a float.
bap578#
Present when the agent's card claims a BAP-578 token; null otherwise.
| Field | Notes |
|---|---|
collection / tokenId |
the token the card claims |
ownershipVerified |
tri-state. true when the claim was proved on-chain, false when it was disproved, null when it could not be checked |
logicAddress |
the agent's logic contract, when readable |
totalActions / totalTrades |
reported metrics — only when the contract actually reported them, null otherwise |
ownershipVerified travels with every stored row on purpose. Without it, a replayed record is a
token id and a trade count with nothing saying whether they were ever shown to belong to this agent,
and the numbers would read as established the moment they load.
The activity feed is gated on this: no proved wallet, no attributed events.
evidence#
The raw observations the tier was computed from. Whole on a fresh check; on stored rows only
evidence.endpoint.latencyMs survives, because keeping the rest would bloat the replay the store
does on every boot.
evidence.endpoint — the A2A probe#
| Field | Notes |
|---|---|
declared |
whether the agent declared an A2A endpoint at all |
reachable, status, latencyMs |
what the call did |
hasAgentCard |
true only for a card with substance: a protocol version, a callable url, additional interfaces, or real skills. A bare name is not a card |
skillsCount, skillIds |
what the card published |
reason |
no-endpoint, invalid-url, http-error, not-json, timeout, unreachable |
evidence.mcp — the MCP probe#
| Field | Notes |
|---|---|
declared, reachable, status, latencyMs |
as above |
servesTools |
initialize completed and tools/list returned at least one tool |
toolCount, toolIds |
normalised names, used for the capability comparison and for search |
protocolVersion, serverName |
what the server said about itself |
reason |
adds initialize-rejected and tools-list-rejected to the list above |
reason is worth surfacing in a UI. initialize-rejected and not-json are findings about a
registration; timeout is not distinguishable from our own egress and is treated as neutral
everywhere in this system.
evidence.wallet#
{ checked, address, txCount, balanceWei }. checked: false means the node did not answer — the
formula then emits no wallet proof rather than assuming zero.
agentsOwned is asked only when falling back to the registrant's address, since a proved ERC-6551
account belongs to one token by construction. When it is unknown, no wallet proof is emitted;
an unavailable ownership count cannot establish activity beyond minting.
evidence.bazaar#
{ listed, checked, resourceCount, calls30d, uniquePayers } from the B402 Bazaar. A listing exists
only because x402 payments settled, which is why it corroborates. checked: false distinguishes "we
asked and it is not listed" from "we could not ask".
evidence.registry#
What 8004scan holds: feedbackCount, healthScore, isEndpointVerified,
endpointVerificationError, protocols, x402Supported, and observedMcp — that registry's own
last observation of the agent's MCP endpoint, with its own timestamp. Their observation and ours are
kept separate and dated separately, because they are two different measurements and merging them
would erase whose finding is whose.
Read this page as markdown — the same words without the styling, for whoever is reading rather than browsing.