{"openapi":"3.1.0","info":{"title":"MiroShark site API","version":"1.0.0","description":"Public endpoints on miroshark.xyz: the NLWeb /ask query endpoint, the health probe, the machine-readable ecosystem registry, the URL reachability check, and the feedback route (report anything broken, wrong, or missing). The paid simulation API is a separate service (see externalDocs).\n\n## Versioning & deprecation\nThe current major is **v1**. Every endpoint is reachable at its path and, identically, under an explicit `/api/v1/…` prefix (see the `servers` list). Every response carries an `API-Version: 1` header. A breaking change ships as a new `/api/v2/…` tree alongside v1; when a version is retired its responses gain a `Deprecation: true` header (RFC 9745) and a `Sunset:` date (RFC 8594) at least 90 days ahead, plus a `Link: rel=\"successor-version\"` to the replacement. Clients can also send an optional `Api-Version` request header to pin the major.\n\n## Rate limits\nAbuse-bearing endpoints return the standard `RateLimit-Limit`, `RateLimit-Remaining`, and `RateLimit-Reset` (delta-seconds) headers, and a `Retry-After` header on a 429, so a client can self-throttle. See `x-rate-limit`.","license":{"name":"AGPL-3.0","url":"https://www.gnu.org/licenses/agpl-3.0.html"}},"externalDocs":{"description":"MiroShark x402 pay-per-run simulation API (OpenAPI 3.1)","url":"https://x402.miroshark.xyz/openapi.json"},"servers":[{"url":"https://www.miroshark.xyz","description":"miroshark.xyz (current major, v1)"},{"url":"https://www.miroshark.xyz/api/v1","description":"Explicit v1 base. /api/v1/health, /api/v1/ecosystem.json and /api/v1/ask mirror the unprefixed paths."}],"x-api-versioning":{"current":"v1","strategy":"url-path + response-header","pathPrefix":"/api/v1","versionHeader":"API-Version","deprecationHeaders":["Deprecation","Sunset","Link (rel=successor-version)"],"policy":"Breaking changes ship under a new /api/vN prefix; retired versions are announced with Deprecation + Sunset at least 90 days before removal."},"x-rate-limit":{"headers":["RateLimit-Limit","RateLimit-Remaining","RateLimit-Reset","Retry-After"],"reset":"delta-seconds until the current window resets","note":"Emitted on every public response so the convention is observable without authentication: /ask (120/min, enforced), /api/health (600/min, advisory), /api/sim/validate-url, /api/swap and /api/feedback (10/hour, enforced). A 429 includes Retry-After (seconds)."},"paths":{"/ask":{"get":{"operationId":"describeAsk","summary":"Describe the /ask endpoint","description":"Returns a self-describing NLWeb descriptor: how to call /ask and what it returns.","parameters":[{"$ref":"#/components/parameters/ApiVersion"}],"responses":{"200":{"description":"NLWeb endpoint descriptor.","headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AskDescriptor"}}}},"429":{"description":"Too many requests - slow down (see Retry-After).","headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"},"Retry-After":{"$ref":"#/components/headers/RetryAfter"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"operationId":"askMiroshark","summary":"Ask a natural-language question about MiroShark","description":"NLWeb query endpoint. Send a natural-language question and get ranked schema.org items from MiroShark's pages. Set streaming (or header 'prefer: streaming=true') for Server-Sent Events.","parameters":[{"$ref":"#/components/parameters/ApiVersion"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AskRequest"},"examples":{"run":{"value":{"query":"how do I run a simulation?"}},"cost":{"value":{"query":"how much does a run cost?"}}}}}},"responses":{"200":{"description":"Ranked results. application/json for a single body, or text/event-stream when streaming is requested.","headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AskResults"}},"text/event-stream":{"schema":{"type":"string","description":"SSE stream of start / result / complete events, each a JSON payload with an NLWeb _meta object."}}}},"400":{"description":"Invalid or missing query.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests - slow down (see Retry-After).","headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"},"Retry-After":{"$ref":"#/components/headers/RetryAfter"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/health":{"get":{"operationId":"getHealth","summary":"Readiness / health probe","description":"Reports whether the process booted and which feature areas are configured, as booleans only (never secret values).","parameters":[{"$ref":"#/components/parameters/ApiVersion"}],"responses":{"200":{"description":"Service healthy.","headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Health"}}}},"503":{"description":"Degraded: up but missing a required configuration.","headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Health"}}}}}}},"/api/ecosystem.json":{"get":{"operationId":"getEcosystem","summary":"Machine-readable ecosystem registry","description":"The projects and partners building with MiroShark, parsed from the canonical ECOSYSTEM.md.","parameters":[{"$ref":"#/components/parameters/ApiVersion"}],"responses":{"200":{"description":"Ecosystem registry.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Ecosystem"}}}}}}},"/api/sim/validate-url":{"get":{"operationId":"validateUrl","summary":"Check that an article URL is reachable","description":"Best-effort, SSRF-guarded reachability pre-check for a seed article URL. Rate-limited per IP: responses carry the RateLimit-* headers and a 429 includes Retry-After.","parameters":[{"$ref":"#/components/parameters/ApiVersion"},{"name":"url","in":"query","required":true,"description":"The absolute http(s) article URL to check.","schema":{"type":"string","format":"uri"}}],"responses":{"200":{"description":"Reachability verdict. ok=true when the URL looks like a fetchable article; ok=false with a reason otherwise (definitive=false means 'might still work').","headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UrlCheck"}}}},"429":{"description":"Too many checks - slow down.","headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"},"Retry-After":{"$ref":"#/components/headers/RetryAfter"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UrlCheck"}}}}}}},"/api/feedback":{"post":{"operationId":"submitFeedback","summary":"Report a bug, error, missing endpoint, or idea","description":"Files a report in MiroShark's private feedback inbox. No key needed. Use it when a route or tool fails, answers wrong data, a doc is wrong, an endpoint you expected is missing, or you have an idea. Set app to 'miroshark' when the report is about the simulation product rather than this site. A report close to an open one counts as a vote (200). Rate-limited to 10 per hour per IP. Do not include secrets, payment signatures, or cancel tokens. A problem with a simulation run goes to https://x402.miroshark.xyz/feedback with its run_id instead.","parameters":[{"$ref":"#/components/parameters/ApiVersion"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/FeedbackRequest"},"examples":{"bug":{"value":{"kind":"bug","title":"/api/ecosystem.json returns an empty list","details":"Expected the projects from ECOSYSTEM.md, got count 0.","context":{"route":"/api/ecosystem.json","status":200},"agent":{"name":"my-agent","model":"claude-opus-5-5"}}}}}}},"responses":{"200":{"description":"A close open report already existed; this one counted as a vote on it.","headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/FeedbackResult"}}}},"201":{"description":"New report filed.","headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/FeedbackResult"}}}},"400":{"description":"Invalid body (bad kind, title length, oversized context/agent).","headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Sent from a browser on another website (an Origin header other than this site). Agents and servers send no Origin and are not affected.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"Body over 16 KB.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"415":{"description":"Content-Type is not application/json.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests - slow down (see Retry-After).","headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"},"Retry-After":{"$ref":"#/components/headers/RetryAfter"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"The feedback inbox is unreachable or erroring; try again later.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"Feedback is not configured on this deploy.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}},"components":{"parameters":{"ApiVersion":{"name":"Api-Version","in":"header","required":false,"description":"Optional request header to pin the API major. The response always echoes the served major in the `API-Version` header.","schema":{"type":"string","enum":["1"],"default":"1"}}},"headers":{"RateLimitLimit":{"description":"Request ceiling for the current window.","schema":{"type":"integer"}},"RateLimitRemaining":{"description":"Requests left in the current window.","schema":{"type":"integer"}},"RateLimitReset":{"description":"Delta-seconds until the window resets.","schema":{"type":"integer"}},"RetryAfter":{"description":"Seconds to wait before retrying (on 429).","schema":{"type":"integer"}}},"schemas":{"AskRequest":{"type":"object","required":["query"],"properties":{"query":{"type":"string","description":"The natural-language question."},"limit":{"type":"integer","minimum":1,"maximum":20,"default":8,"description":"Maximum number of results."},"streaming":{"type":"boolean","default":false,"description":"Return Server-Sent Events instead of a single JSON body."}}},"AskResults":{"type":"object","properties":{"_meta":{"$ref":"#/components/schemas/NlwebMeta"},"query":{"type":"string"},"results":{"type":"array","items":{"$ref":"#/components/schemas/ResultItem"}}}},"AskDescriptor":{"type":"object","properties":{"_meta":{"$ref":"#/components/schemas/NlwebMeta"},"name":{"type":"string"},"description":{"type":"string"},"method":{"type":"string"},"request":{"type":"object"}}},"ResultItem":{"type":"object","properties":{"@type":{"type":"string","description":"schema.org type."},"name":{"type":"string"},"url":{"type":"string","format":"uri"},"description":{"type":"string"}}},"NlwebMeta":{"type":"object","properties":{"response_type":{"type":"string"},"version":{"type":"string"}}},"Health":{"type":"object","properties":{"status":{"type":"string","enum":["ok","degraded"]},"commit":{"type":["string","null"]},"env":{"type":["string","null"]},"missingRequired":{"type":"array","items":{"type":"string"}},"features":{"type":"object","additionalProperties":{"type":"boolean"}}}},"Ecosystem":{"type":"object","properties":{"source":{"type":"string","format":"uri"},"count":{"type":"integer"},"projects":{"type":"array","items":{"type":"object"}}}},"UrlCheck":{"type":"object","properties":{"ok":{"type":"boolean","description":"Whether the URL looks fetchable."},"status":{"type":"integer","description":"HTTP status observed (when ok)."},"contentType":{"type":["string","null"]},"finalUrl":{"type":"string","format":"uri","description":"URL after redirects."},"definitive":{"type":"boolean","description":"When ok=false: true = definitely bad, false = might still work."},"reason":{"type":"string","description":"Human-readable reason when ok=false."}}},"FeedbackRequest":{"type":"object","required":["kind","title"],"properties":{"kind":{"type":"string","enum":["bug","error","idea","missing"],"description":"bug = wrong behavior, error = a failure, missing = something that should exist, idea = an improvement."},"title":{"type":"string","minLength":5,"maxLength":200,"description":"One-line summary."},"details":{"type":"string","maxLength":4000,"description":"What happened, what you expected."},"context":{"type":"object","description":"Small JSON object (<= 2 KB), e.g. { route, params, status }."},"agent":{"type":"object","description":"Who is reporting (<= 512 B), e.g. { name, model }."},"app":{"type":"string","enum":["miroshark-website","miroshark"],"default":"miroshark-website","description":"miroshark-website for this site, miroshark for the simulation product."}}},"FeedbackResult":{"type":"object","required":["id"],"properties":{"id":{"type":["integer","string"],"description":"The inbox id of the report."}}},"Error":{"type":"object","description":"The one error envelope every site API route answers with (app/api/_lib/api-response.ts).","required":["error"],"properties":{"error":{"type":"object","required":["code","message"],"properties":{"code":{"type":"string","description":"Stable machine token, e.g. not_found or rate_limited."},"message":{"type":"string","description":"Human-readable summary."},"hint":{"type":"string","description":"How to recover, when there is one."}}}}}}}}