describe.net docs

a2a.md — the agent-to-agent gate

The same answers over other doors: api.md (HTTP + MCP) · auth.md (how you pay) · badge.md (the embeddable badge). This page is only about A2A.

One endpoint, one method, no account, no payment: POST https://api.describe.net/a2a speaks JSON-RPC 2.0 and answers message/send with a wallet's free per-chain ERC-8004 reputation preview. A2A (Agent2Agent) is how one agent talks to another without either of them knowing the other's API. Ours wraps what is already free — it sells nothing and it signs nothing.

This file is the only documentation this gate will ever have, and that is a measured fact, not a figure of speech. Both A2A routes are registered with include_in_schema=False, so they are absent from the OpenAPI description and will never appear in Swagger or ReDoc:

curl -s https://api.describe.net/openapi.json | python -c "import sys,json; p=json.load(sys.stdin)['paths']; print(len(p), '/a2a' in p, '/.well-known/agent-card.json' in p)"
# → 18 False False        (measured 2026-08-30)

Do not send an agent to /docs looking for this endpoint. There is nothing there to find. Everything below was measured with curl against the live API on 2026-08-30 (build a6f49ca).


The two routes

Route Method Price What it is
https://api.describe.net/.well-known/agent-card.json GET free The Agent Card: what this agent is, where it lives, what it can do
https://api.describe.net/a2a POST free The JSON-RPC endpoint

The card — and only the card — is also served from the site, byte-identical, because the site proxies it from the API rather than keeping a copy that could drift:

curl -s https://api.describe.net/.well-known/agent-card.json -o a.json
curl -s https://describe.net/.well-known/agent-card.json     -o b.json
cmp a.json b.json && echo identical     # → identical (2026-08-30)

The endpoint itself is not proxied, and it does not need to be — the card names it with an absolute URL on api.describe.net. Measured, so the mistake is cheap to avoid:

POST https://describe.net/a2a      → 404      ← the site does not serve this
POST https://api.describe.net/a2a  → 200
GET  https://api.describe.net/a2a  → 405 {"detail":"Method Not Allowed"}

The Agent Card

curl -s https://api.describe.net/.well-known/agent-card.json

The full object, with the three long description strings elided for this page (the URL returns them; everything else below is verbatim, 2026-08-30):

{
  "protocolVersion": "0.3.0",
  "name": "describe.net reputation agent",
  "version": "2.0.0",
  "description": "…",
  "url": "https://api.describe.net/a2a",
  "preferredTransport": "JSONRPC",
  "supportedInterfaces": [
    { "url": "https://api.describe.net/a2a", "transport": "JSONRPC", "protocolBinding": "JSONRPC" }
  ],
  "provider": { "organization": "Ultravioleta DAO", "url": "https://ultravioletadao.xyz" },
  "documentationUrl": "https://describe.net/skill.md",
  "capabilities": { "streaming": false, "pushNotifications": false, "stateTransitionHistory": false },
  "defaultInputModes": ["text/plain"],
  "defaultOutputModes": ["text/plain"],
  "skills": [
    { "id": "vet-wallet",   "name": "Vet a wallet",  "description": "…",
      "tags": ["reputation", "erc-8004", "vetting", "x402"],
      "examples": ["0x715dc035ffb97dd7bb4095c6670138ba05bb4e6d",
                   "What is the reputation of 0x715dc035ffb97dd7bb4095c6670138ba05bb4e6d?"] },
    { "id": "index-status", "name": "Index status",  "description": "…",
      "tags": ["reputation", "erc-8004", "status"],
      "examples": ["status", "help"] }
  ]
}

Four things in there are worth reading slowly:

  • protocolVersion is 0.3.0 — the A2A protocol. version is 2.0.0 — this API's version, the same one every HTTP response carries in Api-Version. They move independently.
  • capabilities is three falses. No streaming, no push notifications, no state-transition history. That is not modesty, it is the rule this repository works under: a card that declares less than the server does is prudent; a card that declares more is the lie that discovery exists to prevent. Everything the card claims, the endpoint does.
  • supportedInterfaces sits next to url + preferredTransport on purpose. New validators want the first form; A2A 0.3 clients read the second. Both name the same single endpoint.
  • Two skills, and they are the whole surface: vet-wallet (send a message containing an address) and index-status (send anything else).

The only method: message/send

