# The verdict object

> One record per agent per check. `tier` is 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](/docs/concepts#the-three-tiers) |
| `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*.

```json
"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.

```json
"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?

```json
"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`

```json
"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`](/docs/api/reads#get-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](/docs/api/checks#get-api-activity-chainid-tokenid) 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.
