{"openapi":"3.1.1","info":{"title":"LOLSkills API","version":"1.0.0","description":"Queue bounded scans of public GitHub skill bundles and read sanitized, unlisted results. Hosted submissions require an operator-issued scanner API key; loopback SQLite development can run without one. The website and API share queue capacity. This API does not execute skill instructions and does not publish reports."},"servers":[{"url":"/","description":"The same origin serving this document"}],"externalDocs":{"description":"Quickstart and integration guide","url":"/docs/api"},"tags":[{"name":"Scans","description":"Submit once, then poll the returned capability URL."}],"paths":{"/api/v1/scans":{"post":{"tags":["Scans"],"operationId":"createScan","summary":"Queue a public GitHub skill scan","description":"Requires Authorization: Bearer with the operator-managed SCANNER_API_KEY in hosted mode, or whenever that key is configured locally. Only loopback SQLite without a configured key permits unauthenticated requests. This local exception is intentionally not advertised as a hosted anonymous security alternative. No query parameters, cross-origin browser requests, or CORS are supported. Limit: 5 submissions per integration key per 10 minutes; 100 submissions across the hosted website and API per hour. Anonymous local submissions share the local browser bucket. AI is opt-in and may incur operator provider charges. Submission is not idempotent: retrying a successful POST queues another job.","security":[{"scannerApiKey":[]}],"requestBody":{"required":true,"description":"JSON body up to 8,192 bytes. No file contents or credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ScanInput"},"examples":{"repository":{"summary":"Deterministic scan","value":{"source":"https://github.com/owner/repository","intent":false}},"pinnedSkill":{"summary":"Pin a known commit and opt into intent assessment","value":{"source":"https://github.com/owner/repository","ref":"0123456789abcdef0123456789abcdef01234567","skillPath":"skills/example/SKILL.md","intent":true}}}}}},"responses":{"202":{"description":"Queued. Save both URLs; neither can be recovered by listing scans. Poll statusUrl until complete or incomplete. A running worker is required.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ScanAccepted"}}},"headers":{"Location":{"description":"Relative status capability URL, identical to statusUrl.","schema":{"type":"string","format":"uri-reference"}},"Retry-After":{"description":"Minimum suggested seconds before polling again, or conservative delay after a submission limit.","schema":{"type":"integer","minimum":1}}}},"400":{"description":"Invalid JSON, unsupported parameters, or unsupported GitHub source/ref/path.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or invalid scanner API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"WWW-Authenticate":{"schema":{"type":"string"},"description":"Bearer authentication challenge."}}},"403":{"description":"Host, origin, or browser request context is not allowed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"405":{"description":"HTTP method not supported. Framework response; it may have an empty body."},"413":{"description":"JSON body exceeds 8,192 bytes.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"415":{"description":"Content-Type must be application/json.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Client/key limit or shared hosted capacity reached.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"Retry-After":{"description":"Minimum suggested seconds before polling again, or conservative delay after a submission limit.","schema":{"type":"integer","minimum":1}}}},"503":{"description":"Server configuration or queue storage is unavailable. Internal details are not returned.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/scans/{token}":{"get":{"tags":["Scans"],"operationId":"getScan","summary":"Read progress and the sanitized report","description":"The 64-character random token in the returned statusUrl is the read credential. Anyone holding this URL can read the report; no API key is needed. Keep it out of logs, analytics, public examples, and source control. No public listing or lookup by job ID, repository, or API key exists. Poll every 3 seconds or slower, and stop at complete or incomplete. An incomplete job may have a partial report or report:null with an error. Complete does not imply safe or analyst-approved.","security":[],"parameters":[{"name":"token","in":"path","required":true,"description":"Bearer report capability returned only at creation. Not a job ID or token hash.","schema":{"type":"string","pattern":"^[a-f0-9]{64}$","minLength":64,"maxLength":64}}],"responses":{"200":{"description":"Current state; report is null while queued/running. Terminal outcomes remain available at the same URL. All responses are private and no-store.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicScan"}}},"headers":{"Retry-After":{"description":"Present only while the scan is not terminal. Minimum suggested polling delay in seconds.","schema":{"type":"integer","minimum":1}}}},"403":{"description":"Host is outside the configured scanner origin or allowed local loopback boundary.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Unknown, malformed, or expired report capability.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"405":{"description":"HTTP method not supported. Framework response; it may have an empty body."},"503":{"description":"Server configuration or queue storage is unavailable.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}},"components":{"securitySchemes":{"scannerApiKey":{"type":"http","scheme":"bearer","description":"Operator-managed scanner integration key, not a Supabase key. Keep it server-side. Configure SCANNER_API_KEY with 32–256 random URL-safe characters. No self-service issuance is implemented."}},"schemas":{"ScanInput":{"type":"object","additionalProperties":false,"required":["source"],"properties":{"source":{"type":"string","minLength":1,"maxLength":2048,"description":"Public github.com repository, tree, or SKILL.md blob URL. HTTPS only; the github.com/ prefix is also accepted. Credentials, ports, query strings, fragments, encoded paths, and non-GitHub hosts are rejected."},"ref":{"type":["string","null"],"maxLength":200,"description":"Optional branch, tag, or commit SHA. Omit, null, or empty uses the URL ref or repository default branch. Use a repository root URL with explicit ref for branch names containing slashes."},"skillPath":{"type":["string","null"],"maxLength":500,"description":"Optional relative skill folder or SKILL.md path. Omit, null, or empty uses the URL path or scans discovered skills. Use SKILL.md to select a root skill. Traversal, encoded paths, and absolute paths are rejected."},"intent":{"type":"boolean","default":false,"description":"Explicitly opt into advisory AI intent assessment. If requested analysis cannot complete, coverage is incomplete."}}},"ScanAccepted":{"type":"object","additionalProperties":false,"required":["reportUrl","statusUrl"],"properties":{"reportUrl":{"type":"string","pattern":"^/reports/[a-f0-9]{64}$","description":"Relative unlisted web report URL; resolve against the scanner origin."},"statusUrl":{"type":"string","pattern":"^/api/v1/scans/[a-f0-9]{64}$","description":"Relative polling capability URL; resolve against the scanner origin."}}},"Error":{"type":"object","additionalProperties":false,"required":["error"],"properties":{"error":{"type":"string"}}},"ScanStatus":{"type":"string","enum":["queued","fetching","scanning","analyzing","complete","incomplete"]},"Verdict":{"type":"string","enum":["no_findings","signals_detected","needs_review","incomplete"],"description":"Automated observations, never a guarantee of safety or analyst-confirmed maliciousness."},"ScanTarget":{"type":"object","additionalProperties":false,"required":["repository","ref","skillPath"],"properties":{"repository":{"type":"string"},"ref":{"type":["string","null"]},"skillPath":{"type":["string","null"],"description":"Normalized repository-relative skill folder; '.' denotes the root skill."}}},"PublicScan":{"type":"object","additionalProperties":false,"required":["status","target","createdAt","updatedAt","report","error"],"properties":{"status":{"$ref":"#/components/schemas/ScanStatus"},"target":{"$ref":"#/components/schemas/ScanTarget"},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"},"report":{"anyOf":[{"$ref":"#/components/schemas/ScanReport"},{"type":"null"}]},"error":{"type":["string","null"]}}},"ScanReport":{"type":"object","additionalProperties":false,"required":["repository","commitSha","requestedRef","skillPath","sourceUrl","verdict","startedAt","completedAt","durationSeconds","skills","coverage","engine","isDemo"],"properties":{"repository":{"type":"string"},"commitSha":{"type":"string","pattern":"^[a-f0-9]{40}$","description":"Immutable GitHub commit resolved before scanning."},"requestedRef":{"type":["string","null"]},"skillPath":{"type":["string","null"]},"sourceUrl":{"type":"string","format":"uri","description":"Verified GitHub source tree at the scanned commit."},"verdict":{"$ref":"#/components/schemas/Verdict"},"startedAt":{"type":"string","format":"date-time"},"completedAt":{"type":"string","format":"date-time"},"durationSeconds":{"type":["number","null"],"minimum":0,"description":"Null when a public observation does not record an individual scan duration."},"skills":{"type":"array","items":{"$ref":"#/components/schemas/SkillResult"}},"coverage":{"$ref":"#/components/schemas/Coverage"},"engine":{"$ref":"#/components/schemas/Engine"},"isDemo":{"type":"boolean","description":"Real queued scans return false. Site example reports are separately labeled demonstrations."}}},"Coverage":{"type":"object","additionalProperties":false,"required":["complete","incomplete","gaps"],"properties":{"complete":{"type":"integer","minimum":0},"incomplete":{"type":"integer","minimum":0},"gaps":{"type":"array","items":{"type":"string"}}}},"Engine":{"type":"object","additionalProperties":false,"required":["scanner","nova","yara","rulesRevision","intentModel"],"properties":{"scanner":{"type":"string"},"nova":{"type":"string"},"yara":{"type":"string"},"rulesRevision":{"type":"string"},"intentModel":{"type":["string","null"]}}},"SkillResult":{"type":"object","additionalProperties":false,"required":["name","description","path","verdict","candidateTier","summary","fileCount","totalBytes","artifactHash","specValid","findings","rawMatches","rawMatchCount","rawMatchesTruncated","intent"],"properties":{"name":{"type":"string"},"description":{"type":"string"},"path":{"type":"string","description":"Repository-relative SKILL.md path."},"verdict":{"$ref":"#/components/schemas/Verdict"},"candidateTier":{"type":"string","enum":["none","low","medium","high"]},"summary":{"type":"string"},"fileCount":{"type":"integer","minimum":0},"totalBytes":{"type":"integer","minimum":0},"artifactHash":{"type":["string","null"]},"specValid":{"type":["boolean","null"]},"findings":{"type":"array","items":{"$ref":"#/components/schemas/Finding"}},"rawMatches":{"type":"array","items":{"$ref":"#/components/schemas/RawMatch"}},"rawMatchCount":{"type":"integer","minimum":0},"rawMatchesTruncated":{"type":"boolean"},"intent":{"$ref":"#/components/schemas/IntentAssessment"}}},"Finding":{"type":"object","additionalProperties":false,"required":["kind","confidence","path","lines","engines","rules","explanation"],"properties":{"kind":{"type":"string"},"confidence":{"type":"string","enum":["low","medium","high"],"description":"The current adapter publishes only medium/high correlated findings; low-confidence observations stay in rawMatches."},"path":{"type":"string","description":"Already repository-relative; do not prepend the skill folder."},"lines":{"type":"array","items":{"type":"integer","minimum":1}},"engines":{"type":"array","items":{"type":"string"}},"rules":{"type":"array","items":{"type":"string"}},"explanation":{"type":"string"}}},"RawMatch":{"type":"object","additionalProperties":false,"required":["engine","rule","path","severity","lines"],"properties":{"engine":{"type":"string","enum":["Nova","YARA"]},"rule":{"type":"string"},"path":{"type":"string","description":"Already repository-relative."},"severity":{"type":"string"},"lines":{"type":"array","items":{"type":"integer","minimum":1}}}},"IntentAssessment":{"type":"object","additionalProperties":false,"required":["status"],"properties":{"status":{"type":"string","enum":["complete","not_requested","not_applicable","unavailable"]},"label":{"type":"string","enum":["likely_malicious","dual_use_security","likely_benign","uncertain"]},"confidence":{"type":"number","minimum":0,"maximum":1},"rationale":{"type":"string"},"model":{"type":"string"}}}}}}