API Documentation

Base URL: https://nessgate.com. Every endpoint is a public read — no authentication, no API keys, CORS enabled (Access-Control-Allow-Origin: *). Responses are JSON unless noted. NessGate stores no discovered domain or resource data; every answer is read on demand from the target domain and cached at the edge for up to 10 minutes.

Resolve a domain

GET /discover/{domain}

Give it any domain. NessGate reads whatever that domain publishes across the standard machine-discovery locations and returns one normalized answer, with a sourceUrl on every record pointing back to the document it came from so you can verify against the domain directly.

Reads run through an adapter architecture with four discovery channels: well-known (fixed paths on the domain), link-rel (a <link rel="ard"> in the homepage), robots (an Agentmap: directive in /robots.txt), and dns (a DoH TXT lookup). The adapters cover llms.txt, ARD / ai-catalog across ARD's normative resolution paths (the well-known catalog and the rel="ard" link, both required by ARD v0.91) plus the robots Agentmap: surface, the A2A agent card, RFC 9727 api-catalog, ai-info.json, OpenAPI, Open Resource Discovery, AWP, RFC 6415 host-meta, Agent Network Protocol (ANP), Universal Commerce Protocol (UCP), AID (a TXT record at _agent.<domain>: v=aid1;…), and GB/Z 185.4 agent descriptions. ANP and UCP are emerging; AID and AWP are drafts. (ARD's in-page JSON-LD and DNS-Service-Binding surfaces, and the separate IETF DNS-AID SVCB draft at _agents.<domain>, are not yet read.) See the specification for the full adapter list.

$ curl https://nessgate.com/discover/example.com

200 OK
{
  "domain": "example.com",
  "provenance": "self-published",
  "note": "These locations are published by the domain itself …",
  "discovered": [
    { "type": "llms.txt", "url": "https://example.com/llms.txt" },
    { "type": "ard-catalog", "url": "https://example.com/.well-known/ard.json" }
  ],
  "resources": [
    { "source": "ard-catalog",
      "sourceUrl": "https://example.com/.well-known/ard.json",
      "type": "application/json",
      "name": "Company data",
      "url": "https://example.com/ai-info.json",
      "class": "publisher-declared" }
  ],
  "checked": ["llms.txt","ard-catalog","a2a-agent-card","api-catalog",
              "ai-info.json","openapi","ord","awp","host-meta",
              "anp","ucp","ard-link","ard-agentmap","aid"]
}

400 Bad Request      — input is not a valid domain
429 Too Many Requests — rate limited (120/hour per IP)

discovered is the routing map (which standards the domain publishes, and where). resources is the normalized union of what those documents contain — each record keeps its source standard, the authoritative sourceUrl, an open type label (never a closed set), and, where the source provides them, name/id/raw. checked lists every standard probed, so an empty discovered is still a successful 200 observation. Answers are cached 10 minutes. See the specification for the full contract.

Default discovery is complete and ARD v0.91-conformant — it honours the required <link rel="ard"> HTML link and the robots Agentmap: directive in addition to every well-known path and DNS. For latency-sensitive callers there is a separately labeled fast mode: add ?fast=1 (or resolve(domain, {fast:true})) to skip those two alternate ARD locators and save ~2 requests. Fast mode is not fully ARD-conformant — a catalog advertised only via rel="ard" can be missed — so the response is explicitly flagged "mode": "fast" and checked omits ard-link/ard-agentmap. Never enable it silently.

Every resource also carries a class so your agent knows how far NessGate verified it — with no extra requests:

verified-publisher-locationNessGate fetched and validated this surface on the domain's own registrable domain — the resource is the document that was read.
verified-external-locationLibrary only: the fetched document's final URL (after redirects) sits on a different registrable domain — the queried domain redirected there (e.g. a wholesale rebrand). The hosted resolver never follows cross-domain redirects, so it never emits this.
publisher-declaredDeclared inside a fetched catalog, with a target on the same registrable domain (including subdomains). NessGate did not fetch the target itself.
declared-external-pointerDeclared inside a fetched catalog, but the target is on a different registrable domain. The publisher asserts it; NessGate did not verify it — treat with caution.
unsupportedThe record has no usable target URL to locate the resource.

