describe.net docs

auth.md — how to authenticate with describe

What the API serves and what it costs: api.md · api.html · llms.txt. This page is only about how you pay.

There is no account, no registration, no API key, no OAuth, no OIDC, and no token. Payment is the authentication. You pay per request with x402 over USDC, on any of six EVM networks. Nothing needs to happen before your first call. If you can sign an EIP-3009 authorization, you are already a customer.

Everything on this page was verified against the live API on 2026-08-11; the /feed row, on 2026-08-13; the free-route table (every row answers 200) and the 402 challenge (headers, body shape, recipient, base-unit amounts) were re-verified on 2026-08-21. On 2026-08-30 the route tables, the challenge headers and body, and the three /.well-known/ claims were re-measured against production, and what had drifted was corrected in place, with the correction left written — the paragraphs in italics below are those corrections, not history for its own sake. The date is per-claim on purpose — restating one date for the whole page would assert a re-check that did not happen.


Why there is no OAuth discovery document here

An agent that reads /.well-known/openid-configuration, follows it to an authorization endpoint and finds nothing has been sent on a trip that cannot succeed. So we do not publish one. Specifically, describe does not serve:

Not published · HTTP 404 Why
/.well-known/openid-configuration There is no OpenID Provider. No issuer, no authorization_endpoint, no token_endpoint, no jwks_uri exist.
/.well-known/oauth-authorization-server There is no OAuth authorization server, so there is no agent_auth block, no register_uri, and no credential to claim or revoke.

(Both verified 404 on both hosts, 2026-08-30.)

The absence is the accurate signal. Do not look for a bearer token — there is none to get, and no request to this API is ever improved by an Authorization header.

/.well-known/oauth-protected-resource is published — and says so honestly

Correction, 2026-08-30: this page carried that path in the table above, as "not published", with the reasoning "RFC 9728 requires an authorization_servers array; ours would have to be empty or invented". The document has in fact been served since 2026-08-21, it answers 200 on both hosts, and it names this very page as its resource_documentation — so an agent following the pointer was reading a page that denied the pointer existed. The correction is kept written, because the original reasoning was the right question with the wrong answer: an empty array is not an invented one.

curl -s https://api.describe.net/.well-known/oauth-protected-resource
{
  "resource": "https://api.describe.net",
  "resource_name": "describe.net — ERC-8004 reputation API",
  "resource_documentation": "https://describe.net/auth.md",
  "authorization_servers": [],
  "scopes_supported": [],
  "bearer_methods_supported": [],
  "x402": {
    "protocol": "x402+HTTP",
    "pricing": "https://api.describe.net/pricing",
    "documentation": "https://describe.net/auth.md"
  }
}

authorization_servers: [] is the claim, not a gap. RFC 9728 lets a protected resource describe itself; an empty array is the machine-readable way to say this resource is real and there is no token issuer for it — which is exactly true, and strictly better than a 404, because a 404 cannot distinguish "we thought about it and there is none" from "nobody configured this". The empty scopes_supported and bearer_methods_supported say the same thing twice more. The non-standard x402 block is where an agent goes next: it names the pricing route and this document. Same body from https://describe.net/.well-known/oauth-protected-resource.


What is free, and what costs money

Free routes need no payment and no headers at all. Start here — reading the free tier costs nothing and tells you whether the paid tier is worth it.

