{"openapi":"3.1.0","info":{"title":"fledgify proof","version":"v0","summary":"Prove, from a stranger's vantage, that a stranger can actually pay for an offer.","description":"An outside-in, read only check of a public offer URL: whether the domain resolves and its nameservers serve it, whether an anonymous visitor can load the page, whether a price, a live checkout and policy pages exist, and whether the page agrees with the checkout. It never buys, never enters a card and never submits a form. Every check returns PROVEN, NOT_PROVEN or NOT_CHECKED with a reason; NOT_CHECKED always names what blocked it and is never a pass.","contact":{"email":"jack@dotcomjack.com"}},"servers":[{"url":"https://proof.fledgify.com"}],"paths":{"/v0/proof":{"get":{"operationId":"prove","summary":"Run a proof and return the report as JSON","parameters":[{"name":"url","in":"query","required":true,"description":"The public offer URL to prove, http or https.","schema":{"type":"string","format":"uri"},"example":"https://example.com/offer"}],"responses":{"200":{"description":"The report.","headers":{"x-fledgify-verdict":{"description":"PROVEN, NOT_PROVEN or NOT_CHECKED, so a script can branch without parsing.","schema":{"type":"string","enum":["PROVEN","NOT_PROVEN","NOT_CHECKED"]}},"x-fledgify-record":{"description":"The permanent record URL of this observation.","schema":{"type":"string"}},"x-fledgify-cache":{"description":"HIT when a recent report for the same URL was served, MISS when a proof ran now, RECORD when a stored record was read.","schema":{"type":"string","enum":["HIT","MISS","RECORD"]}},"link":{"description":"RFC 8288 link to the permanent record of this observation as JSON, rel=\"alternate\". Absent when the record could not be stored, and absent whenever the answer being served is itself that JSON.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProofReport"}}}},"400":{"description":"The url parameter is missing, does not parse, is not http or https, carries credentials, names a port other than the default (this prover reaches a site on the default port only), names a private or loopback host, or is not a usable public host name.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Over a limit: 10 proof requests a minute per caller, or 6 proofs a minute per site. At most 3 of a site's 6 go to any one caller. Every request past the door counts as one of the caller's, including one served from cache and one a site limit refuses. Carries retry-after.","headers":{"retry-after":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"The service is at its limit (30 proofs a minute) or its rate limiter cannot be reached; it refuses rather than run unlimited. Carries retry-after.","headers":{"retry-after":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v0/proof.txt":{"get":{"operationId":"proveText","summary":"Run a proof and return the report as plain text","parameters":[{"name":"url","in":"query","required":true,"description":"The public offer URL to prove, http or https.","schema":{"type":"string","format":"uri"},"example":"https://example.com/offer"}],"responses":{"200":{"description":"The report, rendered as text.","headers":{"x-fledgify-verdict":{"description":"PROVEN, NOT_PROVEN or NOT_CHECKED, so a script can branch without parsing.","schema":{"type":"string","enum":["PROVEN","NOT_PROVEN","NOT_CHECKED"]}},"x-fledgify-record":{"description":"The permanent record URL of this observation.","schema":{"type":"string"}},"x-fledgify-cache":{"description":"HIT when a recent report for the same URL was served, MISS when a proof ran now, RECORD when a stored record was read.","schema":{"type":"string","enum":["HIT","MISS","RECORD"]}},"link":{"description":"RFC 8288 link to the permanent record of this observation as JSON, rel=\"alternate\". Absent when the record could not be stored, and absent whenever the answer being served is itself that JSON.","schema":{"type":"string"}}},"content":{"text/plain":{"schema":{"type":"string"}}}},"400":{"description":"The url parameter cannot be proved; the body is the status and the reason.","content":{"text/plain":{"schema":{"type":"string"}}}},"429":{"description":"Over a caller or site limit. Carries retry-after.","headers":{"retry-after":{"schema":{"type":"integer"}}},"content":{"text/plain":{"schema":{"type":"string"}}}},"503":{"description":"The service is at its limit or its rate limiter cannot be reached. Carries retry-after.","headers":{"retry-after":{"schema":{"type":"integer"}}},"content":{"text/plain":{"schema":{"type":"string"}}}}}}},"/p/{id}.json":{"get":{"operationId":"getRecord","summary":"Read a stored proof record, exactly as it was observed","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","pattern":"^pr_[A-Za-z0-9_-]{22}$"}}],"responses":{"200":{"description":"The stored report. Immutable.","headers":{"x-fledgify-verdict":{"description":"PROVEN, NOT_PROVEN or NOT_CHECKED, so a script can branch without parsing.","schema":{"type":"string","enum":["PROVEN","NOT_PROVEN","NOT_CHECKED"]}},"x-fledgify-record":{"description":"The permanent record URL of this observation.","schema":{"type":"string"}},"x-fledgify-cache":{"description":"HIT when a recent report for the same URL was served, MISS when a proof ran now, RECORD when a stored record was read.","schema":{"type":"string","enum":["HIT","MISS","RECORD"]}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProofReport"}}}},"404":{"description":"No record has that id.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"The record is stored, but its bytes are not a report this version can read. The link is real; retrying will not help.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"The record could not be read from here in time, which is not the same as it not existing. Carries retry-after.","headers":{"retry-after":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/p/{id}.txt":{"get":{"operationId":"getRecordText","summary":"Read a stored proof record rendered as text","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","pattern":"^pr_[A-Za-z0-9_-]{22}$"}}],"responses":{"200":{"description":"The stored report as text.","headers":{"x-fledgify-verdict":{"description":"PROVEN, NOT_PROVEN or NOT_CHECKED, so a script can branch without parsing.","schema":{"type":"string","enum":["PROVEN","NOT_PROVEN","NOT_CHECKED"]}},"x-fledgify-record":{"description":"The permanent record URL of this observation.","schema":{"type":"string"}},"x-fledgify-cache":{"description":"HIT when a recent report for the same URL was served, MISS when a proof ran now, RECORD when a stored record was read.","schema":{"type":"string","enum":["HIT","MISS","RECORD"]}},"link":{"description":"RFC 8288 link to the permanent record of this observation as JSON, rel=\"alternate\". Absent when the record could not be stored, and absent whenever the answer being served is itself that JSON.","schema":{"type":"string"}}},"content":{"text/plain":{"schema":{"type":"string"}}}},"404":{"description":"No record has that id.","content":{"text/plain":{"schema":{"type":"string"}}}},"500":{"description":"The record is stored, but its bytes are not a report this version can read. The link is real.","content":{"text/plain":{"schema":{"type":"string"}}}},"503":{"description":"The record could not be read from here in time, which is not the same as it not existing. Carries retry-after.","headers":{"retry-after":{"schema":{"type":"integer"}}},"content":{"text/plain":{"schema":{"type":"string"}}}}}}},"/p/{id}":{"get":{"operationId":"getRecordPage","summary":"A stored proof record: an HTML page for a browser (Accept: text/html), JSON otherwise","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","pattern":"^pr_[A-Za-z0-9_-]{22}$"}}],"responses":{"200":{"description":"The record, negotiated on the Accept header. The link header is on the HTML rendering; the JSON answer is itself the record JSON, so it carries none.","headers":{"x-fledgify-verdict":{"description":"PROVEN, NOT_PROVEN or NOT_CHECKED, so a script can branch without parsing.","schema":{"type":"string","enum":["PROVEN","NOT_PROVEN","NOT_CHECKED"]}},"x-fledgify-record":{"description":"The permanent record URL of this observation.","schema":{"type":"string"}},"x-fledgify-cache":{"description":"HIT when a recent report for the same URL was served, MISS when a proof ran now, RECORD when a stored record was read.","schema":{"type":"string","enum":["HIT","MISS","RECORD"]}},"link":{"description":"RFC 8288 link to the permanent record of this observation as JSON, rel=\"alternate\". Absent when the record could not be stored, and absent whenever the answer being served is itself that JSON.","schema":{"type":"string"}}},"content":{"text/html":{"schema":{"type":"string"}},"application/json":{"schema":{"$ref":"#/components/schemas/ProofReport"}}}},"404":{"description":"No record has that id.","content":{"text/html":{"schema":{"type":"string"}},"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"The record is stored, but its bytes are not a report this version can read. The link is real; retrying will not help.","content":{"text/html":{"schema":{"type":"string"}},"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"The record could not be read from here in time, which is not the same as it not existing. Carries retry-after.","headers":{"retry-after":{"schema":{"type":"integer"}}},"content":{"text/html":{"schema":{"type":"string"}},"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/mcp":{"post":{"operationId":"mcp","summary":"MCP over streamable HTTP, stateless. One tool, proof, returning structured content matching ProofReport.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","description":"One JSON-RPC 2.0 message."}}}},"responses":{"200":{"description":"One JSON-RPC 2.0 response.","content":{"application/json":{"schema":{"type":"object","required":["jsonrpc"],"properties":{"jsonrpc":{"type":"string","const":"2.0"},"id":{"description":"The id of the request this answers, or null on a parse error."},"result":{"type":"object","description":"Present on success."},"error":{"type":"object","required":["code","message"],"properties":{"code":{"type":"integer"},"message":{"type":"string"}},"description":"Present on failure. -32700 parse error, -32600 invalid request, -32602 unknown tool."}}}}}},"202":{"description":"Accepted, no body: a notification, or a response or error sent to the server."},"400":{"description":"Three causes, each with its own JSON-RPC error code: the body is not JSON (-32700, parse error); it is not one JSON-RPC message, batching having been removed in MCP 2025-06-18 (-32600, invalid request); or the mcp-protocol-version header names a version this server does not speak (-32600, invalid request).","content":{"application/json":{"schema":{"type":"object","required":["jsonrpc"],"properties":{"jsonrpc":{"type":"string","const":"2.0"},"id":{"description":"The id of the request this answers, or null on a parse error."},"result":{"type":"object","description":"Present on success."},"error":{"type":"object","required":["code","message"],"properties":{"code":{"type":"integer"},"message":{"type":"string"}},"description":"Present on failure. -32700 parse error, -32600 invalid request, -32602 unknown tool."}}}}}}},"parameters":[{"name":"mcp-protocol-version","in":"header","required":false,"description":"Optional. Any value other than 2025-06-18 or 2025-03-26 or 2024-11-05 or 2024-10-07 refuses the call with 400 and JSON-RPC -32600. Omitting it is allowed by the spec.","schema":{"type":"string","enum":["2025-06-18","2025-03-26","2024-11-05","2024-10-07"]}}]},"get":{"operationId":"mcpStream","summary":"The SSE stream of streamable HTTP, which this stateless server does not offer","responses":{"405":{"description":"Always: this server is stateless and offers no SSE stream. Carries allow: POST.","content":{"text/plain":{"schema":{"type":"string"}}}}}},"delete":{"operationId":"mcpEndSession","summary":"Ending a session, which a stateless server never opens","responses":{"405":{"description":"Always: there is no session to end. Carries allow: POST.","content":{"text/plain":{"schema":{"type":"string"}}}}}}},"/":{"get":{"operationId":"frontDoor","summary":"The front page for a browser (Accept ranks text/html first), a plain text index otherwise","responses":{"200":{"description":"Negotiated on the Accept header by q value.","content":{"text/html":{"schema":{"type":"string"}},"text/plain":{"schema":{"type":"string"}}}}}}},"/prove":{"get":{"operationId":"provePage","summary":"The form a person submits: runs a proof and sends the browser to its permanent record","parameters":[{"name":"url","in":"query","required":true,"description":"The public offer URL to prove, http or https.","schema":{"type":"string","format":"uri"},"example":"https://example.com/offer"}],"responses":{"200":{"description":"The proof ran but could not be stored, so the report is shown here.","content":{"text/html":{"schema":{"type":"string"}}}},"303":{"description":"The proof ran and was stored: location is its record page.","headers":{"x-fledgify-verdict":{"description":"PROVEN, NOT_PROVEN or NOT_CHECKED, so a script can branch without parsing.","schema":{"type":"string","enum":["PROVEN","NOT_PROVEN","NOT_CHECKED"]}},"x-fledgify-record":{"description":"The permanent record URL of this observation.","schema":{"type":"string"}},"x-fledgify-cache":{"description":"HIT when a recent report for the same URL was served, MISS when a proof ran now, RECORD when a stored record was read.","schema":{"type":"string","enum":["HIT","MISS","RECORD"]}},"link":{"description":"RFC 8288 link to the permanent record of this observation as JSON, rel=\"alternate\". Absent when the record could not be stored, and absent whenever the answer being served is itself that JSON.","schema":{"type":"string"}}}},"400":{"description":"The url cannot be proved; the form is shown again with the reason.","content":{"text/html":{"schema":{"type":"string"}}}},"429":{"description":"Over a caller or site limit. Carries retry-after.","content":{"text/html":{"schema":{"type":"string"}}}},"503":{"description":"The service is at its limit or its rate limiter cannot be reached. Carries retry-after.","content":{"text/html":{"schema":{"type":"string"}}}}}}},"/bot":{"get":{"operationId":"aboutTheBot","summary":"What the prover's user agent requests, how often, and how a site opts out","responses":{"200":{"description":"An HTML page.","content":{"text/html":{"schema":{"type":"string"}}}}}}},"/openapi.json":{"get":{"operationId":"openApi","summary":"This document","responses":{"200":{"description":"OpenAPI 3.1.","content":{"application/json":{"schema":{"type":"object"}}}}}}},"/v0/schema.json":{"get":{"operationId":"reportSchema","summary":"The report as JSON Schema 2020-12","responses":{"200":{"description":"The schema every report and record matches.","content":{"application/schema+json":{"schema":{"type":"object"}}}}}}},"/llms.txt":{"get":{"operationId":"llmsText","summary":"How to call this service and read its verdicts, written for a model","responses":{"200":{"description":"Markdown.","content":{"text/plain":{"schema":{"type":"string"}}}}}}},"/robots.txt":{"get":{"operationId":"robots","summary":"Crawler rules: /prove is disallowed, so no crawler ever triggers a proof","responses":{"200":{"description":"robots.txt.","content":{"text/plain":{"schema":{"type":"string"}}}}}}},"/.well-known/security.txt":{"get":{"operationId":"securityText","summary":"Where to report a vulnerability (RFC 9116)","responses":{"200":{"description":"security.txt.","content":{"text/plain":{"schema":{"type":"string"}}}}}}},"/assets/app.css":{"get":{"operationId":"styles","summary":"The pages' stylesheet","responses":{"200":{"description":"CSS.","content":{"text/css":{"schema":{"type":"string"}}}}}}},"/assets/app.js":{"get":{"operationId":"script","summary":"The pages' one script","responses":{"200":{"description":"JavaScript.","content":{"text/javascript":{"schema":{"type":"string"}}}}}}},"/favicon.svg":{"get":{"operationId":"icon","summary":"The icon","responses":{"200":{"description":"SVG.","content":{"image/svg+xml":{"schema":{"type":"string"}}}}}}},"/favicon.ico":{"get":{"operationId":"iconIco","summary":"Where browsers look for an icon","responses":{"301":{"description":"To /favicon.svg."}}}}},"components":{"schemas":{"ProofReport":{"title":"Fledgify proof report","$schema":"https://json-schema.org/draft/2020-12/schema","description":"A fledgify proof: what was observed, at a time, from a vantage. It never attests to the seller's honesty.","type":"object","properties":{"version":{"type":"string"},"url":{"description":"The offer URL as proved, normalized.","type":"string"},"host":{"type":"string"},"registrable":{"description":"eTLD+1 of the host.","type":"string"},"startedAt":{"description":"ISO 8601, when observation began.","type":"string"},"finishedAt":{"description":"ISO 8601, when observation ended.","type":"string"},"checks":{"description":"The 15 checks, in contract order.","type":"array","items":{"type":"object","properties":{"id":{"description":"Frozen check id, e.g. reachable.authoritative-answers.","type":"string"},"group":{"type":"string","enum":["reachable","buyable","honest"]},"title":{"type":"string"},"verdict":{"description":"PROVEN: observed and true. NOT_PROVEN: observed and false. NOT_CHECKED: could not be observed; always names what blocked it and is never a pass.","type":"string","enum":["PROVEN","NOT_PROVEN","NOT_CHECKED"]},"reason":{"description":"One sentence saying what was observed. Present on every verdict.","type":"string"},"artifacts":{"type":"array","items":{"type":"object","properties":{"label":{"type":"string"},"value":{"description":"Short and quotable. Never a secret value: a matched secret is reported by pattern name only.","type":"string"},"source":{"description":"Which instrument produced it, e.g. tcp:ns1.example.net:53 or rdap:rdap.verisign.com.","type":"string"}},"required":["label","value"],"additionalProperties":false}},"notes":{"description":"Observations that are not the verdict.","type":"array","items":{"type":"string"}},"ms":{"type":"number"}},"required":["id","group","title","verdict","reason","artifacts","notes","ms"],"additionalProperties":false}},"counts":{"type":"object","properties":{"proven":{"type":"number"},"notProven":{"type":"number"},"notChecked":{"type":"number"}},"required":["proven","notProven","notChecked"],"additionalProperties":false},"verdict":{"description":"PROVEN only when every check is PROVEN. NOT_PROVEN when any check is NOT_PROVEN. NOT_CHECKED when nothing failed but something could not be observed.","type":"string","enum":["PROVEN","NOT_PROVEN","NOT_CHECKED"]},"summaryLine":{"description":"One line an agent can paste into a commit message or a reply.","type":"string"},"notes":{"type":"array","items":{"type":"string"}},"cost":{"description":"What this proof cost the sites it touched: real requests by kind, and wall time.","type":"object","properties":{"http":{"type":"number"},"doh":{"type":"number"},"rdap":{"type":"number"},"tcp":{"type":"number"},"ms":{"type":"number"}},"required":["http","doh","rdap","tcp","ms"],"additionalProperties":false},"vantage":{"description":"Where it was observed from, e.g. Cloudflare Worker at ORD.","type":"string"},"record":{"description":"The immutable record of this observation, when the surface that ran it keeps one.","type":"object","properties":{"id":{"type":"string"},"url":{"type":"string"}},"required":["id","url"],"additionalProperties":false}},"required":["version","url","host","registrable","startedAt","finishedAt","checks","counts","verdict","summaryLine","notes","cost"],"additionalProperties":false},"Error":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"Why the request was refused, in one sentence."}}}}}}