{"openapi":"3.1.0","info":{"title":"Agent Ready API","version":"1.0.0","description":"Programmatic access to agent-ready.dev scans. Available to Pro subscribers. Auth via `Authorization: Bearer ar_live_...` issued from the dashboard.\n\n## Versioning and deprecation policy\n\nThe REST API uses URL-based versioning. Stable endpoints live under `/api/v1`; breaking changes ship behind a new version prefix (`/api/v2`, …) and never on the existing one. When an endpoint is scheduled for removal we set [`Sunset`](https://datatracker.ietf.org/doc/html/rfc8594) and [`Deprecation`](https://datatracker.ietf.org/doc/html/rfc9745) response headers with the planned removal date, mark the operation `deprecated: true` in this spec, and announce the deprecation in the changelog at least 90 days before removal. No `/api/v1` endpoint is currently deprecated."},"servers":[{"url":"https://agent-ready.dev"}],"security":[{"ApiKey":[]}],"components":{"securitySchemes":{"ApiKey":{"type":"http","scheme":"bearer","bearerFormat":"ar_live_<prefix>_<secret>","description":"API key issued from /dashboard/api-keys. Pro subscription required."}},"schemas":{"CheckResult":{"type":"object","properties":{"checkId":{"type":"string","example":"S1"},"name":{"type":"string","example":"llms.txt exists"},"status":{"type":"string","enum":["pass","fail","warn","error"],"description":"Outcome of a single check."},"message":{"type":"string"},"howToFix":{"type":["string","null"]},"details":{"type":"object","additionalProperties":{}}},"required":["checkId","name","status","message","howToFix","details"],"description":"Result of a single check."},"PageResult":{"type":"object","properties":{"url":{"type":"string","format":"uri"},"checks":{"type":"array","items":{"$ref":"#/components/schemas/CheckResult"}}},"required":["url","checks"],"description":"Per-page check results."},"Scan":{"type":"object","properties":{"id":{"type":"string","example":"V1StGXR8_Z"},"rootUrl":{"type":"string","format":"uri"},"status":{"type":"string","enum":["running","completed","failed"],"description":"Lifecycle state of a scan. Poll until status is 'completed' or 'failed'."},"createdAt":{"type":"string","format":"date-time"},"completedAt":{"type":["string","null"],"format":"date-time"},"pagesDiscovered":{"type":"integer","minimum":0},"pagesScanned":{"type":"integer","minimum":0},"vercelScore":{"type":"integer","minimum":0,"maximum":100},"vercelRating":{"type":"string","enum":["excellent","good","fair","needs_improvement"],"description":"Coarse rating bucket derived from the Vercel Agent Readability score."},"llmstxtScore":{"type":"integer","minimum":0,"maximum":100},"siteChecks":{"type":"array","items":{"$ref":"#/components/schemas/CheckResult"}},"llmstxtChecks":{"type":"array","items":{"$ref":"#/components/schemas/CheckResult"}},"pageResults":{"type":"array","items":{"$ref":"#/components/schemas/PageResult"}},"shareToken":{"type":"string"}},"required":["id","rootUrl","status","createdAt","completedAt","pagesDiscovered","pagesScanned","vercelScore","vercelRating","llmstxtScore","siteChecks","llmstxtChecks","pageResults","shareToken"],"description":"Full scan result. Same shape returned for sync, async, and cached reads."},"ScanSummary":{"type":"object","properties":{"id":{"type":"string"},"shareToken":{"type":"string"},"domain":{"type":"string"},"rootUrl":{"type":"string","format":"uri"},"vercelScore":{"type":["integer","null"]},"vercelRating":{"type":["string","null"],"enum":["excellent","good","fair","needs_improvement",null],"description":"Coarse rating bucket derived from the Vercel Agent Readability score."},"llmstxtScore":{"type":["integer","null"]},"pagesScanned":{"type":["integer","null"]},"createdAt":{"type":"string","format":"date-time"}},"required":["id","shareToken","domain","rootUrl","vercelScore","vercelRating","llmstxtScore","pagesScanned","createdAt"],"description":"Lightweight scan listing entry."},"StartScanRequest":{"type":"object","properties":{"url":{"type":"string","maxLength":2000,"format":"uri","example":"https://example.com"},"pageLimit":{"type":"integer","minimum":1,"maximum":2000}},"required":["url"],"description":"Body for POST /api/v1/scans."},"StartScanResponse":{"type":"object","properties":{"id":{"type":"string"},"status":{"type":"string","enum":["running","completed","failed"],"description":"Lifecycle state of a scan. Poll until status is 'completed' or 'failed'."},"url":{"type":"string","format":"uri"},"pollUrl":{"type":"string"}},"required":["id","status","url","pollUrl"],"description":"Async-job acknowledgement returned with 202."},"ScanListResponse":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/ScanSummary"}},"nextCursor":{"type":"string"}},"required":["data"],"description":"Paginated list of scans owned by the API key."},"Error":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"subscription_required"},"message":{"type":"string"}},"required":["code","message"]}},"required":["error"],"description":"Structured error envelope."},"AskRequest":{"type":"object","properties":{"query":{"anyOf":[{"type":"string","minLength":1,"maxLength":2000},{"type":"object","properties":{"q":{"type":"string","minLength":1,"maxLength":2000},"itemType":{"type":"string","enum":["methodology","checks","specs","llms-txt","check","any"],"description":"Optional filter mapping to an Agent Ready corpus type. 'any' (default) searches everything."},"site":{"type":"string"}},"required":["q"]}],"example":"how is the agent readability score calculated?"},"mode":{"type":"string","enum":["list","summarize","generate"]},"streaming":{"anyOf":[{"type":"boolean"},{"type":"string"}]},"itemType":{"type":"string","enum":["methodology","checks","specs","llms-txt","check","any"],"description":"Optional filter mapping to an Agent Ready corpus type. 'any' (default) searches everything."},"site":{"type":"string"},"query_id":{"type":"string"},"prev":{"type":"string"},"decontextualized_query":{"type":"string"},"prefer":{"type":"object","properties":{"streaming":{"type":"boolean"},"format":{"type":"string","enum":["json"]},"mode":{"type":"string","enum":["list","summarize"]},"language":{"type":"string"}}},"context":{"type":"array","items":{"type":"object","properties":{"role":{"type":"string","enum":["user","assistant"]},"content":{"type":"string"}},"required":["role","content"]}},"meta":{"type":"object","properties":{"version":{"type":"string"},"session":{"type":"string"},"user":{"type":"string"}}}},"required":["query"],"description":"NLWeb /ask request. `query` is the natural-language string; flat `mode`/`streaming` follow the github.com/nlweb-ai/NLWeb REST API. The object form `{ query: { q } }` plus a `prefer` envelope is also accepted."},"SchemaObject":{"type":"object","properties":{"@context":{"type":"string","enum":["https://schema.org"]},"@type":{"type":"string","enum":["Article","TechArticle","WebPage"]},"name":{"type":"string"},"url":{"type":"string"},"description":{"type":"string"}},"required":["@context","@type","name","url"],"description":"Schema.org JSON-LD for a result."},"AskResult":{"type":"object","properties":{"url":{"type":"string"},"name":{"type":"string"},"site":{"type":"string"},"score":{"type":"number","description":"Relevance score (BM25)."},"description":{"type":"string"},"schema_object":{"$ref":"#/components/schemas/SchemaObject"}},"required":["url","name","site","score","description","schema_object"],"description":"An NLWeb result object."},"AskMeta":{"type":"object","properties":{"response_type":{"type":"string","enum":["answer","failure","elicitation","promise"]},"version":{"type":"string","enum":["0.1"]},"mode":{"type":"string","enum":["list","summarize"]}},"required":["response_type","version","mode"],"description":"NLWeb response envelope metadata."},"AskAnswer":{"type":"object","properties":{"_meta":{"$ref":"#/components/schemas/AskMeta"},"query_id":{"type":"string"},"site":{"type":"string"},"mode":{"type":"string","enum":["list","summarize"]},"query":{"type":"string"},"results":{"type":"array","items":{"$ref":"#/components/schemas/AskResult"}},"summary":{"type":"string","description":"Present when mode is 'summarize'."}},"required":["_meta","query_id","site","mode","query","results"],"description":"Successful NLWeb answer."},"AskFailure":{"type":"object","properties":{"_meta":{"$ref":"#/components/schemas/AskMeta"},"error":{"type":"object","properties":{"code":{"type":"string","enum":["NO_RESULTS","INVALID_QUERY","RATE_LIMITED","TIMEOUT","INTERNAL"]},"message":{"type":"string"}},"required":["code","message"]}},"required":["_meta","error"],"description":"NLWeb failure envelope."},"AskResponse":{"anyOf":[{"$ref":"#/components/schemas/AskAnswer"},{"$ref":"#/components/schemas/AskFailure"}],"description":"Either an answer or a failure."}},"parameters":{}},"paths":{"/api/v1/scans":{"post":{"operationId":"startScan","summary":"Start a scan","description":"Queues an asynchronous scan and returns a 202 with the scan id. Poll GET /api/v1/scans/{id} until status is 'completed' or 'failed'.","tags":["Scans"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StartScanRequest"}}}},"responses":{"202":{"description":"Scan queued. The `Location` response header carries the polling URL (per RFC 7231 §6.3.3); the same URL is also returned as `pollUrl` in the body.","headers":{"Location":{"description":"Absolute or root-relative URL to poll for the scan result.","schema":{"type":"string","example":"/api/v1/scans/V1StGXR8_Z"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StartScanResponse"}}}},"400":{"description":"Invalid request body or URL.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Subscription required.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded. Response carries `Retry-After` (seconds) plus `X-RateLimit-Limit` and `X-RateLimit-Remaining`. Back off using exponential delay with jitter — see the Rate limits & retry section in the docs.","headers":{"X-RateLimit-Limit":{"description":"Maximum number of requests permitted in the current window.","schema":{"type":"integer","example":10}},"X-RateLimit-Remaining":{"description":"Requests remaining in the current window. `0` on a 429 response.","schema":{"type":"integer","example":0}},"Retry-After":{"description":"Seconds until a slot frees in the sliding window (RFC 7231 §7.1.3). Honour this before retrying.","schema":{"type":"integer","example":42}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Internal server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"Service temporarily unavailable. Retry with backoff.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"get":{"operationId":"listScans","summary":"List recent scans","description":"Returns scans owned by the API key's user, newest first. Cursor-paginate by passing the `nextCursor` value from a previous response as the `cursor` query parameter on the next request; `nextCursor` is only returned when the page filled exactly to `limit`.","tags":["Scans"],"parameters":[{"schema":{"type":"integer","minimum":1,"maximum":100},"required":false,"name":"limit","in":"query"},{"schema":{"type":"string","format":"date-time","description":"Opaque pagination cursor (ISO 8601 datetime returned as `nextCursor` by a previous response). Returns scans strictly older than the cursor.","example":"2026-04-19T00:00:00.000Z"},"required":false,"description":"Opaque pagination cursor (ISO 8601 datetime returned as `nextCursor` by a previous response). Returns scans strictly older than the cursor.","name":"cursor","in":"query"}],"responses":{"200":{"description":"Scan list.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ScanListResponse"}}}},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Subscription required.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Internal server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"Service temporarily unavailable. Retry with backoff.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/scans/{id}":{"get":{"operationId":"getScan","summary":"Get a scan","description":"Returns the full scan including per-check results. Returns 404 if the scan does not exist or is not owned by the API key's user.","tags":["Scans"],"parameters":[{"schema":{"type":"string","description":"Scan id."},"required":true,"description":"Scan id.","name":"id","in":"path"}],"responses":{"200":{"description":"Scan.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Scan"}}}},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Subscription required.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Scan not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Polling too frequently. Response carries `Retry-After` (seconds) plus `X-RateLimit-Limit` and `X-RateLimit-Remaining`.","headers":{"X-RateLimit-Limit":{"description":"Maximum number of requests permitted in the current window.","schema":{"type":"integer","example":10}},"X-RateLimit-Remaining":{"description":"Requests remaining in the current window. `0` on a 429 response.","schema":{"type":"integer","example":0}},"Retry-After":{"description":"Seconds until a slot frees in the sliding window (RFC 7231 §7.1.3). Honour this before retrying.","schema":{"type":"integer","example":42}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Internal server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"Service temporarily unavailable. Retry with backoff.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/ask":{"get":{"operationId":"askGet","summary":"Ask in natural language (NLWeb)","description":"NLWeb /ask endpoint, conventional GET form: /ask?query=<text>. Natural-language search over Agent Ready's own content (methodology, check registry, specs). Public — no API key required. Returns NLWeb result objects with a nested Schema.org `schema_object`. Set stream=true for Server-Sent Events. Also served at the root path /ask.","tags":["NLWeb"],"parameters":[{"schema":{"type":"string","example":"how is the score calculated?"},"required":true,"name":"query","in":"query"},{"schema":{"type":"string","enum":["list","summarize"]},"required":false,"name":"mode","in":"query"},{"schema":{"type":"string","enum":["methodology","checks","specs","llms-txt","check"]},"required":false,"name":"itemType","in":"query"},{"schema":{"type":"string","enum":["true","false"]},"required":false,"name":"stream","in":"query"}],"responses":{"200":{"description":"An NLWeb answer.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AskResponse"}}}},"400":{"description":"Missing or invalid query.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded. Response carries `Retry-After` (seconds) plus `X-RateLimit-Limit` and `X-RateLimit-Remaining`. Back off using exponential delay with jitter — see the Rate limits & retry section in the docs.","headers":{"X-RateLimit-Limit":{"description":"Maximum number of requests permitted in the current window.","schema":{"type":"integer","example":10}},"X-RateLimit-Remaining":{"description":"Requests remaining in the current window. `0` on a 429 response.","schema":{"type":"integer","example":0}},"Retry-After":{"description":"Seconds until a slot frees in the sliding window (RFC 7231 §7.1.3). Honour this before retrying.","schema":{"type":"integer","example":42}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Internal server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"Service temporarily unavailable. Retry with backoff.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"operationId":"askPost","summary":"Ask in natural language (NLWeb), JSON-body form","description":"NLWeb /ask endpoint, JSON-body form: { query: { q }, prefer: { mode, streaming } }. Same response as the GET form. Public — no API key required.","tags":["NLWeb"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AskRequest"}}}},"responses":{"200":{"description":"An NLWeb answer.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AskResponse"}}}},"400":{"description":"Invalid /ask request body.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No results (NLWeb failure envelope).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AskResponse"}}}},"429":{"description":"Rate limit exceeded. Response carries `Retry-After` (seconds) plus `X-RateLimit-Limit` and `X-RateLimit-Remaining`. Back off using exponential delay with jitter — see the Rate limits & retry section in the docs.","headers":{"X-RateLimit-Limit":{"description":"Maximum number of requests permitted in the current window.","schema":{"type":"integer","example":10}},"X-RateLimit-Remaining":{"description":"Requests remaining in the current window. `0` on a 429 response.","schema":{"type":"integer","example":0}},"Retry-After":{"description":"Seconds until a slot frees in the sliding window (RFC 7231 §7.1.3). Honour this before retrying.","schema":{"type":"integer","example":42}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Internal server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"Service temporarily unavailable. Retry with backoff.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}},"webhooks":{}}