Free · HTTP 200 What it gives you
GET https://api.describe.net/wallets/{wallet}/chains Per-chain ERC-8004 identities and reputation for one wallet — agent_ids, final_score, total_reviews, distinct_raters, plus caveats[] (a subset — see below). The preview before paying for /reputation/wallet/{wallet}: if nothing is here, there is nothing to buy.
GET https://api.describe.net/search/{query} The one box: an EVM address, a Solana base58 id or an agent id. Answers what it was read as and one row per chain, each with the wallet that owns it there. Exact match only; never in the singular.
GET https://api.describe.net/pricing Every price, the reason for each price, and the current free tier. Machine-readable.
GET https://api.describe.net/health Liveness and how far the index has scanned on each chain.
GET https://api.describe.net/chains Every indexed chain side by side — agents, ratings, distinct raters, stale_hours.
GET https://api.describe.net/chains/{network} One chain of the index — its stats, its recent activity, its top wallets.
GET https://api.describe.net/manifesto The principles as data, each with enforced_by — the file or route that makes it true.
GET https://api.describe.net/leaderboard First page — the shop window. Ranked by evidence, not by raw average. Takes no parameters: a query param answers 422 pointing at the metered GET /leaderboard/page.
GET https://api.describe.net/stats/facets Every rating facet in the index with its volume and mean.
GET https://api.describe.net/stats/types Declared-type coverage across indexed agents.
GET https://api.describe.net/stats/issuers Who wrote the index: ratings grouped by the host of their feedbackURI, with distinct agents and raters per issuer, plus a no_host row. Free because it is the route somebody audits us with.
GET https://api.describe.net/feed The newest ratings across every chain, newest first. A feed of facts — every row already happened in a finalised block.
GET https://api.describe.net/badge/{wallet}.svg The embeddable badge: one SVG with the global score, its distinct raters and the date of the data. Free by the cost rule — one URL serving the same bytes to everybody, cached an hour at the edge. See badge.md.
POST https://api.describe.net/a2a The same free preview over A2A JSON-RPC (message/send). Agent card at /.well-known/agent-card.json. See a2a.md.
POST https://api.describe.net/mcp MCP over Streamable HTTP. Free door: the metered tools relay the 402 of the route behind them and forward the signature you pass in payment.
GET https://api.describe.net/.well-known/oauth-protected-resource RFC 9728 self-description — see the section above. A /.well-known/ route that charged would be a contradiction in terms.
GET https://api.describe.net/openapi.json OpenAPI 3.1 description.
GET https://api.describe.net/docs · /redoc Human documentation.

The /chains, /chains/{network} and /manifesto rows were missing from this table and were added on 2026-08-21, each verified live with an HTTP 200 that day — the per-claim dates above are unchanged.

The /search/{query}, /stats/issuers, /badge/{wallet}.svg, /a2a, /mcp and /.well-known/oauth-protected-resource rows were added on 2026-08-30, each verified live that day. All six had been answering 200 for days before they were named here: a free route missing from the table of free routes reads as a route that does not exist, and an agent that reads this page to decide what it can do without paying was being told less than the truth.

Metered routes answer a question about one named subject. Each returns HTTP 402 until paid.

Metered · HTTP 402 Price (USD)
GET /reputation/wallet/{wallet} 0.01
GET /reputation/wallet/{wallet}?snapshot=true 0.05 — the only route that writes: it records a durable, citable receipt row with inputs_digest + policy_version.
GET /reputation/wallet/{wallet}/history 0.03
GET /reputation/agent/{network}/{agent_id} 0.02
GET /reputation/rater/{wallet} 0.01 — the other side of the grain: this wallet as a rater, not as a subject. Totals emitted, distinct subjects, dispersion, per-chain split, favourite-subject share, roles from tag1. It emits no score: ranking raters would be a new verdict, and this index returns evidence. Added 2026-08-30 to this table; live and metered since 2026-08-29.
GET /leaderboard/page 0.01 — paging past the shop window (offset, type, limit ≤500), every call. Split from /leaderboard on 2026-08-21 so the free page could live in the CDN.

Prices come from the live challenge, never from this table. This table can go stale; the accepts[] array in the 402 cannot.

Every metered 402 also carries a free_preview naming the free route that answers the same question about the same subject — except /reputation/rater/{wallet}, which deliberately has none: the free /wallets/{wallet}/chains previews the wallet as a subject, and previewing a different question would be worse than previewing nothing.


The flow, in five steps

1. Call the route you want. Do not pre-authorize.

Asking without paying is free and is the intended first move — the 402 is the price quote, not an error.

GET /reputation/wallet/0xf9d1…d5f1 HTTP/1.1
Host: api.describe.net

2. Read the challenge

HTTP/1.1 402 Payment Required
Content-Type: application/json
X-Accept-Payment: x402 USDC 2.0
X-Pricing-Version: <the pricing version in force — read it, do not assume it>
Payment-Required: <the JSON body below, base64>
Cache-Control: no-store

The pricing version is not written on this page on purpose. It travels in the X-Pricing-Version header and again in pricing.version of the body, and it bumps whenever a price or the free/paid line moves — which is exactly the event that would make a number copied into this file wrong. It is the same argument this page already makes about prices, applied to the thing that versions them. (Correction, 2026-08-30: until today this example and the JSON below both read cost-tiered@1. Four bumps had shipped without either line moving — the leaderboard split, /search, the badge and rater profile, and /stats/issuers. The fix is not a newer number: it is not printing one.)