Everything else — message/stream, tasks/get, tasks/cancel, tasks/pushNotificationConfig/*, agent/authenticatedExtendedCard — answers -32601. There is nothing hidden behind an undocumented verb.

The request

curl -s -X POST https://api.describe.net/a2a \
  -H 'Content-Type: application/json' \
  -d '{"jsonrpc":"2.0","id":"1","method":"message/send","params":{"message":{"role":"user","messageId":"m1","parts":[{"kind":"text","text":"What is the reputation of 0x715dc035ffb97dd7bb4095c6670138ba05bb4e6d?"}]}}}'

No headers beyond Content-Type. No Authorization — there is none to get, and no request here is improved by one (auth.md explains why that is a design decision and not an omission).

Tolerances, all measured:

You send What happens
parts[].kind or parts[].type both accepted
a non-text part ignored without error (defaultInputModes is text/plain)
no params at all falls through to the index summary — verified with {"jsonrpc":"2.0","id":null,"method":"message/send"}
role, messageId not read; harmless
id as string, int or null echoed back exactly as sent

The response

Always a message result — never a Task:

{"jsonrpc":"2.0","id":"1","result":{"kind":"message","role":"agent","messageId":"fbb5db97-ed9c-4fb4-93fd-6fcf0726c04e","parts":[{"kind":"text","text":"ERC-8004 reputation of 0x715dc035ffb97dd7bb4095c6670138ba05bb4e6d (free preview, per chain):\n- base: 3 identities, score 98.270763, 642 ratings from 467 distinct raters\n\nNever decide with the score alone — a score without its raters is a rumor. The paid breakdown (who wrote every rating, when, in which transaction) costs $0.01 via x402: GET https://api.describe.net/reputation/wallet/0x715dc035ffb97dd7bb4095c6670138ba05bb4e6d — how to pay: https://describe.net/auth.md"}]}}

The query takes milliseconds, so returning a Task with intermediate states would be theatre — asynchrony performed rather than needed. messageId is a fresh UUID per answer. Parse the text from result.parts[0].text.

Note that A2A serves the score raw (98.270763). It is the one door that does not apply the canonical two-decimal display format; if you are rendering it for a human, apply the rule from api.md yourself.


What it answers — the three branches

Dispatch is a single pass over your text, in this order.

1. An EVM address anywhere in the sentence

The address is matched inside the text, so "What is the reputation of 0x…?" works as well as the bare address. Multi-chain wallets get one line per chain:

ERC-8004 reputation of 0x000000000000000000000000000000000000dead (free preview, per chain):
- base: 59 identities, score 56.649123, 57 ratings from 8 distinct raters
- celo: 1 identities, score 74.333333, 12 ratings from 2 distinct raters
- bsc: 14 identities, score 100.000000, 1 ratings from 1 distinct raters
- ethereum: 7 identities, score 12.000000, 1 ratings from 1 distinct raters
- polygon: 6 identities, no ratings yet, 0 ratings from 0 distinct raters

no ratings yet is not a zero. No evidence and a bad score are different facts, and this index refuses to collapse them anywhere, including here.

A wallet with no identity on any indexed chain gets its own sentence — because that, too, is a different fact:

0x000000000000000000000000000000000000beef: no ERC-8004 identity found on any
indexed chain. That is an answer, not an error — 'not registered anywhere' and
'registered but unrated' are different facts. Free re-check any time:
GET https://api.describe.net/wallets/{wallet}/chains

2. A base58 string

Resolved against the index before answering, because in Solana a wallet and an agent id are the same syntax — a Metaplex Core asset looks exactly like an account. Sending every base58 string down the wallet path used to answer "no identity found" about agents this very service publishes for free, which is a false negative indistinguishable from a fact. So the string is resolved first:

A7jngtmJJoRa95pkMZi41Uqn2mRESN5etMiv8WRKYpmQ is an AGENT ID (a Metaplex Core asset), not a wallet:
- solana: agent id A7jngtmJJoRa95pkMZi41Uqn2mRESN5etMiv8WRKYpmQ, owned by unknown, score 25.23301

Paid breakdown (every rating with its transaction, $0.02 via x402): GET https://api.describe.net/reputation/agent/solana/A7jngtmJJoRa95pkMZi41Uqn2mRESN5etMiv8WRKYpmQ — how to pay: https://describe.net/auth.md

A base58 string that is a wallet gets the per-chain preview of branch 1.

3. Anything else — the live index status

curl -s -X POST https://api.describe.net/a2a -H 'Content-Type: application/json' \
  -d '{"jsonrpc":"2.0","id":2,"method":"message/send","params":{"message":{"parts":[{"kind":"text","text":"status"}]}}}'
describe.net — the reputation index for humans, AI agents and robots. Live right
now: 470,064 agents and 552,375 ratings across 11 chains.

Send me a wallet address (EVM 0x… or Solana base58) and I answer with its
per-chain ERC-8004 reputation, free. The full breakdown is $0.01 via x402 on the
HTTP API: https://describe.net/api.md

Those figures are the ones this endpoint returned at 18:21 UTC on 2026-08-30. The shape reproduces; the numbers do not, and should not — the indexer runs hourly. GET https://api.describe.net/health is the authority on the size of the index, and nothing on this page restates it.


What is free here, and what is not

The whole gate is free — the card, the endpoint, every branch above. It is free because it serves what is already free by product promise: the per-chain preview (the same answer as GET /wallets/{wallet}/chains) and the live size of the index. One cached run serves everybody.

The paid decomposition is not served over A2A at all. Who wrote each rating, when, in which transaction — that stays on the x402 HTTP routes, and every A2A answer points at them by URL and price. Payment is the authentication, and speaking A2A does not change that. There is no x402 challenge over JSON-RPC here, no 402 in this envelope, and no way to pay through this door. See auth.md.

GET https://api.describe.net/pricing is the authority and carries the reason for each price in the response body.


🔴 Known gap: A2A answers carry no caveats

As of 2026-08-30, the A2A response is the only one of this service's four doors that serves a flagged wallet with no flag. Written here rather than left for somebody to discover, because a reputation index that hides its own defects has no standing to decompose anybody else's.

Measured the same minute, same wallet, four doors:

Door What it says about 0x…dEaD
GET /wallets/0x…dEaD/chains (free) caveats: [{"code": "burn-address", …}]
GET /badge/0x…dEaD.svg (free) aria-label="describe.net: 60.75 · burn address · 10 raters · 2026-08-30", value box in muted grey
MCP describe_check_wallet (free) data.caveats[0].code == "burn-address"
POST /a2a - base: 59 identities, score 56.649123, …no mention of anything

The subject is one of three well-known burn addresses: nobody controls it, anybody can rate it, nobody can answer for its reputation. The ratings are real on-chain facts and are served on purpose — filtering them would be exactly the verdict this index refuses to issue. What is missing here is the distinction, and only in this door.

So: do not treat an A2A answer as a complete one. If you are going to republish it, or decide money on it, fetch the caveats yourself — free, no payment, one call:

curl -s "https://api.describe.net/wallets/0x000000000000000000000000000000000000dEaD/chains"
# → caveats: [{"code":"burn-address", "text":"…"}], global_score: 60.745614   (2026-08-30)

Branch on caveats[].code, which is stable, not on caveats[].text, which is prose (and which ships in Spanish today — measured 2026-08-30). That array is the public subset: what can be computed from public data without touching the paid decomposition. The evidence-quality caveats — concentration, single rater, campaign — read fields only the paid response computes, and shipping half of them would be worse than shipping none: a list that looks complete and is not.

This entry stays until the endpoint carries the subset itself. When it does, the correction gets written next to this paragraph rather than replacing it — whoever remembers the gap deserves to know when it closed.


Errors — all four modes, measured

JSON-RPC answers 200 even when the envelope carries an error; the only non-200 is a body that is not JSON at all.

Case HTTP Body
unknown method 200 {"jsonrpc":"2.0","id":3,"error":{"code":-32601,"message":"Method not found: message/stream. This agent implements message/send only (capabilities.streaming is false — the card does not promise more than the server does)."}}
not a JSON-RPC 2.0 envelope 200 {"jsonrpc":"2.0","id":null,"error":{"code":-32600,"message":"Invalid Request: expected a JSON-RPC 2.0 object"}}
unparseable body 400 {"jsonrpc":"2.0","id":null,"error":{"code":-32700,"message":"Parse error: body is not JSON"}}
GET instead of POST 405 {"detail":"Method Not Allowed"}

If your client raises on HTTP 200 bodies containing error, that is your client's bug and not ours — the transport succeeded, the call did not.


CORS: open, on purpose

curl -s -D - -o /dev/null -X OPTIONS https://api.describe.net/a2a \
  -H 'Origin: https://example.com' \
  -H 'Access-Control-Request-Method: POST' \
  -H 'Access-Control-Request-Headers: content-type'
HTTP/1.1 204 No Content
access-control-allow-origin: *
access-control-allow-methods: POST, GET, OPTIONS
access-control-allow-headers: content-type
access-control-max-age: 600

Wide-open CORS is scoped to the protocol endpoints (/a2a and /mcp) and nothing else, for one reason: MCP and A2A exist so that other agents can talk to us, including agents running in a browser. There is no credential to leak through a permissive origin, because there is no credential.


What this gate does not do

  • No streaming, no tasks, no push. All three capabilities are false and the server matches. message/stream and tasks/get answer -32601.
  • No payment. No 402 travels in this envelope; the paid routes are named in every answer and live on the HTTP API.
  • No writes, ever. This service signs nothing on any chain and holds no keys. A2A changes nothing about that.
  • No memory. There is no session, no conversation state, no contextId to carry. Each call is answered on its own.
  • No verdict. The answer is evidence with a price attached to its decomposition, never a recommendation. Never decide with the score alone is in the response text because it is the whole thesis of this index.

  • https://api.describe.net/.well-known/agent-card.json — the card (also at https://describe.net/.well-known/agent-card.json, byte-identical)
  • https://describe.net/api.md — the HTTP and MCP doors: every route and price
  • https://describe.net/auth.md — how you pay for the decomposition
  • https://describe.net/badge.md — the badge: the other free, embeddable door
  • https://describe.net/skill.md — the card's documentationUrl: the whole service as an installable playbook
  • https://api.describe.net/wallets/{wallet}/chains — the same preview over plain HTTP, plus the caveats[] this gate does not carry
  • https://api.describe.net/health — the authority on the size and freshness of the index

Verbatim source: a2a.md · from site/a2a.md at git:b5eb83903e36 · back to the documentation portal