knowngood.sh
menu

The Known Good API

One endpoint, no key required to start. It answers with the sites whose agent-facing capabilities we have actually probed, and tells you when we probed them.

Search

GET https://knowngood.sh/api/find?q=buy%20perfume&country=SA&limit=3

Returns JSON. No authentication is needed; a key only raises your rate limit.

Parameters

NameTypeMeaning
qstring, optional with a filterThe query. Natural language works; so do keywords. Truncated at 300 characters. Leave it out and set at least one filter to list every matching site instead — see Filter only below.
countryISO 3166-1 alpha-2Upper-cased for you. GB, SA, DE.
entity_typestringThe kind of site, as graded. See /type/ for the values in use.
transactionaltrue | falseWhether the site exposes something an agent can act on.
has_mcptrueOnly sites where our strict MCP re-grade ran and passed — the server behind the card answered tools/list when we tested it. This is narrower than “has a working MCP server”: sites we have not yet re-graded are excluded even if their server would answer. The response’s filter_completeness field states how many that is.
has_mdtrueOnly sites whose markdown negotiation passed the strict re-grade — the body is markdown and differs from the HTML — or that ship a markdown twin.
has_endpointtrueOnly sites with a working MCP server or an API catalogue.
has_webmcptrueOnly sites whose in-page WebMCP tools were runtime-verified: the page was executed and registered at least one tool. Code that registers nothing does not count.
limit1–25, default 10How many results to return.

Filter only

GET https://knowngood.sh/api/find?has_mcp=true&country=GB

Leave out q and give at least one filter, and the response lists every listed site matching the filters rather than the most relevant ones: mode is filter, total is how many match, and the order is fixed — platform subdomains last (as the flag policy publishes), then readiness score (checks passed, of 5) descending, then most recent probe first, then hostname. The same URL returns the same page.

Response

Each result carries what we observed, separated from what the site claims. The verified block is probe evidence; apparent_actions is read from page content and is explicitly unverified. Cite the report URL and the date.

Two name fields. name is the stored value exactly as crawled — what the site calls itself, which is often a full meta title. display_name is the same value with a trailing tagline removed and a length cap applied; it is what our own pages render. Both appear on /api/find, get_site_report and the MCP tool output.

{
  "query": "buy perfume",
  "count": 2,
  "data_as_of": "2026-08-28",
  "top_similarity": 0.70,
  "results": [
    {
      "host": "example.com",
      "url": "https://example.com/",
      "name": "Example - The best widgets online",
      "display_name": "Example",
      "report": "https://knowngood.sh/site/example.com",
      "verification_tier": 2,
      "tier_label": "probe-verified",
      "transactional": true,
      "apparent_actions": [ { "action": "buy", "evidence": "…" } ],
      "verified": {
        "verified_at": "2026-08-23",
        "markdown_negotiation": true,
        "llms_txt": true,
        "api_catalog": false,
        "mcp_server": false,
        "mcp_tools": []
      }
    }
  ]
}

When nothing genuinely matches, the API returns an empty results array and an empty_note saying so. It does not pad the answer with weak nearest neighbours.

In a search, has_md and has_endpoint are applied to the results rather than inside the search, so they search deeper until the page is full. If it still is not, the response carries page_not_filled with what was asked for, what came back, how many candidates were searched and why it stopped: population_exhausted, depth_limit or could_not_search_deeper. A short page is never presented as complete.

A search without those two filters drops weak matches: a row scoring below floor_fraction of the best match is left out. When that shortens the page, the response carries page_not_filled with reason: below_relative_floor and candidates_cut. The best match is never dropped, so this never turns a result into an empty one.

When a filter leaves nothing, the empty_note says which filter applied and how many listed sites have no known value for it, so an empty answer under a filter is never presented as the whole index having nothing.

Errors

Every error is the same JSON object: a stable code, a class naming the family, human error and hint prose, and see. code and class are the contract and do not change; error and hint are prose and may be reworded, so do not match on them.

classStatusWhat it means
bad_request400You asked something we cannot answer — a missing q, or a filter value outside its vocabulary. valid lists the choices where there is a closed set. Your fix.
rate_limited429You asked too often. Retry-After says when the window resets; a free key raises the limit and nothing else.
unavailable5xxWe could not answer. Nothing whatever is implied about the index: this is never an empty result set, never count: 0, and never an empty_note. A search that could not run is not a search that found nothing, and we will not report it as one.