/discover never fetches declared pointers (that keeps request counts low), so it emits no inaccessible class; a third-party association only appears under /explore's related[]. The embeddable library's resolve() carries the same field, with one deliberate divergence: the library follows redirects — recording the final URL as sourceUrl and labeling any cross-registrable landing verified-external-location — while the hosted resolver stays domain-locked.

Declared capabilities (verbatim, never inferred). Where a source document itself declares operations or skills, the resource carries a capabilities array copied 1:1 from the publisher's own words: OpenAPI operations (method, path, operationId, summary) plus declared security schemes, and A2A / GB‑Z agent-card skills (id, name, description, tags). ARD entries already carry their declared media types and metadata. NessGate never infers, renames, or reclassifies a capability — it surfaces exactly what the domain declares, or nothing (capabilities is simply absent when nothing is declared). Lists are capped at 40 and flagged capabilitiesTruncated when the publisher declares more. For an OpenAPI document larger than the 64 KB detection prefix, NessGate performs one additional bounded read (1 MB cap) to enumerate operations; a spec larger than that stays detected but not enumerated — never partially extracted.

Outcome honesty. Every response carries a single outcome label so a blank result is never ambiguous: found (validated resources exist), none-found (the checks completed — clean 404s and nonexistent hosts count as answers — and no supported declaration exists at the locations checked), or blocked (refusals — auth walls, rate limiting, timeouts, network failures — were at least as common as clean answers: the checks were prevented, so absence is unknown). /explore adds incomplete when its request/byte/time budgets cut the walk short. Whenever any probe was refused, blockedProbes discloses the count even under none-found — uncertainty is never hidden.

Reachability verification (opt-in: ?verify=1). Finding an address does not mean an agent can use it. The verify pass adds a per-record reachability label — ok (the endpoint answered a safe request; never a claim that operations succeed or that you are authorized), auth-required (401/407 or a WWW-Authenticate header — protocol evidence, never assumed), rate-limited (alive; the Retry-After hint is captured as evidence, never acted on), blocked (anti-bot protection reliably identified via a small, conservative signal list — NessGate identifies walls and reports them honestly; it never attempts CAPTCHA bypasses, proxy rotation, or any evasion), not-found (declared, but nothing is there), unreachable, or unknown (a denial whose cause cannot be established — a bare 403 is used by both auth systems and bot-walls, and NessGate never assumes either) — plus checkedAt and an evidence object ({status, signal name, retryAfterSeconds}; response content is never stored). Surfaces NessGate fetched during the resolution are marked ok with no extra request (the fetch is the evidence); MCP records reuse introspection results when present; declared pointers get one safe probe each (HEAD, then a bounded GET when HEAD is unsupported — never a POST, never an execution), capped at 8. Absent reachability means not checked, stated honestly. The embeddable library does the same via resolve(domain, {verify: true}).

MCP introspection (opt-in: ?mcp=1). When a domain declares an MCP endpoint (via AWP, an AID record, a catalog entry — never a guessed path), NessGate can ask that server what it declares, using the protocol's own read-only handshake: initialize (or, for revision-2026-07-28 stateless servers, the revision's required server/discover) and tools/list — nothing else, ever. No tool is executed, no credentials are sent (an auth wall is reported as introspection.status: "auth-required", not retried), POSTs never follow redirects, and only HTTPS endpoints on the queried domain qualify (max 3 per resolution, 256 KB response cap each). The server's declared tools — verbatim name/description/inputSchema — land in the same capabilities envelope, with introspection carrying the negotiated protocol version and serverInfo. The response is labeled top-level "introspected": ["mcp"]; the default response never introspects. The embeddable library does the same via resolve(domain, {mcp: true}).

