describe.net

Embed

One line, and their reputation is on your page

Paste one script tag and the wallet's score appears where you pasted it — with the number of distinct raters behind it and the date of the data, because a score without its raters is a rumor. No cookies, no calls to our API from your page, and nothing that tells us which page it is on.

Make yours

Live preview — mounted by the very file you are about to copy:

The four things it can say — and none of them is a zero

Every badge below is real, mounted right now by this same file. They look different on purpose: telling them apart is the whole job.

Rated

The score, how many different counterparties wrote it, and the date the index was last refreshed. Two decimals, trailing zeros trimmed — the same rule every surface of this ecosystem uses.

Rated, with a warning

The ratings are real and still served; what changes is the dress. A burn address nobody controls carries its warning inside the pixel and loses the violet. The badge is the surface with the least context, so the caveat travels with it.

Nobody rated it yet

"sin calificaciones", never a 0. No data is not bad data, and a zero would be a verdict we never measured. This is the state most subjects start in, and it is an invitation, not a shame.

We are down

Different words, different shape: an empty dashed box, never the grey filled one. Turning "describe is down" into "this wallet has no reputation" is exactly the confusion this index exists to prevent — so the failure refuses to wear the empty state's clothes, and it still links to the profile.

The failure is observable from your page, and it never phones home

A silent fail-open is a slow lie. Three channels, all local to your page: the attribute data-describe-state on the element (loading · ready · error · invalid), a bubbling describe:badge event carrying the reason, and one console.warn. Nothing is sent to us. Ever.

What it sends us — measured, not promised

A widget that gets pasted everywhere is exactly how an accidental tracker gets built: every load could tell the API which page it is on. That is the 2010 Facebook Like button, and it ended in an EFF letter and, at the CJEU, in a site that embeds a data-transmitting widget being a joint controller of the transmission. We copied the Like button's distribution and refused its architecture.

Does the widget… Answer How you can tell
…call our JSON API from your page? No There is not one fetch in the file. The only request is the image, and the browser makes it in non-scriptable mode. One URL per wallet, cached an hour at the edge: a page with a million views an hour costs this origin about one request per CDN location per hour. While your page stays open, the badge asks for that same image again at most once an hour, under a URL every viewer shares that hour — so keeping it fresh costs this origin at most one more request per wallet per CDN location per hour, never one per viewer.
…send the page you are on? No The image carries referrerpolicy="no-referrer" and the link rel="noreferrer". Measured 2026-08-30 against a local origin that logs the header: the hand-written <img> snippet delivered the host's origin; the widget delivered nothing at all.
…set cookies or touch storage? No No document.cookie, no localStorage, no beacons. There is nothing to attach a request to anyway: this service has no accounts.
…read anything on your page? No It reads the attributes you wrote and your <html lang>, to pick the language of its own fallback text. Nothing leaves the page.
…know who clicked? No Deliberately: rel="noreferrer" costs us the referral metric and we pay it. Analytics is not worth contradicting the promise on somebody else's page.
…does our CDN log the image request? Standard access logs IP, URL and timestamp, 30 days, then deleted — same as any image on any CDN. Most loads never reach us because the edge answers first. Nothing sold, shared or profiled. The rest is on Privacy.

One honest caveat about the "reads without JavaScript" variant. If the fallback you put inside the element is the badge <img> itself, the browser already requested it before our file existed, and it did so without referrerpolicy — so your origin reaches us anyway. Measured 2026-08-30: one single request, but carrying Referer. If the no-referrer property is what you want, make that inner fallback a text link. The configurator's variant does exactly that.

When you should not use this

For one badge in a README, a Markdown doc or a static page, the plain image is better and this file is a waste. It is one tag, it runs no code, it cannot break the page it is on, and it needs nothing from us but the pixel. Take it from the badge configurator and skip this page. A widget that sells itself as a replacement for something cheaper than itself is lying.

The script earns its place when there are many badges, when your page paints its list with JavaScript and the badges do not exist at parse time, or when you need the reader to see something honest if we are down. Everything else the image already does.

And it changes the authoring, never the transport: what lands on your page is the same <img> pointing at the same free GET /badge/{wallet}.svg the image snippet uses. A widget that fetched JSON per pageview would be three orders of magnitude more expensive for the same pixel, and could not stay free.

As an iframe: the same badge, keeping itself fresh

embed-frame.html is the one page of this site that any other site may put in an <iframe>: it is served with Content-Security-Policy: frame-ancestors *, while every other page of describe.net keeps answering X-Frame-Options: DENY. Inside it is the same free badge the script mounts — the score, how many distinct raters wrote it and the date of the last description, drawn inside the pixel. Nothing from the paid routes: the frame never asks for anything a visitor could not ask for free.