The body is the contract:

{
  "x402Version": 2,
  "scheme": "exact",
  "resource": "GET /reputation/wallet/0xf9d1…d5f1",
  "amount": "0.01",
  "price_usd": "0.01",
  "token": "USDC",
  "maxTimeoutSeconds": 120,
  "retryable": true,
  "recipient": "0xe4dc963c56979E0260fc146b87eE24F18220e545",
  "supportedChains": [8453, 43114, 42161, 10, 137, 42220],
  "accepts": [
    {
      "scheme": "exact",
      "network": "eip155:8453",
      "asset": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
      "amount": "10000",
      "payTo": "0xe4dc963c56979E0260fc146b87eE24F18220e545",
      "maxTimeoutSeconds": 120,
      "extra": { "name": "USD Coin", "version": "2" }
    }
  ],
  "pricing": { "version": "<in force>", "tier": "wallet-lookup", "why": "…" },
  "next_action": "…",
  "free_preview": { "endpoint": "GET /wallets/{wallet}/chains", "gives": "…" },
  "see_also": ["GET /search/{query}", "GET /badge/{wallet}.svg", "…"]
}

accepts[] carries one entry per network and is the only thing you should sign against. amount inside accepts[] is in token base units — USDC has 6 decimals, so "10000" is $0.01. The top-level amount is the human-readable dollar figure; do not sign that one. The challenge also carries a facilitator identifier, which you do not need in order to pay.

Two fields are worth reading before you decide to pay at all, and both are in the body above:

  • free_preview — the free route that answers the same question about the same subject. Call it first; if it comes back empty, this charge buys nothing.
  • see_also — the structured list of every free door of the service, in the challenge itself. Measured 2026-08-30 it carried sixteen entries, from GET /search/{query} to POST /a2a and POST /mcp. It is a list because a paragraph of prose is not something an agent can branch on; read it from the challenge rather than from any table, here included.