Explore a domain (evidence-based, v2)

GET /explore/{domain}

Everything /discover returns, plus bounded delegated discovery. It follows the explicit machine-readable pointers a domain declares — the links inside an llms.txt index, ARD and api-catalog entries — following an explicit declaration chain to arbitrary depth (bounded by the request/byte/host/time budgets, with loop detection and per-hop validation, plus a runaway backstop), crossing to another host only where the publisher itself named the target. It also federates the official MCP Registry for the domain's verified reverse-DNS namespace. Every resource gains an evidence class and a provenance chain:

evidencemeaning
publisher-hostedServed directly by the entered domain.
publisher-declaredA pointer a hosted document explicitly names (possibly on another host).
namespace-verifiedAn external registry (the MCP Registry) verified control of the namespace — attributed to the registry, not re-claimed by NessGate.
candidateA caller-suggested URL (opt-in, below) that NessGate fetched and confirmed is a real machine-readable resource. Its relationship to the domain is unverified — NessGate makes no ownership claim.
publisher-redirect-candidateThe apex homepage redirects to a different registrable domain; recorded with the redirect provenance and only when verified resources exist there — never the same authoritative host.
same-domain-hostA verified resource on a host under the same registrable domain (Organization Discovery, opt-in below). Same registrable domain; the organizational relationship is not independently verified.

Organization Discovery (opt-in). Add ?org=1 to look beyond the exact host: many organizations publish their machine-readable resources on a related subdomain (for example developers.openai.com rather than openai.com). Org mode probes a bounded set of plausible same-domain hosts — homepage-linked subdomains with developer/documentation-facing labels first, then a small conventional shortlist (docs, developers, cloud, api, …; at most 6 hosts, 3 paths each) — and reports only resources it actually verified, labelled same-domain-host. Hosts probed are listed in orgChecked; hosts that refused or blackholed every probe (bot protection — absence there is unknown, a clean 404 still counts as answered) are listed in orgBlocked. If the queried domain's own homepage redirects to a different registrable domain (aws.com → aws.amazon.com), that is reported as homepageRedirect — an observation of the publisher's own configuration, never merged into resources. No brute-force scanning, no crawling; a host that serves nothing recognized is simply absent. For a related host's full resource graph, explore that host directly. The embeddable library offers the same capability in-process via resolve(domain, {org: true}), reporting hosts attempted in orgChecked.

Related Discovery (opt-in). Add ?related=1 for a clearly separate cross-domain layer, returned in related[] (never mixed with resources[]). Classes: publisher-declared-related — the queried domain itself serves a purpose-built declaration naming the site (a legacy Related Website Set file or a Digital Asset Links web statement; hyperlinks never qualify), with a mutual flag when the site reciprocates; registry-verified-related — a domain-verifying registry (the MCP Registry) names the specific host, always attributed; infrastructure-correlated-candidate — caller-supplied candidates with a cited technical signal. Technical signals (nameserver containment, …) appear in signals[] as corroboration only — they never mean "official" or "same organization". Bounded (≤5 hosts) and best-effort; full rules in docs/related-discovery-rules.md.

Known limitation of the hosted resolver: some sites' bot protection challenges requests from shared cloud infrastructure, so the hosted endpoints can miss files those sites do publish (they are reported as not found, never guessed). The embeddable library, running from your own environment, does not share this limitation.

Opt-in candidate verification. POST /explore/{domain} with a JSON body {"candidates":["https://…", …]} lets your own AI/search suggest resource URLs; NessGate fetches each (bounded, SSRF-checked), verifies it is genuinely machine-readable, and returns only those, labelled evidence:"candidate" with ["ai-candidate", url] provenance. NessGate runs no AI and stores no discovered domain or resource data — the principle is AI suggests, NessGate verifies; NessGate never decides ownership. Up to 10 candidates per request; these responses are never cached. The plain GET stays fully deterministic.