While the page stays open it asks for the badge again, never more often than the route's own max-age — an hour, which is also how often the index is recomputed — and further apart after an error or a 429. If we are down, the dated badge it already has stays where it is, because an old badge that states its date beats "unavailable".

<iframe src="https://describe.net/embed-frame.html?wallet=0xa3279f744438f83bc75ce9f8a8282c448f97cc8a"
        title="reputation on describe.net"
        width="440" height="20" loading="lazy"
        referrerpolicy="no-referrer"
        sandbox="allow-scripts allow-popups allow-popups-to-escape-sandbox"
        style="border:0;overflow:hidden"></iframe>

There is nothing in that frame to act with: no session, no cookies, no form (its own policy says form-action 'none') and no call to our API but the image. The sandbox above, without allow-same-origin, gives it an opaque origin, so it could not read or write anything of describe.net even if it tried; allow-popups is what lets the profile link open in a new tab. And referrerpolicy="no-referrer" keeps the address of your page from reaching us at all.

Three parameters and no more: wallet (required), height in pixels (8 to 200, default 20) and lang (es or en, for the frame's own text). A frame does not shrink to its content, so the width is yours to set: about 22 times the height fits even a badge carrying a warning (427 px at height 20).

It runs on your page and it cannot break it

Your CSS cannot disfigure it Each badge lives in an open shadow root, so no selector of yours reaches inside. Measured 2026-08-30 against a hostile sheet (img{width:100%!important;filter:invert(1)!important;border:4px solid red!important} — three rules that exist in real themes): the hand-written badge came out 908×28, inverted and red-bordered; the widget came out 315×20, untouched. Open, not closed: you have every right to inspect what we put in your page.
It cannot throw at you Every entry point is wrapped. The worst case is a console.warn and a badge that does not appear.
It claims one global window.describeBadge, and if it already exists the second copy reuses the first instead of overwriting it.
It writes no HTML No innerHTML, no document.write, no eval. The DOM is built node by node, and the wallet is validated with the same regular expression this page uses before it ever reaches a URL. That matters when the wallet in the attribute comes from the rated subject rather than from you.
It installs no observers on your document No MutationObserver watching a page we do not own. If your list renders later, call describeBadge.scan() yourself — explicit and cheap.
It does not steal your layout The height is reserved before the image lands, so the badge does not shove your paragraph down. The width is not fixed, because a badge grows with what it has to say (315 px with a score, 427 px when it also carries a warning).

Every attribute it reads

Put them on the element, or on the script tag to set a default for the whole page. A value that does not parse falls back to the default with a warning — never an exception.

Attribute Default What it does
data-wallet—On the <script> tag only: mounts one badge exactly where the tag is.
data-describe-wallet—On any element: mounts the badge inside it, replacing what it paints. This is the one to use for many badges.
data-height20Badge height in pixels. The width follows the content.
data-linktruefalse renders the badge with no anchor, for pages that handle their own clicks.
data-target_blankWhere the profile opens. _self navigates away from your page, which is why it is not the default.
data-timeout30000Milliseconds before a hanging request counts as down. 30 s is the same clock the three live consumers use, and it starts when the badge reaches the screen, not when it mounts.
data-refreshtrueAsks for the badge again while the page stays open — at most once an hour, which is the route's own max-age, and further apart after an error or a 429. false mounts it once and never looks again.
data-apihttps://api.describe.netFor testing against a local backend, or self-hosting.
data-sitehttps://describe.netWhere the profile link points. Same reason.

For pages that paint their lists with JavaScript

The script scans once when the DOM is ready. Badges that appear later are yours to mount — that is deliberate, because watching a whole document we do not own is a cost we cannot measure from here.

describeBadge.scan()                    // mount every [data-describe-wallet] not mounted yet
describeBadge.scan(container)           // same, limited to a subtree
describeBadge.mount(el, { wallet })     // mount one element explicitly
describeBadge.badgeUrl(wallet)          // the SVG URL, or null. Touches no network.
describeBadge.profileUrl(wallet)        // the profile URL, or null
describeBadge.isWallet(s)               // the same validation the badge route uses

el.addEventListener('describe:badge', (e) => {
  // e.detail = { state, wallet, reason, version }
  // state: 'ready' | 'error' | 'invalid'
});

There is no method that returns a bare number, and there never will be: the score always arrives with its rater count and its date, inside the pixel. A score without its raters is a rumor, and a helper that handed you the float alone would erase the only thing this index is for.

Related

  • The badge configurator — the plain image, no JavaScript, the right answer for a README
  • badge.md — what is inside the pixel, the cache headers, and the known limits
  • The profile of a wallet — where the click lands: every rating with its transaction
  • API and MCP — the free route that tells "never registered" from "registered but never rated", which the badge cannot
  • Privacy — the whole policy, of which this page is one corner