(The block above names fifteen top-level fields and the prose names a sixteenth, facilitator. The live 402 carried twenty-two on 2026-08-30; the six this page does not walk through are description, error, message, mimeType, recipients and service. Do not take that on faith — asking for a challenge is free: curl https://api.describe.net/reputation/wallet/0x715dc035ffb97dd7bb4095c6670138ba05bb4e6d.)

3. Verify who you are about to pay — do this every time

The only address describe will ever ask you to pay is:

0xe4dc963c56979E0260fc146b87eE24F18220e545

Check payTo against it before signing. This address is also pinned as a constant in our MCP server (PUBLISHED_TREASURY in describenet/mcp_server.py) for exactly this reason: a 402 is a payment instruction that arrived over the network, and the one defense a payer always has is comparing it to a value published out of band. If payTo differs, do not pay. Either the challenge did not come from describe, or our treasury changed and this document is stale. Both are resolved by asking, not by signing.

4. Sign an EIP-3009 authorization

Pick one entry from accepts[]. Sign a TransferWithAuthorization for that amount, to that payTo, for that asset, on that network. Encode the x402 payload as base64.

The six accepted networks, with the USDC contract each challenge names (order updated 2026-08-21; verify against the live 402, which is always the authority — a challenge issued before the update may still list the same six networks in the previous order):

Network Chain ID USDC
Base 8453 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913
Avalanche 43114 0xB97EF9Ef8734C71904D8002F8b6Bc66Dd9c48a6E
Arbitrum 42161 0xaf88d065e77c8cC2239327C5EDb3A432268e5831
Optimism 10 0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85
Polygon 137 0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359
Celo 42220 0xcebA9300f2b948710d2653dD7B07f33A8B32118C

Ethereum L1 is deliberately absent: settlement gas is paid for you, and paying L1 gas to collect one cent would be selling at a loss. There is no Solana path — the challenge advertises only networks where the treasury actually has an address.

You never send a private key anywhere. You sign locally and send the signature. Ways to produce that base64 string:

  • an MCP wallet — ows_sign_eip3009 (Open Wallet Standard) or pay_x402 (PayBox);

  • Python — pip install -U "uvd-x402-sdk[wallet]". Take the latest: measured 2026-08-30, the service itself runs 0.70. (Until today this line pinned a floor of >=0.42.0. It was never false — the floor still admits every current release — but it named a version nothing runs any more, on either side of the wire, and it is the snippet an integrator copies verbatim. A published floor that nobody tests is a suggestion to install something untested.)

    from decimal import Decimal
    from uvd_x402_sdk import X402Client, X402Config
    
    client = X402Client(config=X402Config(recipient_evm=pay_to))
    client.connect_with_signer(...)          # key stays on your side
    header = client.create_authorization(
        pay_to=pay_to, amount_usd=Decimal("0.01"), chain_name="base",
    )
    

5. Replay the exact same request with X-PAYMENT

Same method, same path, same query string — one header more.

GET /reputation/wallet/0xf9d1…d5f1 HTTP/1.1
Host: api.describe.net
X-PAYMENT: <base64 payload>

Payment is verified and settled on-chain before the query runs. On success:

HTTP/1.1 200 OK
X-Payment-Receipt: 0x<settlement transaction hash>
X-Payment-Reused: false

X-Payment-Receipt is a public hash, not a secret. Keep it: it is how you reconcile what you read against what moved on-chain.


Rules that will save you money

One authorization per paid call. The nonce is consumed when the facilitator settles. A credential that already paid will not pay again — signing one per call is the design, not a workaround.

The receipt is reusable, but only for the identical resource. The receipt is keyed by sha256(X-PAYMENT | resource). Re-sending the same header for the same URL reads again for free until it expires (a window between 300 and 1800 seconds), and the response says so with X-Payment-Reused: true. Re-sending it for a different resource will not work — that is a deliberate guard, not a bug. Wallet A's receipt cannot unlock wallet B.

A 503 is not a 402. Do not re-sign on a 503.

{
  "error": "facilitator_unavailable",
  "message": "Your authorization was NOT considered used.",
  "retryable": true,
  "retry_after_seconds": 5
}

This means our settlement path was unreachable, not that you failed to pay. Retry the same request with the same X-PAYMENT header after Retry-After. Signing a fresh authorization here can make you pay twice.

A 4xx after you sent X-PAYMENT is almost always a spent credential or one signed for a different amount or resource. Retrying with the same header fails identically. Ask again without X-PAYMENT, read the fresh challenge, sign against that one.


Identity, quotas, and what we know about you

There is no client identity here — no account, no key, no session — so there is no per-client quota to apply for and no tier to be upgraded into. Price is the throttle: metered routes cost what GET /pricing says they cost, and that is the whole access-control model. You are never identified, never profiled, and never asked to prove who you are — only that you paid.

That is a statement about authentication, not a promise of unlimited throughput: like any service, this one sits behind infrastructure that may shape or reject abusive traffic. If you get a 429, back off and retry; it is not a payment problem and re-signing will not fix it.


Also worth reading

  • https://docs.describe.net — the documentation hub: every guide plus the rendered OpenAPI (Swagger, ReDoc) and a citable snapshot of the schema.
  • https://describe.net/api.md (twin of api.html) — what the API serves, what each route costs, and the decision loop.
  • https://describe.net/badge.md — the free embeddable badge, and what it deliberately cannot do.
  • https://describe.net/a2a.md — the A2A door, for a client that speaks message/send rather than HTTP.
  • https://describe.net/llms.txt — the whole surface, for agents.
  • https://describe.net/.well-known/api-catalog — machine-readable catalog (RFC 9727): OpenAPI description, documentation, pricing, health.
  • https://api.describe.net/pricing — free, authoritative, and explains the reasoning behind every price.
  • https://describe.net/.well-known/mcp/server-card.json — the MCP server, if you would rather call tools than write HTTP.
  • https://describe.net/.well-known/agent-skills/index.json — the skill index; the vet-then-pay loop itself lives at https://describe.net/.well-known/agent-skills/vet-a-counterparty/SKILL.md.

One last thing, because it is the reason this API exists: never make a decision on final_score alone. Every paid response ships distinct_raters, top_client_share, self_rated and caveats[] alongside the number — the last one being the traps those very numbers trigger, already written out, each with a stable code you can branch on (caveats[].code, never caveats[].text; the codes are listed in api.md). None of it moves the score, and none of it fills a null one. A score without its raters is a rumor.

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