Response adds federated, per-record evidence/provenance/depth, and stats (counts, requests, hosts, bytes, truncated). It is strictly bounded — ≤8 hosts, ≤24 requests, a global byte cap and deadline (declaration chains are followed to arbitrary depth within those budgets; a depth backstop only guards against runaways), and the same SSRF checks on every cross-host hop — and, like /discover, stores no discovered domain or resource data, crawls nothing, guesses no subdomains, runs no AI, and makes no ownership claim. /discover is unchanged for existing integrations.

Run the resolver yourself

The same normalization is available as a dependency-free library that fetches target domains directly, with no runtime dependency on nessgate.com:

npm i @nessgate/resolver

import { resolve } from "@nessgate/resolver";
const { resources } = await resolve("example.com");

Or import it straight from the edge: import { resolve } from "https://nessgate.com/resolver.mjs". Source and package: GitHub · npm.

Connection readiness & connection plan

GET  /explore/{domain}?readiness=1   — per-endpoint readiness assessment
POST /connect/{domain}               — connection plan for YOUR client

Beyond listing what a domain publishes, NessGate can assess how ready each published endpoint is to connect to, read from the service's own metadata (OpenAPI servers+securitySchemes, A2A agent cards, and the MCP OAuth metadata chain, RFC 9728 → RFC 8414). Outcomes are honest enums, never scores: ready (connect now, no credentials), credentials-required (everything is known — you supply your own secret, which never touches NessGate), incomplete (the protocol-defined metadata is not published; missing[] names each field), or broken (the declared location answers but contradicts the declaration).

POST /connect/{domain} takes your client's capabilities — {"client":{"supports":[{"protocol":"mcp","auth":["oauth2","none"]}]}} — and returns one outcome plus a concrete plan (protocol, endpoint, transport, version, auth metadata) with provenance. Capabilities are input only; nothing is stored. Publishers can self-test with npx @nessgate/resolver yourdomain.com.

MCP server (for AI agents)

POST /mcp          — Model Context Protocol, Streamable HTTP transport

AI agents can call the resolver natively over the Model Context Protocol instead of the REST API. The server is stateless, requires no authentication, and exposes three read-only tools that dispatch to the same handlers (and rate limits) as the REST endpoints:

ToolReturns
discover_domainThe normalized machine-readable resources the domain publishes (same answer as /discover/{domain}), with a sourceUrl on each record.
connect_domainA connection plan for the calling client (same answer as POST /connect/{domain}): one outcome — ready / credentials-required / incomplete / broken / no-compatible-method — plus endpoint, transport, version, and published auth metadata. Client capabilities ride in arguments.client (input only).
check_readinessPer-endpoint readiness for the domain (same answer as /explore/{domain}?readiness=1), client-agnostic.
$ curl -X POST https://nessgate.com/mcp -H "Content-Type: application/json" -d '{
    "jsonrpc": "2.0", "id": 1, "method": "tools/call",
    "params": { "name": "discover_domain",
                "arguments": { "domain": "example.com" } } }'

Connect from an MCP client with transport type streamable-http and URL https://nessgate.com/mcp. Sessions and server-initiated streams are not used, and batching is not supported.

Human-readable pages

GET /{domain}          e.g. https://nessgate.com/example.com

A live HTML view of what a domain publishes — the resolver rendered for humans. Machine discovery: /sitemap.xml and /robots.txt.

NessGate's own declaration

NessGate reads these standards on other domains, so it publishes its own where meaningful: /llms.txt, an RFC 9727 API catalog at /.well-known/api-catalog, /ai-info.json, and an OpenAPI description at /openapi.json. It does not publish an A2A agent card, because NessGate is not an A2A agent — publishing artifacts that describe nothing real is exactly what this project exists to discourage.

Machine-readable API description

An OpenAPI 3.0 description of this API is available at /openapi.json for client generation and tooling, and is linked from the RFC 9727 catalog at /.well-known/api-catalog.