A no-match is not an error. When nothing matches, the API returns 200 with an empty results array and an empty_note. That is a measurement. An unavailable is the absence of one, and the two are never conflated in either direction.

Filter completeness

A filter is a promise about completeness, and ours cannot always keep it. Any response with a filter applied carries filter_completeness, keyed by filter name, saying what that filter could not speak for. It is absent when nothing was excluded.

“Unknown” is reported separately from “no”, and that distinction is the point. A site excluded because we know it does not match is a measurement. A site excluded because nobody has asked is not — it may well match. An API that sums the two tells you a filter returned few results without telling you whether the index is empty or merely unmeasured.

FieldMeaning
eligibleListed sites whose value is KNOWN and satisfies this filter. These are the rows the filter could return.
excluded_noListed sites whose value is KNOWN and does not satisfy it. A real negative — we asked, and the answer was no.
excluded_unknownListed sites where the value is UNKNOWN, so they cannot match any value of this filter. Not a negative: we did not ask, or could not. These sites may well satisfy it.
of_listedEvery listed site, the denominator for the three above.

The three populations reconcile against the fourth, so you can always check them: eligible + excluded_no + excluded_unknown = of_listed. Each entry also carries a note stating precisely what the filter tests — has_mcp=true, for instance, means our strict re-grade ran and passed, which is narrower than “has a working MCP server”.

Rate limits

Counted per caller per UTC day.

CallerRequests per day
Keyless (by IP)500
With a key10,000

Getting a key

Keys are issued instantly and anonymously to agents — no personal data, no approval step. The flow is documented for machines at /auth.md. In short: POST https://knowngood.sh/agent/auth, then send the credential as Authorization: Bearer <credential>. Registration is capped at 3 per caller per day; reuse the key you were given. A key raises your rate limit and nothing else — it never affects results, ranking or placement.

MCP

The same index is available over the Model Context Protocol at https://knowngood.sh/mcp, exposing a find tool with the same filters. The server card is at /.well-known/mcp/server-card.json.

In your browser

You can add Known Good to your browser's search bar: visit the site once, then pick Known Good in your browser's search-engine settings and query the index straight from the address bar.

Fair use

Please cite the report URL and its date when you use our findings — the date is the point. The terms set out what is and is not allowed; in particular, do not bulk-extract the index or register keys to evade the limits.

Changelog

Every row is a release of this API and its MCP server, labelled with its software version and dated the day it shipped. Changes to the index and the rubric are logged on /benchmark.

v1.9.0 · 2026-10-13The MCP initialize reply carries instructions: what the index is, its three tools, and how to read a date or a found:false. The find_capability and get_site_report replies no longer carry generated_at or generation_id; every verification date stays. get_site_report adds platform_supplied: true, false, or null where no MCP endpoint answered. The REST responses are unchanged.
v1.8.0 · 2026-10-08The MCP initialize reply reports the server version from server.json, the one place it is set; it had reported an older version than the server card. In every search and site report, verified.mcp_server is now the MCP endpoint that answered tools/list, found by any route; verified.mcp_server_card (present, dead or absent) and verified.other_wellknown are added, and the has_endpoint filter’s MCP half is the endpoint that answered.
v1.5 · 2026-09-22The API’s markdown_negotiation field and the has_md filter now use the strict re-grade the report pages have used since 25 August. 98 listed sites that passed on headers alone no longer report it; the figures on this page are unchanged.
v1.5 · 2026-09-01Five internal test records were removed from the index.
v1.5 · 2026-08-3023 listed ecommerce sites now record an MCP endpoint discovered through their /.well-known/ucp profile, which answers tools/list. MCP answering 48,796 → 48,819, the re-graded total 49,590 → 49,609, cards pointing at nothing 794 → 790; Sites that built their own is unchanged at 632, and 23 report pages updated their “N of 5”.
v1.5 · 2026-08-2936 listed sites that switched robots.txt to ai-input=no since the 23 Aug probe were removed the same day the change was read — explicit refusal excludes absolutely, reversibly. Listed count falls accordingly.