# lookup.disclose.io — full guide for AI agents & developers > Resolve any internet asset to the right security / vulnerability-disclosure > contact. POST an asset, get back the owner and the channel to report a > vulnerability to (security.txt, bug bounty program, VDP, PSIRT, or national > CERT). Free, CORS-open, unauthenticated. A disclose.io project. This document is a single, self-contained reference an AI coding assistant can ingest in one fetch to integrate the API or the MCP server correctly. The machine-readable contract is the OpenAPI 3.1 spec at https://lookup.disclose.io/openapi.yaml — fetch it for exact schemas. Base URL (production): `https://lookup.disclose.io` --- ## How it works 1. The input is classified into one of 16 asset types (auto-detected, with prefix forms or an explicit `kind` to disambiguate). 2. It is dispatched to a strategy whose steps run in parallel. 3. Steps can emit cross-strategy chain targets (e.g. a package → its repo → the org's domain → its security.txt). Max chain depth is 3, with cycle detection. 4. Results merge and contacts dedupe, then owner/authorized routes rank before operators, related leads, and coordinators; quality and confidence break ties within those route classes. A backward-compatible `status` is assigned. The same engine backs the web UI, the HTTP API, the MCP server, and the official tool integrations — identical routing semantics across every surface. A current RFC 9116 `security.txt` or dnssecuritytxt declaration published by the exact queried domain is authoritative and pinned first. Authority does not transfer through a chain to a host, parent, subsidiary, or unrelated operator. For IP, IPv6, and CIDR inputs, the most-specific bootstrap-RDAP assignment is preferred over broader WHOIS and origin-AS evidence. Assignment holders and network operators remain responsible-operator routes; they do not prove who owns the service at an address and cannot make an IP result `complete` alone. --- ## API: `POST /api/lookup` Request body: | Field | Type | Required | Description | |---------|--------|----------|-------------| | `input` | string | yes | A non-empty asset string. Auto-classified unless `kind` is supplied; the API does not publish an unenforced maximum length. | | `kind` | string | no | Explicit asset type; the REST endpoint runs that strategy instead of auto-detection. Useful for ambiguous bare names. | ```bash curl -s https://lookup.disclose.io/api/lookup \ -H 'Content-Type: application/json' \ -d '{"input":"cloudflare.com"}' ``` JavaScript (fetch): ```js const lookupSession = crypto.randomUUID(); // create once, then persist and reuse const res = await fetch("https://lookup.disclose.io/api/lookup", { method: "POST", headers: { "Content-Type": "application/json", "X-Lookup-Session": lookupSession, }, body: JSON.stringify({ input: "cloudflare.com" }), }); const result = await res.json(); console.log(result.attribution.organization, result.contacts); ``` For browser progress, also supply an `X-Request-Id`, then poll `GET /api/lookup/progress?requestId=` with the same `X-Lookup-Session`. Stages come from work the backend actually started or completed. Progress is short-lived, session-bound, and never contains the lookup input or result. Python (requests): ```python import requests r = requests.post( "https://lookup.disclose.io/api/lookup", json={"input": "cloudflare.com"}, timeout=45, ) result = r.json() print(result["attribution"]["organization"], result["contacts"]) ``` ### Response shape ```jsonc { "input": "cloudflare.com", "assetType": "domain", "status": "complete", // complete | partial | failed "requestId": "req_...", "hasErrors": false, // true if any step crashed mid-resolution "attribution": { "organization": "Cloudflare", "jurisdiction": "US", "confidence": "high" // high | medium | low }, "contacts": [ // owner/authorized routes first; quality/confidence break ties { "type": "bug_bounty", // see contact types below "value": "https://hackerone.com/cloudflare", "label": "Cloudflare on HackerOne", "source": "bounty-platforms", "confidence": "high", "verified": true // false for heuristic convention emails } ], "contactGroups": [ // contacts clustered by the party reached { "entity": "Cloudflare", "routeClass": "first_party", "contacts": [ /* applicable channels */ ] } ], "routeSummary": { "routeClass": "first_party", "headline": "First-party reporting route found" }, "chains": [ /* how strategies connected: input → org → bounty */ ], "dataSources": [ /* every step that ran, with per-step confidence + error */ ] } ``` ### `status` semantics - `complete` — at least one strong reporting channel is tied to an owner-qualified relationship. `publisher`, `build_origin`, `identifier_assignee`, and `disclosure_agent` routes cannot complete a result alone. - `partial` — useful routing or fallback evidence exists but no owner-qualified strong channel surfaced (`cna`, managed-platform, `abuse_contact`, `convention`, or `cert`). - `failed` — no contacts at all, OR the input was a structurally-invalid attack payload (`details.reason: "invalid-input"`). Owner-contact quality (additive): `details.ownerContactFound` is `false` (and `details.kind` is `"coordinator_only"`) when the only channel found is a universal CERT/CC backstop. Treat such a `partial` as "no direct owner contact found", not a meaningful result. For `container` inputs, `details.container` also carries `{ registry, registryName, repository, tag, digest, inferredOrg, officialImage, attributable }`; `attributable: false` marks a private, account-scoped registry (ACR / per-account ECR) with no public owner. For IP, IPv6, and CIDR inputs, a registrant, abuse role, origin ASN, transit provider, or operator policy is useful routing evidence but does not establish the service owner. These results remain `partial` unless separate owner-qualified evidence exists; the resolver never promotes a conflicting origin-AS policy over the most-specific address assignment. All three are HTTP `200` — branch on the body's `status`, not the HTTP code. `400` is only for a missing/non-string `input` at the request boundary; `429` for rate limiting; `503` + `Retry-After` when the bounded lookup queue is busy; `500` for engine errors. Every non-2xx body is a JSON `ErrorEnvelope` with `status`, `errorMessage`, `requestId`, and optional `errorCode`. ### Contact types — primary vs. fallback | Group | `type` values | Meaning | |----------|---------------|---------| | Primary | `bug_bounty`, `security_txt`, `dns_security_txt`, `vdp`, `psirt`, `web_form`, `email` | Direct vendor channels — try first | | Allocation / fallback | `cna`, `abuse_contact`, `convention`, `cert` | CVE-allocation / generic / network-level / jurisdictional — does not establish ownership | `verified: false` marks heuristic guesses (e.g. `security@` convention emails) that a coordinator should confirm before outreach. Ordering is route-first: owner/authorized routes, applicable operators, related parties, inferred leads, then coordinators. A published `security@` mailbox keeps its observed source and is not a convention guess. Only when otherwise-equivalent `type: convention` contacts are compared does `security@` come before `abuse@`; both remain unverified and neither outranks an observed route. ### Input types Start with the exact affected asset. If it does not resolve, try another identifier for the same target; use an organization name as a fallback. Types are auto-detected; prefix forms disambiguate types that cannot be inferred. | Type | Example | |------|---------| | Domain | `cloudflare.com` | | IPv4 / IPv6 | `1.1.1.1`, `2001:4860:4860::8888` | | URL | `https://portal.azure.com/path` | | Email | `security@microsoft.com` | | ASN / CIDR | `AS13335`, `192.168.0.0/24` | | Package | `npm:express`, `pypi:django`, `crates:serde`, `go:github.com/gorilla/mux`, `maven:com.google.guava:guava`, `gem:rails`, `composer:laravel/framework` | | Repository | `gh:facebook/react`, `gl:gitlab-org/gitlab`, `bb:atlassian/repo`, or a repo URL | | Container | `ghcr.io/actions/runner`, `nginx:latest` | | Cloud resource | `acme-backups.s3.amazonaws.com` | | Mobile app | `app:WhatsApp` | | Hardware | `hw:Cisco ASA 5505`, `hw:BCG-E2599A` (FCC ID → grantee code → manufacturer) | | Extension | `ext:uBlock Origin`, `ext:chrome:`, `ext:firefox:`, or a Chrome/Firefox/Edge store URL | | Desktop app | `desktop:Slack` | | Organization | `Microsoft`, `Swiss Post` (fallback for unrecognized input) | ### Caching & rate limits - HTTP and MCP share a process-local ~6h response cache. Identical concurrent misses join one execution. `X-Lookup-Cache` reports `hit`, `miss`, or `coalesced`; weak `ETag` + `If-None-Match` provides `304` when unchanged. - Anonymous lookup/MCP transport: 600 req/60s per identified IP. New work is separately limited to 30/60s per valid `X-Lookup-Session`, or 120/60s per IP when no session is supplied. Unknown-IP traffic remains 30/60s fail-closed. Persist one opaque 20–128 character session ID per browser/client; do not rotate it per request. - API-key compute quotas are 120/60s (`standard`) and 600/60s (`partner`), with lookup transport ceilings of 1,200 and 6,000 respectively. Progress polling is 240/IP, 480/standard key, or 1,200/partner key per 60s. `/api/feedback` is 10/IP, 30/standard key, or 60/partner key per 60s; `/stats` requires a key and is 120/60s. `RateLimit-*` describes the active budget for that response. - A full or expired bounded execution queue returns HTTP 503 with `errorCode: "lookup-overloaded"` and `Retry-After`; retry with jitter. ### Product feedback The public [product feedback form](https://lookup.disclose.io/feedback) collects general product feedback, feature requests, and an email address for a reply. It is a separate, human-facing Tally form and is not part of the lookup API. Do not submit it automatically or place vulnerability details, secrets, lookup inputs, or a person's email address into it without their explicit action. Lookup-result accuracy feedback remains lookup-bound at `POST /api/feedback` and does not accept a reply address. --- ## Experimental API: `POST /api/evidence` The production deployment exposes a separately gated technical-evidence surface for one public domain or HTTP(S) site: ```bash curl -s -X POST https://lookup.disclose.io/api/evidence \ -H 'Content-Type: application/json' \ -d '{"input":"https://example.com"}' ``` The response is an `attribution-evidence.v1` graph with artifacts, observations, typed claims, decisions, module executions, budgets, and errors. It is not a second owner/contact answer. Every successful graph fixes `mode: "shadow"` and `routingImpact: "none"`; it cannot alter `POST /api/lookup`, contact ordering, or disclosure routing. Scores are uncalibrated ranking evidence, not probabilities. If configured, Censys emits only exact `served_by` host relationships and contextual software observations—never ownership or reporting contacts. Production currently allows anonymous CORS calls. Self-hosted deployments can disable the route (404) or require a key (401). Success responses include `Cache-Control: no-store`, `X-Robots-Tag: noindex, nofollow, noarchive`, and `X-Evidence-Cache: hit | miss | coalesced`. Transport budgets are 120/IP, 30/standard key, or 60/partner key per 60s. New collection work is separately limited to 6/60s per valid `X-Lookup-Session`, 30/IP without a session, 5 for unknown-IP traffic, 30/standard key, or 60/partner key; cache hits and identical in-flight joins do not consume compute quota. Fetch the OpenAPI spec for the experimental graph and error schemas. --- ## MCP (Model Context Protocol) Two transports, same tools: - Remote (streamable HTTP): `https://lookup.disclose.io/mcp` — stateless, no install. Send `Accept: application/json, text/event-stream`. - Local (stdio): `bun mcp.ts` from a clone of the repo. Tools: - `lookup_security_contact` — full lookup. Returns `content[0].text` (Markdown summary) plus `structuredContent` (the full `LookupResult`, identical to the HTTP API). Argument: `asset` (string), optional `asset_type`. - `classify_asset` — instant asset-type classification, no network. Argument: `input` (string). Add to a client: ```bash # Claude Code claude mcp add --transport http lookup https://lookup.disclose.io/mcp ``` ```jsonc // Cursor (.cursor/mcp.json) or any URL-based MCP client { "mcpServers": { "lookup": { "url": "https://lookup.disclose.io/mcp" } } } ``` ```jsonc // Local stdio (~/.claude.json, .cursor/mcp.json, claude_desktop_config.json) { "mcpServers": { "lookup-disclose-io": { "command": "bun", "args": ["mcp.ts"], "cwd": "/path/to/lookup.disclose.io" } } } ``` MCP over HTTP, by hand: ```bash # 1) initialize curl -s -X POST https://lookup.disclose.io/mcp \ -H 'Content-Type: application/json' \ -H 'Accept: application/json, text/event-stream' \ -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-03-26","clientInfo":{"name":"my-client","version":"1.0"},"capabilities":{}}}' # 2) list tools curl -s -X POST https://lookup.disclose.io/mcp \ -H 'Content-Type: application/json' \ -H 'Accept: application/json, text/event-stream' \ -d '{"jsonrpc":"2.0","id":2,"method":"tools/list"}' # 3) call lookup_security_contact curl -s -X POST https://lookup.disclose.io/mcp \ -H 'Content-Type: application/json' \ -H 'Accept: application/json, text/event-stream' \ -d '{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"lookup_security_contact","arguments":{"asset":"cloudflare.com"}}}' ``` --- ## Official integrations Every integration below is a thin client over the production API. Resolution, ownership safeguards, route grouping, and ranking remain server-side: - `dio-lookup` Unix CLI and npm package — newline-delimited stdin to JSONL: https://github.com/disclose/dio-lookup - Caido plugin — context-menu and sidebar lookups: https://github.com/disclose/caido-lookup - Burp Suite Montoya extension — right-click a host: https://github.com/disclose/burp-lookup - OWASP ZAP add-on — selected site or message host: https://github.com/disclose/zap-lookup - Chrome extension — disclosure posture for the current tab: https://github.com/disclose/chrome-extension-v2 - Nmap NSE script — enrich hosts and IP addresses from a scan: https://github.com/disclose/nmap-lookup - Nuclei templates — enrich scan targets with disclosure routes: https://github.com/disclose/nuclei-templates Custom clients should send a stable, versioned `X-Lookup-Client` such as `my-tool/1.0.0`, reuse one opaque `X-Lookup-Session`, honor `Retry-After`, and preserve `routeClass` instead of presenting an operator or coordinator as the asset owner. --- ## Paste-into-your-agent prompt > Integrate the lookup.disclose.io security-contact API. The full OpenAPI 3.1 > spec is at https://lookup.disclose.io/openapi.yaml and an LLM index is at > https://lookup.disclose.io/llms.txt. To look up an asset, POST > `{"input":""}` to https://lookup.disclose.io/api/lookup and read > `attribution.organization` and the `contacts` array (each has `type`, > `value`, `confidence`, `verified`). Treat the `status` field > (`complete | partial | failed`) as the result discriminator — it is `200` > in all three cases. Handle the JSON `ErrorEnvelope` on non-2xx. Respect the > returned `RateLimit-*` response headers; transport and new-work budgets are > separate, and cache hits do not consume lookup compute quota. --- ## Reference - OpenAPI 3.1 spec: https://lookup.disclose.io/openapi.yaml - Swagger UI: https://lookup.disclose.io/api-docs - disclose.io: https://disclose.io