{"openapi":"3.1.0","info":{"title":"Vettd Skills API","version":"0.1.0","description":"OpenAPI contract for Vettd's current Skills audit API. The API is audit-oriented: create immutable skill audits, read them back, publish or unpublish them, and re-run GitHub-backed audits as new revisions."},"jsonSchemaDialect":"https://spec.openapis.org/oas/3.1/dialect/base","servers":[{"url":"/","description":"Current deployment origin"}],"tags":[{"name":"Skills","description":"Create, inspect, publish, and re-run skill audits"},{"name":"Batch scans","description":"Queue, inspect status of, and cancel bulk scan batches"},{"name":"Public directory","description":"Keyless public read surface: lookup, search, compare, reports, badges"}],"components":{"securitySchemes":{"SessionCookieAuth":{"type":"apiKey","in":"cookie","name":"next-auth.session-token","description":"Authenticated browser session. Production deployments may use a __Secure-next-auth.session-token cookie name."},"ApiKeyAuth":{"type":"apiKey","in":"header","name":"Authorization","description":"Vettd API key in the form 'Bearer ah_<token>'. Supported today on GitHub import and audit detail/report endpoints."}},"schemas":{"ErrorResponse":{"type":"object","description":"Error body. New and updated public endpoints add RFC 9457-style problem members (type/title/status) plus next (the operation that resolves the problem) and documentation (the agent guide); the legacy error field is always present.","properties":{"error":{"type":"string"},"details":{"type":"object","additionalProperties":true},"type":{"type":"string","format":"uri","description":"Problem type URI (agent-guide anchor)"},"title":{"type":"string"},"status":{"type":"integer"},"next":{"$ref":"#/components/schemas/NextOperation"},"documentation":{"type":"string","format":"uri"}},"required":["error"],"additionalProperties":true},"AssetFinding":{"type":"object","properties":{"id":{"type":"string"},"category":{"type":"string","enum":["security","structure","description","best-practices","scripts","evals"]},"label":{"type":"string"},"detail":{"type":"string"},"severity":{"type":"string","enum":["critical","high","medium","low","info"]},"filepath":{"type":"string"},"intent":{"type":"string","enum":["malicious","negligent"],"nullable":true},"chainId":{"type":"string","nullable":true}},"required":["category","label","detail","severity"],"additionalProperties":false},"DetailedAssetFinding":{"allOf":[{"$ref":"#/components/schemas/AssetFinding"},{"type":"object","properties":{"ruleId":{"type":"string"},"sources":{"type":"array","items":{"type":"string"}},"index":{"type":"integer","minimum":1}},"required":["ruleId","sources","index"],"additionalProperties":false}]},"AssetFindingSummary":{"type":"object","properties":{"total":{"type":"integer","minimum":0},"critical":{"type":"integer","minimum":0},"high":{"type":"integer","minimum":0},"medium":{"type":"integer","minimum":0},"low":{"type":"integer","minimum":0},"info":{"type":"integer","minimum":0},"hasDetectedChain":{"type":"boolean"},"byCategory":{"type":"array","items":{"type":"object","properties":{"category":{"type":"string","enum":["security","structure","description","best-practices","scripts","evals"]},"total":{"type":"integer","minimum":0},"critical":{"type":"integer","minimum":0},"high":{"type":"integer","minimum":0},"medium":{"type":"integer","minimum":0},"low":{"type":"integer","minimum":0},"info":{"type":"integer","minimum":0}},"required":["category","total","critical","high","medium","low","info"],"additionalProperties":false}}},"required":["total","critical","high","medium","low","info","hasDetectedChain","byCategory"],"additionalProperties":false},"SkillAuditSummary":{"type":"object","properties":{"id":{"type":"string"},"repositoryId":{"type":["string","null"]},"name":{"type":"string"},"description":{"type":["string","null"]},"version":{"type":["string","null"]},"author":{"type":["string","null"]},"license":{"type":["string","null"]},"status":{"type":"string"},"overallGrade":{"type":"string","enum":["A","B","C","F","pending","verified"]},"fileCount":{"type":"integer","minimum":0},"hasSkillMd":{"type":"boolean"},"hasScripts":{"type":"boolean"},"hasReferences":{"type":"boolean"},"hasEvals":{"type":"boolean"},"hasAssets":{"type":"boolean"},"sourceType":{"type":["string","null"]},"sourceUrl":{"type":["string","null"]},"createdAt":{"type":"string","format":"date-time"},"completedAt":{"type":["string","null"],"format":"date-time"},"_count":{"type":"object","properties":{"findings":{"type":"integer","minimum":0}},"required":["findings"],"additionalProperties":false}},"required":["id","name","status","overallGrade","fileCount","hasSkillMd","hasScripts","hasReferences","hasEvals","hasAssets","createdAt","_count"],"additionalProperties":true},"CreatedSkillAudit":{"allOf":[{"$ref":"#/components/schemas/SkillAuditSummary"},{"type":"object","properties":{"findings":{"type":"array","items":{"$ref":"#/components/schemas/AssetFinding"}},"scanEventId":{"type":["string","null"]},"reused":{"type":"boolean"}},"additionalProperties":true}]},"SkillHistoryEntry":{"type":"object","properties":{"id":{"type":"string"},"overallGrade":{"type":"string","enum":["A","B","C","F","pending","verified"]},"createdAt":{"type":"string","format":"date-time"},"sourceHash":{"type":["string","null"]}},"required":["id","overallGrade","createdAt","sourceHash"],"additionalProperties":false},"SkillAuditDetail":{"allOf":[{"$ref":"#/components/schemas/SkillAuditSummary"},{"type":"object","properties":{"compatibility":{"oneOf":[{"type":"string"},{"type":"array","items":{"type":"string"}},{"type":"null"}]},"rawManifest":{"type":"object","additionalProperties":true},"findings":{"type":"array","items":{"$ref":"#/components/schemas/DetailedAssetFinding"}},"findingSummary":{"$ref":"#/components/schemas/AssetFindingSummary"},"history":{"type":"array","items":{"$ref":"#/components/schemas/SkillHistoryEntry"}},"visibility":{"type":["string","null"],"enum":["private","public",null]},"slug":{"type":["string","null"]},"category":{"type":["string","null"],"enum":["Search","Infrastructure","Data","Communication","Database","Security","Automation","Monitoring","Testing","Development","AI/ML","Other",null]},"frameworks":{"type":"array","items":{"type":"string","enum":["OWASP","NIST 800-53","CMMC","ISO 42001","EU AI Act","CISA"]}},"publishedAt":{"type":["string","null"],"format":"date-time"}},"required":["findings","findingSummary","history"],"additionalProperties":true}]},"PublishSkillInput":{"type":"object","properties":{"visibility":{"type":"string","enum":["private","public"]},"category":{"type":"string","enum":["Search","Infrastructure","Data","Communication","Database","Security","Automation","Monitoring","Testing","Development","AI/ML","Other"]},"frameworks":{"type":"array","items":{"type":"string","enum":["OWASP","NIST 800-53","CMMC","ISO 42001","EU AI Act","CISA"]}},"slug":{"type":"string","minLength":3,"maxLength":64,"pattern":"^[a-z0-9]+(?:-[a-z0-9]+)*$"},"confirmPrivateSourcePublicListing":{"type":"boolean","description":"Required only when publishing a GitHub-backed skill whose source repository is private."}},"required":["visibility","category","frameworks","slug"],"additionalProperties":false},"GitHubSkillImportRequest":{"type":"object","properties":{"url":{"type":"string","description":"GitHub repository, tree, or SKILL.md URL of the skill to analyze"},"repo":{"type":"string","description":"owner/repo shorthand as an alternative to url"},"branch":{"type":"string","description":"Branch to import from; omitted means the repository's default branch."},"path":{"type":"string","description":"Path to a concrete skill directory within the repository"}},"additionalProperties":false},"SkillAuditReport":{"type":"object","properties":{"reportVersion":{"type":"integer","const":1},"reportType":{"type":"string"},"generatedAt":{"type":"string","format":"date-time"},"statusPolicy":{"type":"object","properties":{"summaryStatus":{"type":"string"},"drivenByCategories":{"type":"array","items":{"type":"string","enum":["security","structure"]}},"findingOrder":{"type":"object","properties":{"categories":{"type":"array","items":{"type":"string","enum":["security","structure","description","best-practices","scripts","evals"]}},"severities":{"type":"array","items":{"type":"string","enum":["critical","high","medium","low","info"]}}},"required":["categories","severities"],"additionalProperties":false}},"required":["summaryStatus","drivenByCategories","findingOrder"],"additionalProperties":false},"skill":{"type":"object","additionalProperties":true},"audit":{"type":"object","additionalProperties":true},"findings":{"type":"array","items":{"$ref":"#/components/schemas/DetailedAssetFinding"}},"summary":{"$ref":"#/components/schemas/AssetFindingSummary"}},"required":["reportVersion","reportType","generatedAt","statusPolicy","skill","audit","findings","summary"],"additionalProperties":false},"NextOperation":{"type":"object","description":"The exact operation that resolves the problem.","properties":{"method":{"type":"string"},"href":{"type":"string","format":"uri"},"requires":{"type":"string"},"body":{"type":"object","additionalProperties":true},"note":{"type":"string"}},"required":["method","href"],"additionalProperties":true},"Problem":{"type":"object","description":"RFC 9457-style problem body (application/problem+json) for the keyless public surface and the middleware's auth-required 401. type is anchored in the agent guide's Recovery section.","properties":{"type":{"type":"string","format":"uri","pattern":"/agent\\.md#problem-(skill-unassessed|validation-failed|auth-required|rate-limited|not-found)$"},"title":{"type":"string"},"status":{"type":"integer"},"error":{"type":"string","description":"Legacy alias of title, kept for existing consumers"},"documentation":{"type":"string","format":"uri"},"next":{"$ref":"#/components/schemas/NextOperation"},"details":{"type":"object","additionalProperties":true}},"required":["type","title","status","error","documentation"],"additionalProperties":true},"UnknownSkillSlot":{"type":"object","description":"A requested /api/directory/compare identifier that matched no published skill (#951); a machine-readable per-identifier pointer alongside the plain missing[] id list.","properties":{"id":{"type":"string"},"status":{"type":"string","enum":["unknown"]},"next":{"type":"object","properties":{"method":{"type":"string","enum":["GET"]},"href":{"type":"string","format":"uri"},"note":{"type":"string"}},"required":["method","href","note"],"additionalProperties":true},"documentation":{"type":"string","format":"uri"}},"required":["id","status","next","documentation"],"additionalProperties":true},"BatchTarget":{"type":"object","properties":{"kind":{"type":"string","const":"github-reimport"},"sourceUrl":{"type":"string","format":"uri","description":"GitHub repository URL"},"label":{"type":"string","maxLength":300,"description":"Optional human-readable label for the target"}},"required":["kind","sourceUrl"],"additionalProperties":false},"BatchRequestBody":{"type":"object","properties":{"targets":{"type":"array","minItems":1,"items":{"$ref":"#/components/schemas/BatchTarget"},"description":"Up to MAX_BULK_SELECTION targets (currently 5000) to enqueue as a single batch"}},"required":["targets"],"additionalProperties":false},"BatchEnqueueResponse":{"type":"object","properties":{"batchId":{"type":"string"},"enqueued":{"type":"integer","minimum":0},"alreadyInFlight":{"type":"array","items":{"type":"object","properties":{"target":{"type":"string"},"existingJobId":{"type":"string"}},"required":["target","existingJobId"],"additionalProperties":false}}},"required":["batchId","enqueued","alreadyInFlight"],"additionalProperties":false},"BatchStatusResponse":{"type":"object","properties":{"batchId":{"type":"string"},"done":{"type":"boolean"},"cancelled":{"type":"boolean"},"totals":{"type":"object","additionalProperties":true},"jobs":{"type":"array","items":{"type":"object","additionalProperties":true}}},"required":["batchId","done","cancelled","totals","jobs"],"additionalProperties":false},"RateLimitHeaders":{"type":"object","properties":{"X-RateLimit-Limit":{"type":"string","description":"Maximum number of requests allowed per window"},"X-RateLimit-Remaining":{"type":"string","description":"Number of requests remaining in the current window"},"X-RateLimit-Reset":{"type":"string","description":"Seconds until the rate limit window resets"}},"required":["X-RateLimit-Limit","X-RateLimit-Remaining","X-RateLimit-Reset"],"additionalProperties":false},"DirectorySearchFields":{"type":"object","description":"Additive per-skill fields only present on the SEARCH_BETA_TESTING POST endpoint. Real by default (SEARCH_BETA_MOCK_DATA=false): backed by the agent_skills Qdrant catalog, matched to this skill PER-SKILL (owner/repo/subpath, ref-agnostic) so sibling skills in one repo never share a verdict. On the relevance-first path every returned skill has a catalog match; on the fallback (browse/unreachable) path an unmatched skill gets honest empty/zero values, not fabricated ones. When SEARCH_BETA_MOCK_DATA=true, docLanguage/agentCompatibility/rankings are deterministic fabricated values derived from the skill id instead (llm_scan/cli_security stay null — a scan verdict has no honest mock value).","properties":{"docLanguage":{"type":"string","description":"Real in real mode: the SKILL.md's spoken/content language (e.g. \"en\", \"ja-JP\"), NOT a programming language — deliberately not named `language` to avoid that implication. \"\" if unmatched. In mock mode: a fabricated value that looks like a programming language, for phase-1 backward compatibility."},"agentCompatibility":{"type":"array","items":{"type":"string"},"description":"Real in real mode: agent runtimes/tools this skill declares or is inferred to target (e.g. claude-code, cursor, codex, generic). [] if unmatched or undetected."},"rankings":{"type":"object","properties":{"stars":{"type":"integer","description":"Real GitHub stars from the matched catalog entry, or 0 if unmatched"},"skillsShLeaderboardRank":{"type":"integer","description":"This skill's own fused-search rank for this query (drives the relevance-first result order), or 0 if unmatched"},"numberOfAggregators":{"type":"integer","description":"Count of discovery sources (seed/search/manual/marketplace) for the matched catalog entry, or 0 if unmatched"},"officialClaudeMarketplace":{"type":"boolean","description":"Real: whether \"marketplace\" appears in the matched catalog entry's sources. false if unmatched."}},"required":["stars","skillsShLeaderboardRank","numberOfAggregators","officialClaudeMarketplace"],"additionalProperties":false},"llm_scan":{"type":["object","null"],"additionalProperties":true,"description":"Latest non-deterministic LLM threat-scan verdict for the matched catalog entry's SKILL.md, passed through verbatim from the agent_skills query service (openapi.json SkillHit.llm_scan). null until the skill has been scanned, in mock mode, and when unmatched or the query service is unreachable. Shape: {model, prompt_version, scanned_at, content_sha256, max_severity: CRITICAL|HIGH|MEDIUM|LOW|NONE, finding_count, primary_threats[], overall_assessment, findings[]}. Not preserved across a catalog re-index — can revert to null. Assert structure, not values (non-deterministic). See ah-skills/search-demo/docs/ARCHITECTURE_LLM_SCAN.md."},"cli_security":{"type":["object","null"],"additionalProperties":true,"description":"OSV-backed security-history grade for the command-line tools the matched catalog entry's SKILL.md tells you to install, passed through verbatim from the query service (openapi.json SkillHit.cli_security). null unless the skill installs a confirmed-CLI npm/pip package, plus mock mode / unmatched / query service unreachable. Shape: {grade: A|B|C, packages: [{package, ecosystem, classification, install_command, vuln_count, max_severity, advisory_ids[]}], scanned_at, osv_snapshot_date}. \"Has a security history\" (OSV queried version-less), not \"is vulnerable\". See ah-skills/search-demo/docs/ARCHITECTURE_CLI_SECURITY_SCAN.md."},"vettd_scan":{"type":["object","null"],"additionalProperties":true,"description":"DETERMINISTIC Vettd scan rollup for the matched catalog entry's repo, passed through verbatim from the query service (openapi.json SkillHit.locations[].vettd_scan_findings — the repo-matched location entry). null when the repo has no published Vettd scan, in mock mode, and when unmatched / query service unreachable. Shape: {scan_id, overall_grade, trust_level, has_malicious_findings, finding_count, severity_counts: {critical,high,medium,low,info}, categories_flagged[], top_findings: [{rule_id, category, severity, label}]}. Preserved across a catalog re-index. For a skill already imported and scanned in vettd, vettd holds this natively (SkillAudit.overallGrade + AssetFinding) — the catalog copy is the value-add for catalog skills not yet scanned here. Sourced from the locations[] entry whose repo matches the hit (first-entry fallback). See ah-skills/search-demo/docs/ARCHITECTURE_PUBLISHING_SCANS.md."}},"required":["docLanguage","agentCompatibility","rankings","llm_scan","cli_security","vettd_scan"],"additionalProperties":false},"DirectorySkill":{"type":"object","properties":{"id":{"type":"string"},"slug":{"type":"string"},"name":{"type":"string"},"description":{"type":"string"},"version":{"type":"string"},"author":{"type":"string"},"category":{"type":"string"},"frameworks":{"type":"array","items":{"type":"string"}},"badgeStatus":{"type":"string","enum":["vettd","verified","conditional","flagged","pending"]},"overallGrade":{"type":"string"},"downloadCount":{"type":"integer","minimum":0},"publishedAt":{"type":"string","format":"date-time"},"sourceType":{"type":"string"},"sourceUrl":{"type":["string","null"]}},"required":["id","slug","name","description","version","author","category","frameworks","badgeStatus","overallGrade","downloadCount","publishedAt","sourceType","sourceUrl"],"additionalProperties":true},"DirectorySearchSkill":{"allOf":[{"$ref":"#/components/schemas/DirectorySkill"},{"$ref":"#/components/schemas/DirectorySearchFields"}]},"DirectoryListResponse":{"type":"object","properties":{"skills":{"type":"array","items":{"$ref":"#/components/schemas/DirectorySkill"}},"total":{"type":"integer","minimum":0},"page":{"type":"integer","minimum":1},"totalPages":{"type":"integer","minimum":0}},"required":["skills","total","page","totalPages"],"additionalProperties":false},"DirectorySearchListResponse":{"type":"object","properties":{"skills":{"type":"array","items":{"$ref":"#/components/schemas/DirectorySearchSkill"}},"total":{"type":"integer","minimum":0},"page":{"type":"integer","minimum":1},"totalPages":{"type":"integer","minimum":0},"mock":{"type":"boolean","description":"Reflects SEARCH_BETA_MOCK_DATA on this deployment, not whether any individual skill got a real catalog match"}},"required":["skills","total","page","totalPages","mock"],"additionalProperties":false,"example":{"skills":[{"id":"cma8s0f2h000abcde12345678","slug":"e2e-testing","name":"e2e-testing","description":"Drives end-to-end browser tests with Playwright: installs the runner, generates specs from a user story, runs them headless with retries, and triages failures.","version":"1.4.0","author":"example-org","category":"Testing","frameworks":[],"badgeStatus":"verified","overallGrade":"B","downloadCount":5120,"publishedAt":"2026-08-13T18:42:07.000Z","sourceType":"github","sourceUrl":"https://github.com/example-org/skills-repo/tree/main/skills/e2e-testing","docLanguage":"en","agentCompatibility":["claude-code"],"rankings":{"stars":240095,"skillsShLeaderboardRank":142,"numberOfAggregators":2,"officialClaudeMarketplace":true},"llm_scan":{"model":"openrouter/deepseek/deepseek-v3.2","prompt_version":"37243f9d5700","scanned_at":"2026-08-30T21:41:26.512874+00:00","content_sha256":"b0e00e7e17cb259101139900816c5528aed18dd10bcf5f9cb42cfc35baf8a755","max_severity":"LOW","finding_count":1,"primary_threats":["unpinned-dependency-install"],"overall_assessment":"Benign testing skill. One low-severity note: instructs the agent to install the Playwright CLI globally at an unpinned version.","findings":[{"severity":"LOW","aitech":"AITech-4.3","aisubtech":"AITech-4.3.2","title":"Unpinned global CLI install","description":"SKILL.md instructs `npm install -g playwright` with no version constraint.","location":"SKILL.md, 'Setup' section","evidence":"npm install -g playwright && npx playwright install chromium","remediation":"Pin the version, e.g. `npm install -g playwright@1.55.0`."}]},"cli_security":{"grade":"C","scanned_at":"2026-08-30T21:40:47.315190+00:00","osv_snapshot_date":"2026-08-30","packages":[{"package":"playwright","ecosystem":"npm","classification":"cli","install_command":"npx playwright test tests/search.spec.ts --repeat-each=10","vuln_count":1,"max_severity":"HIGH","advisory_ids":["GHSA-7mvr-c777-76hp"]}]},"vettd_scan":{"scan_id":"scn_01J9Z7Q3K8V2M4N6P8R0T2W4Y6","overall_grade":"B","trust_level":"cautious","has_malicious_findings":false,"finding_count":4,"severity_counts":{"critical":0,"high":0,"medium":1,"low":2,"info":1},"categories_flagged":["scripts","best-practices"],"top_findings":[{"rule_id":"shell-exec-unpinned-install","category":"scripts","severity":"medium","label":"SKILL.md tells the agent to run 'npx playwright install' without a pinned version"}]}}],"total":1,"page":1,"totalPages":1,"mock":false}},"RankingsFilter":{"type":"object","properties":{"stars":{"type":"number"},"skillsShLeaderboardRank":{"type":"number"},"numberOfAggregators":{"type":"number"},"officialClaudeMarketplace":{"type":"boolean"}},"additionalProperties":false},"SearchRequestBody":{"type":"object","description":"See docs/search-beta-api-spec.md for full filter semantics. `assetType` (default \"skill\") picks the collection. For skills, in real mode (default): languages/agentCompatibility/sources filter the agent_skills catalog server-side, rankFilters applies per-source search-rank ceilings, and rankings.stars maps to the catalog's min_stars — all real push-downs. rankings.skillsShLeaderboardRank/numberOfAggregators/officialClaudeMarketplace are accepted and validated but NOT applied (no catalog hook, though the response fields are real). A skill with no catalog match is excluded whenever any real filter is set. In mock mode (SEARCH_BETA_MOCK_DATA=true) none of these filter anything. For assetType \"mcp\", the mcpCategory/deployment/registryType/sources/rankings.stars filters are pushed to the mcp_servers catalog and languages/agentCompatibility/rankFilters are ignored; the response is McpSearchListResponse (McpHit-shaped, no Postgres spine, no mock).","properties":{"search":{"type":"string","default":""},"page":{"type":"integer","minimum":1,"default":1},"sort":{"type":"string","enum":["newest","rating","alpha"],"default":"newest"},"reverse":{"type":"boolean","default":false},"assetType":{"type":"string","enum":["skill","mcp"],"default":"skill","description":"Which catalog to search. \"skill\" (default): agent_skills, relevance-ranked, joined per-skill to the public Postgres directory, response DirectorySearchListResponse. \"mcp\": mcp_servers, a thin fail-open proxy of /query {asset_type:\"mcp\"}, response McpSearchListResponse."},"languages":{"type":"array","items":{"type":"string"},"default":[],"description":"skill only: filters on spoken/content language of the SKILL.md (e.g. \"en\", \"ja-JP\"), not programming language. Real-mode push-down."},"agentCompatibility":{"type":"array","items":{"type":"string"},"default":[],"description":"skill only: filters on real declared/inferred agent runtime targets (e.g. claude-code, cursor). Real-mode push-down."},"sources":{"type":"array","items":{"type":"string"},"default":[],"description":"Discovery channels (marketplace/seed/search/manual). Real push-down for both skill and mcp (openapi.json QueryRequest.sources)."},"rankFilters":{"type":"object","additionalProperties":{"type":"number"},"default":{},"description":"skill only: `{search_rank_<source>_<metric>: maxRank}` ceilings — real push-down (openapi.json QueryRequest.rank_filters). Ignored for mcp."},"mcpCategory":{"type":"array","items":{"type":"string"},"default":[],"description":"mcp only: server/client/framework/tooling. Real push-down. Ignored for skill."},"deployment":{"type":"array","items":{"type":"string"},"default":[],"description":"mcp only: local/remote/hybrid. Real push-down. Ignored for skill."},"registryType":{"type":"array","items":{"type":"string"},"default":[],"description":"mcp only: npm/pypi/oci/etc. Real push-down. Ignored for skill."},"rankings":{"oneOf":[{"$ref":"#/components/schemas/RankingsFilter"},{"type":"null"}],"default":null,"description":"Only rankings.stars is applied as a real filter (skill: min_stars; mcp: min_stars); the other sub-fields are accepted but not applied."}},"additionalProperties":false},"McpDirectoryHit":{"type":"object","description":"One mcp_servers catalog hit, forwarded verbatim from the query service (openapi.json McpHit). No vettd-side enrichment — the mcp_servers Qdrant catalog has no join key back to vettd's own McpServer rows. All `security_*` / `security_direct_deps_*` fields are the OSV dependency-vulnerability scan (ah-skills fetch_mcp_security.py). `required` here must track openapi.json McpHit.required (all fields required, nullable where marked) — same silent-drift risk as AgentSkillsHit; the owed L4a check (AgentMcpHit ⊆ McpHit) is the guard.","properties":{"score":{"type":["number","null"]},"rank":{"type":"integer"},"mcp_id":{"type":"string","description":"e.g. github:owner/repo"},"name":{"type":"string"},"description":{"type":"string"},"readme":{"type":"string"},"repo_url":{"type":["string","null"]},"status":{"type":"string"},"mcp_category":{"type":["string","null"],"description":"server | client | framework | tooling"},"mcp_category_source":{"type":["string","null"]},"sources":{"type":"array","items":{"type":"string"}},"registry_type":{"type":["string","null"],"description":"npm | pypi | oci | …"},"package_identifier":{"type":["string","null"]},"package_url":{"type":["string","null"]},"deployment":{"type":["string","null"],"description":"local | remote | hybrid"},"transport":{"type":["string","null"],"description":"stdio | sse | http | …"},"has_installable_package":{"type":"boolean"},"has_remote":{"type":"boolean"},"attributes":{"type":"array","items":{"type":"string"}},"license":{"type":["string","null"]},"added":{"type":["string","null"]},"stars":{"type":["integer","null"]},"language":{"type":["string","null"]},"weekly_downloads":{"type":["integer","null"]},"monthly_downloads":{"type":["integer","null"]},"security_source":{"type":["string","null"]},"security_vuln_count":{"type":["integer","null"]},"security_vuln_ids":{"type":["array","null"],"items":{"type":"string"}},"security_max_severity":{"type":["string","null"]},"security_direct_deps_scanned":{"type":["integer","null"]},"security_direct_deps_vuln_count":{"type":["integer","null"]},"security_direct_deps_with_vulns":{"type":["array","null"],"items":{"type":"string"}},"security_direct_deps_max_severity":{"type":["string","null"]},"security_direct_deps_vuln_ids":{"type":["array","null"],"items":{"type":"string"}}},"required":["score","rank","mcp_id","name","description","readme","repo_url","status","mcp_category","mcp_category_source","sources","registry_type","package_identifier","package_url","deployment","transport","has_installable_package","has_remote","attributes","license","added","stars","language","weekly_downloads","monthly_downloads","security_source","security_vuln_count","security_vuln_ids","security_max_severity","security_direct_deps_scanned","security_direct_deps_vuln_count","security_direct_deps_with_vulns","security_direct_deps_max_severity","security_direct_deps_vuln_ids"],"additionalProperties":true},"McpSearchListResponse":{"type":"object","description":"Response to POST /api/directory when the request body's assetType is \"mcp\". A thin fail-open proxy of the mcp_servers catalog — no Postgres spine, no mock mode. `mcpServers` is paged client-side (page size 20) from the query service's returned hits; `total`/`totalPages` reflect that returned set, not the whole catalog.","properties":{"mcpServers":{"type":"array","items":{"$ref":"#/components/schemas/McpDirectoryHit"}},"total":{"type":"integer","minimum":0},"page":{"type":"integer","minimum":1},"totalPages":{"type":"integer","minimum":0},"mock":{"type":"boolean","enum":[false],"description":"Always false — nothing to fabricate for an external catalog."},"indexReady":{"type":"boolean","description":"false when the mcp_servers collection is empty/absent or the query service is unreachable (onboarding/outage) — distinct from an empty result set for a valid query."}},"required":["mcpServers","total","page","totalPages","mock","indexReady"],"additionalProperties":false,"example":{"mcpServers":[{"score":0.75,"rank":1,"mcp_id":"github:example-org/context7","name":"io.github.example-org/context7","description":"A Model Context Protocol server that fetches up-to-date, version-specific documentation and code examples from libraries directly into LLM prompts.","readme":"# Context7 Platform - Up-to-date Code Docs For Any Prompt\n\n<full README markdown -- truncated in this example>","repo_url":"https://github.com/example-org/context7","status":"active","mcp_category":"server","mcp_category_source":"rule","sources":["repo_scan","official_registry","glama"],"registry_type":"npm","package_identifier":"@example-org/context7-mcp","package_url":"https://www.npmjs.com/package/@example-org/context7-mcp","deployment":"hybrid","transport":"stdio","has_installable_package":true,"has_remote":true,"attributes":["hosting:remote-capable"],"license":"MIT License","added":"2026-08-17","stars":61421,"language":"TypeScript","weekly_downloads":867314,"monthly_downloads":3729481,"security_source":"osv","security_vuln_count":0,"security_vuln_ids":[],"security_max_severity":null,"security_direct_deps_scanned":8,"security_direct_deps_vuln_count":44,"security_direct_deps_with_vulns":["zod","jose","undici","express"],"security_direct_deps_max_severity":"HIGH","security_direct_deps_vuln_ids":["GHSA-wqq4-5wpv-mx2g","GHSA-m4v8-wqvr-p9f7","GHSA-qw6h-vgh9-j6wx","... (44 advisory ids total -- truncated in this example)"]}],"total":1,"page":1,"totalPages":1,"mock":false,"indexReady":true}}}},"paths":{"/api/skills":{"get":{"tags":["Skills"],"summary":"List the current user's recent skill audits","security":[{"SessionCookieAuth":[]}],"x-vettd-rate-limit":"unmetered","responses":{"200":{"description":"Recent skill audits","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/SkillAuditSummary"}}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}},"post":{"tags":["Skills"],"summary":"Upload a zip file and create a new skill audit","description":"Session-authenticated endpoint for zip-based skill analysis.","security":[{"SessionCookieAuth":[]}],"x-vettd-rate-limit":"skills-upload","requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"type":"object","properties":{"file":{"type":"string","format":"binary","description":"Zip file containing a skill package"}},"required":["file"]}}}},"responses":{"201":{"description":"Created skill audit","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreatedSkillAudit"}}}},"400":{"description":"Invalid upload","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Unprocessable skill package","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/skills/github":{"post":{"tags":["Skills"],"summary":"Import a GitHub skill and create a new audit","description":"Accepts either a GitHub URL or repo/branch/path fields. Supports both browser sessions and Vettd API keys. Public GitHub repositories are automatically listed in the public directory; private repositories remain private unless later published explicitly.","security":[{"SessionCookieAuth":[]},{"ApiKeyAuth":[]}],"x-vettd-rate-limit":"skills-github","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GitHubSkillImportRequest"}}}},"responses":{"200":{"description":"Existing latest audit reused because the GitHub tree SHA is unchanged","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreatedSkillAudit"}}}},"201":{"description":"Created skill audit from GitHub source","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreatedSkillAudit"}}}},"400":{"description":"Invalid request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized or missing GitHub token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Repository or skill path could not be analyzed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/skills/{id}":{"get":{"tags":["Skills"],"summary":"Fetch one skill audit in detail or as a deterministic report","security":[{"SessionCookieAuth":[]},{"ApiKeyAuth":[]}],"x-vettd-rate-limit":"skill-detail","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"format","in":"query","required":false,"schema":{"type":"string","enum":["report"]},"description":"When set to report, returns the deterministic report payload instead of the interactive audit detail payload."}],"responses":{"200":{"description":"Skill audit detail or report","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/SkillAuditDetail"},{"$ref":"#/components/schemas/SkillAuditReport"}]}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Audit not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}},"patch":{"tags":["Skills"],"summary":"Publish or unpublish a skill audit in the Vettd directory","description":"Updates publication metadata for an existing completed audit. This does not mutate the underlying analysis findings. Publishing a GitHub-backed audit from a private repository requires confirmPrivateSourcePublicListing=true. category, frameworks, and slug are required only when publishing (visibility=public); unpublishing requires only visibility=private.","security":[{"SessionCookieAuth":[]}],"x-vettd-rate-limit":"skills-publish","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublishSkillInput"}}}},"responses":{"200":{"description":"Updated publication metadata","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string"},"visibility":{"type":"string","enum":["private","public"]},"category":{"type":"string","enum":["Search","Infrastructure","Data","Communication","Database","Security","Automation","Monitoring","Testing","Development","AI/ML","Other"]},"frameworks":{"type":"array","items":{"type":"string","enum":["OWASP","NIST 800-53","CMMC","ISO 42001","EU AI Act","CISA"]}},"slug":{"type":["string","null"]},"publicId":{"type":["string","null"]},"publishedAt":{"type":["string","null"],"format":"date-time"}},"required":["id","visibility","category","frameworks"],"additionalProperties":true}}}},"400":{"description":"Validation failed or audit not publishable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Audit not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"GitHub repository visibility could not be verified","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/skills/{id}/reanalyze":{"post":{"tags":["Skills"],"summary":"Queue a new revision of a GitHub-backed skill audit","description":"Session-authenticated only. Returns 202 immediately with a batch receipt; the reanalysis runs asynchronously as one or more durable scan jobs. Clients poll GET /api/scans/batch/{batchId} for status. The batch may complete as reused when the GitHub tree SHA is unchanged.","security":[{"SessionCookieAuth":[]}],"x-vettd-rate-limit":"skills-reanalyze","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"202":{"description":"Reanalysis accepted and queued as a durable batch","content":{"application/json":{"schema":{"type":"object","required":["batchId"],"properties":{"batchId":{"type":"string"}}}}}},"400":{"description":"Audit is not GitHub-backed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized or missing GitHub token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Audit not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Remote skill could not be analyzed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/scans/batch":{"post":{"tags":["Batch scans"],"summary":"Enqueue a bulk scan batch","description":"Session-authenticated endpoint for enqueueing up to MAX_BULK_SELECTION GitHub reimport targets as a single durable batch. Returns 202 immediately; the caller polls GET /api/scans/batch/{batchId} for completion status.","security":[{"SessionCookieAuth":[]}],"x-vettd-rate-limit":"scans-batch-post","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BatchRequestBody"}}}},"responses":{"202":{"description":"Batch accepted and enqueued","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BatchEnqueueResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/schemas/RateLimitHeaders/properties/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/schemas/RateLimitHeaders/properties/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/schemas/RateLimitHeaders/properties/X-RateLimit-Reset"}}},"400":{"description":"Invalid request body or validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized (requires browser session)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limited (IP backstop or per-user)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/schemas/RateLimitHeaders/properties/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/schemas/RateLimitHeaders/properties/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/schemas/RateLimitHeaders/properties/X-RateLimit-Reset"}}}}}},"/api/scans/batch/{id}":{"get":{"tags":["Batch scans"],"summary":"Fetch the status of a scan batch by ID","description":"Session-authenticated. Poll this endpoint to track batch progress; the response reflects only jobs owned by the authenticated user. Clients should honor X-RateLimit-* headers when pacing the polling loop.","security":[{"SessionCookieAuth":[]}],"x-vettd-rate-limit":"scans-batch-get","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Batch ID returned by POST /api/scans/batch"}],"responses":{"200":{"description":"Batch status","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BatchStatusResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/schemas/RateLimitHeaders/properties/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/schemas/RateLimitHeaders/properties/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/schemas/RateLimitHeaders/properties/X-RateLimit-Reset"}}},"401":{"description":"Unauthorized (requires browser session)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/schemas/RateLimitHeaders/properties/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/schemas/RateLimitHeaders/properties/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/schemas/RateLimitHeaders/properties/X-RateLimit-Reset"}}},"404":{"description":"Batch not found or not owned by caller","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/schemas/RateLimitHeaders/properties/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/schemas/RateLimitHeaders/properties/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/schemas/RateLimitHeaders/properties/X-RateLimit-Reset"}}},"429":{"description":"Rate limited (IP backstop or per-user)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/schemas/RateLimitHeaders/properties/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/schemas/RateLimitHeaders/properties/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/schemas/RateLimitHeaders/properties/X-RateLimit-Reset"}}}}}},"/api/scans/batch/{id}/cancel":{"post":{"tags":["Batch scans"],"summary":"Cancel a running scan batch","description":"Session-authenticated. Cancels the batch identified by {id} and returns the updated status. Idempotent: cancelling an already-completed or already-cancelled batch returns 404.","security":[{"SessionCookieAuth":[]}],"x-vettd-rate-limit":"scans-batch-cancel","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Batch ID to cancel"}],"responses":{"200":{"description":"Batch cancelled; returns updated status","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BatchStatusResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/schemas/RateLimitHeaders/properties/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/schemas/RateLimitHeaders/properties/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/schemas/RateLimitHeaders/properties/X-RateLimit-Reset"}}},"401":{"description":"Unauthorized (requires browser session)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/schemas/RateLimitHeaders/properties/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/schemas/RateLimitHeaders/properties/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/schemas/RateLimitHeaders/properties/X-RateLimit-Reset"}}},"404":{"description":"Batch not found or not owned by caller","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/schemas/RateLimitHeaders/properties/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/schemas/RateLimitHeaders/properties/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/schemas/RateLimitHeaders/properties/X-RateLimit-Reset"}}},"429":{"description":"Rate limited (IP backstop or per-user)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"X-RateLimit-Limit":{"$ref":"#/components/schemas/RateLimitHeaders/properties/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/schemas/RateLimitHeaders/properties/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/schemas/RateLimitHeaders/properties/X-RateLimit-Reset"}}}}}},"/api/skills/lookup":{"get":{"tags":["Public directory"],"summary":"Look up a published skill's verdict by source URL","description":"Keyless trust lookup. A known skill returns its grade, summary, finding counts, and canonical detail URL. An unassessed skill returns HTTP 200 with status \"unknown\"; the response documents the next step for getting it assessed rather than failing.","security":[],"x-vettd-rate-limit":"skills-lookup","parameters":[{"name":"url","in":"query","required":true,"schema":{"type":"string","format":"uri"},"description":"Source URL of the skill (e.g. a GitHub tree URL)"}],"responses":{"200":{"description":"Lookup result. status is the skill's overall grade for an assessed skill, or \"unknown\" when the URL has not been assessed yet.","headers":{"X-RateLimit-Limit":{"schema":{"type":"string"},"description":"Requests allowed in the current window"},"X-RateLimit-Remaining":{"schema":{"type":"string"},"description":"Requests remaining in the current window"},"X-RateLimit-Reset":{"schema":{"type":"string"},"description":"Seconds until the window resets"}},"content":{"application/json":{"schema":{"oneOf":[{"type":"object","description":"Assessed skill","properties":{"status":{"type":"string","description":"Overall grade value"},"severity":{"type":"string","enum":["low","medium","high","critical"]},"name":{"type":"string"},"summary":{"type":"string","description":"Generated security summary"},"grade":{"type":"string"},"findingCounts":{"type":"object","properties":{"critical":{"type":"integer"},"high":{"type":"integer"},"medium":{"type":"integer"},"low":{"type":"integer"},"info":{"type":"integer"}},"required":["critical","high","medium","low","info"]},"assessedAt":{"type":["string","null"],"format":"date-time"},"detailUrl":{"type":"string","format":"uri"}},"required":["status","severity","name","grade","findingCounts","detailUrl"]},{"type":"object","description":"Unassessed skill: carries the recovery operation","properties":{"status":{"type":"string","enum":["unknown"]},"url":{"type":"string","format":"uri"},"message":{"type":"string"},"next":{"allOf":[{"$ref":"#/components/schemas/NextOperation"},{"type":"object","properties":{"method":{"enum":["POST"]}},"required":["requires"]}]},"documentation":{"type":"string","format":"uri"}},"required":["status","url","message","next","documentation"]}]}}}},"400":{"description":"Missing or invalid url parameter","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Rate limit exceeded","headers":{"Retry-After":{"schema":{"type":"string"},"description":"Seconds to wait before retrying"}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"500":{"description":"Internal error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/directory":{"get":{"tags":["Public directory"],"summary":"Search published skills in the public directory","description":"Keyword search with filters, sorting, and pagination. Matching is lexical today; the search parameter's contract is reserved for lexical matching so a semantic mode can be added without a breaking change. assetType values other than \"skill\" are accepted by the schema but currently served by the multi-kind feed rather than this endpoint. sort=stars is accepted by the schema but served by the feed endpoint.","security":[],"x-vettd-rate-limit":"directory","parameters":[{"name":"search","in":"query","schema":{"type":"string"},"description":"Keyword query"},{"name":"assetType","in":"query","schema":{"type":"string","enum":["repository","prompt","skill","mcp_server","agent","agentic_app"]}},{"name":"category","in":"query","schema":{"type":"string"}},{"name":"framework","in":"query","schema":{"type":"string"}},{"name":"badgeStatus","in":"query","schema":{"type":"string","enum":["vettd","verified","conditional","flagged","pending"]}},{"name":"grade","in":"query","schema":{"type":"string","enum":["A","B","C","F"]}},{"name":"sort","in":"query","schema":{"type":"string","enum":["newest","verdict","alpha","downloads"],"default":"newest"}},{"name":"dir","in":"query","schema":{"type":"string","enum":["asc","desc"],"default":"desc"}},{"name":"page","in":"query","schema":{"type":"integer","minimum":1,"maximum":1000,"default":1}},{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":100,"default":20}}],"responses":{"200":{"description":"Paginated skill cards","content":{"application/json":{"schema":{"type":"object","properties":{"skills":{"type":"array","items":{"type":"object","additionalProperties":true}},"total":{"type":"integer"},"page":{"type":"integer"},"totalPages":{"type":"integer"},"next":{"$ref":"#/components/schemas/NextOperation"},"documentation":{"type":"string","format":"uri"}},"required":["skills","total","page","totalPages"]}}}},"400":{"description":"Invalid filters","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Rate limit exceeded","headers":{"Retry-After":{"schema":{"type":"string"},"description":"Seconds to wait before retrying"}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"500":{"description":"Internal error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}},"post":{"tags":["Public directory"],"summary":"Beta search over the public directory — skills or MCP servers (SEARCH_BETA_TESTING)","description":"404 when SEARCH_BETA_TESTING is unset/false on this deployment. assetType \"skill\" (default): a non-empty `search` runs a relevance-first query over the agent_skills catalog (top 200 hits, request filters pushed down), each hit joined PER-SKILL to a public directory audit — result set + order are the catalog's, `sort`/`reverse` are ignored, and `total`/`totalPages` count matched audits within those 200 hits (not the whole corpus). Hits with no public audit are omitted. Response DirectorySearchListResponse. An empty `search`, or SEARCH_BETA_MOCK_DATA=true, or an unreachable query service falls back to the Postgres-first directory page (browse order / fabricated fields / honest-empty catalog fields respectively). assetType \"mcp\": a thin fail-open proxy of the mcp_servers catalog, response McpSearchListResponse (no Postgres spine, no mock).","security":[],"x-vettd-rate-limit":"directory","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SearchRequestBody"}}}},"responses":{"200":{"description":"Search results — DirectorySearchListResponse for assetType \"skill\", McpSearchListResponse for assetType \"mcp\".","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/DirectorySearchListResponse"},{"$ref":"#/components/schemas/McpSearchListResponse"}]},"examples":{"skill":{"summary":"assetType: \"skill\" — a directory skill card with all three catalog scan verdicts","value":{"skills":[{"id":"cma8s0f2h000abcde12345678","slug":"e2e-testing","name":"e2e-testing","description":"Drives end-to-end browser tests with Playwright: installs the runner, generates specs from a user story, runs them headless with retries, and triages failures.","version":"1.4.0","author":"example-org","category":"Testing","frameworks":[],"badgeStatus":"verified","overallGrade":"B","downloadCount":5120,"publishedAt":"2026-08-13T18:42:07.000Z","sourceType":"github","sourceUrl":"https://github.com/example-org/skills-repo/tree/main/skills/e2e-testing","docLanguage":"en","agentCompatibility":["claude-code"],"rankings":{"stars":240095,"skillsShLeaderboardRank":142,"numberOfAggregators":2,"officialClaudeMarketplace":true},"llm_scan":{"model":"openrouter/deepseek/deepseek-v3.2","prompt_version":"37243f9d5700","scanned_at":"2026-08-30T21:41:26.512874+00:00","content_sha256":"b0e00e7e17cb259101139900816c5528aed18dd10bcf5f9cb42cfc35baf8a755","max_severity":"LOW","finding_count":1,"primary_threats":["unpinned-dependency-install"],"overall_assessment":"Benign testing skill. One low-severity note: instructs the agent to install the Playwright CLI globally at an unpinned version.","findings":[{"severity":"LOW","aitech":"AITech-4.3","aisubtech":"AITech-4.3.2","title":"Unpinned global CLI install","description":"SKILL.md instructs `npm install -g playwright` with no version constraint.","location":"SKILL.md, 'Setup' section","evidence":"npm install -g playwright && npx playwright install chromium","remediation":"Pin the version, e.g. `npm install -g playwright@1.55.0`."}]},"cli_security":{"grade":"C","scanned_at":"2026-08-30T21:40:47.315190+00:00","osv_snapshot_date":"2026-08-30","packages":[{"package":"playwright","ecosystem":"npm","classification":"cli","install_command":"npx playwright test tests/search.spec.ts --repeat-each=10","vuln_count":1,"max_severity":"HIGH","advisory_ids":["GHSA-7mvr-c777-76hp"]}]},"vettd_scan":{"scan_id":"scn_01J9Z7Q3K8V2M4N6P8R0T2W4Y6","overall_grade":"B","trust_level":"cautious","has_malicious_findings":false,"finding_count":4,"severity_counts":{"critical":0,"high":0,"medium":1,"low":2,"info":1},"categories_flagged":["scripts","best-practices"],"top_findings":[{"rule_id":"shell-exec-unpinned-install","category":"scripts","severity":"medium","label":"SKILL.md tells the agent to run 'npx playwright install' without a pinned version"}]}}],"total":1,"page":1,"totalPages":1,"mock":false}},"mcp":{"summary":"assetType: \"mcp\" — an MCP server with OSV dependency security findings","value":{"mcpServers":[{"score":0.75,"rank":1,"mcp_id":"github:example-org/context7","name":"io.github.example-org/context7","description":"A Model Context Protocol server that fetches up-to-date, version-specific documentation and code examples from libraries directly into LLM prompts.","readme":"# Context7 Platform - Up-to-date Code Docs For Any Prompt\n\n<full README markdown -- truncated in this example>","repo_url":"https://github.com/example-org/context7","status":"active","mcp_category":"server","mcp_category_source":"rule","sources":["repo_scan","official_registry","glama"],"registry_type":"npm","package_identifier":"@example-org/context7-mcp","package_url":"https://www.npmjs.com/package/@example-org/context7-mcp","deployment":"hybrid","transport":"stdio","has_installable_package":true,"has_remote":true,"attributes":["hosting:remote-capable"],"license":"MIT License","added":"2026-08-17","stars":61421,"language":"TypeScript","weekly_downloads":867314,"monthly_downloads":3729481,"security_source":"osv","security_vuln_count":0,"security_vuln_ids":[],"security_max_severity":null,"security_direct_deps_scanned":8,"security_direct_deps_vuln_count":44,"security_direct_deps_with_vulns":["zod","jose","undici","express"],"security_direct_deps_max_severity":"HIGH","security_direct_deps_vuln_ids":["GHSA-wqq4-5wpv-mx2g","GHSA-m4v8-wqvr-p9f7","GHSA-qw6h-vgh9-j6wx","... (44 advisory ids total -- truncated in this example)"]}],"total":1,"page":1,"totalPages":1,"mock":false,"indexReady":true}}}}}},"400":{"description":"Invalid JSON body or search request","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"SEARCH_BETA_TESTING disabled on this deployment","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Rate limit exceeded","headers":{"Retry-After":{"schema":{"type":"string"},"description":"Seconds to wait before retrying"}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"500":{"description":"Internal error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/directory/feed":{"get":{"tags":["Public directory"],"summary":"Search the multi-kind public directory feed","description":"Kind-tagged items across skills, repositories, prompts, MCP servers, agents, and agentic apps, with per-kind totals; the same feed the HTML directory renders. Accepts the same filters as /api/directory.","security":[],"x-vettd-rate-limit":"directory-feed","parameters":[{"name":"search","in":"query","schema":{"type":"string"},"description":"Keyword query"},{"name":"assetType","in":"query","schema":{"type":"string","enum":["repository","prompt","skill","mcp_server","agent","agentic_app"]}},{"name":"sort","in":"query","schema":{"type":"string","enum":["newest","stars","downloads","verdict","alpha"],"default":"newest"},"description":"stars ranks by the record's repository's GitHub stars, most first; records with no reading follow"},{"name":"dir","in":"query","schema":{"type":"string","enum":["asc","desc"],"default":"desc"}},{"name":"page","in":"query","schema":{"type":"integer","minimum":1,"maximum":1000,"default":1}},{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":100,"default":20}}],"responses":{"200":{"description":"Kind-tagged feed items with per-kind totals","content":{"application/json":{"schema":{"type":"object","properties":{"items":{"type":"array","items":{"type":"object","additionalProperties":true}},"total":{"type":"integer"},"page":{"type":"integer"},"totalPages":{"type":"integer"},"skillTotal":{"type":"integer"},"repositoryTotal":{"type":"integer"},"assetTotal":{"type":"integer"}},"required":["items","total","page","totalPages"]}}}},"400":{"description":"Invalid filters","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Rate limit exceeded","headers":{"Retry-After":{"schema":{"type":"string"},"description":"Seconds to wait before retrying"}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"500":{"description":"Internal error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/directory/assets/{kind}/{id}":{"get":{"tags":["Public directory"],"summary":"Get public detail for a prompt, MCP server, agent, or agentic app","description":"Keyless detail for the non-skill, non-repo asset kinds, over the same record the public /assets pages render.","security":[],"x-vettd-rate-limit":"asset-detail","parameters":[{"name":"kind","in":"path","required":true,"schema":{"type":"string","enum":["prompts","mcp-servers","agents","agentic-apps"]}},{"name":"id","in":"path","required":true,"schema":{"type":"string","pattern":"^[A-Za-z0-9_.-]{1,128}$"}}],"responses":{"200":{"description":"Public asset detail","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"404":{"description":"Unknown kind segment, or no public asset matches","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Rate limit exceeded","headers":{"Retry-After":{"schema":{"type":"string"},"description":"Seconds to wait before retrying"}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"500":{"description":"Internal error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/directory/compare":{"get":{"tags":["Public directory"],"summary":"Compare 2-8 published skills side by side","description":"Machine-readable comparison of published trust evidence (verdicts, findings, scanner corroboration) for two to eight skills identified by publicId. Deliberately not a quality ranking; the calling agent judges fit.","security":[],"x-vettd-rate-limit":"directory","parameters":[{"name":"skills","in":"query","required":true,"schema":{"type":"string","maxLength":512},"description":"Comma-separated list of 2-8 distinct skill publicIds"}],"responses":{"200":{"description":"Comparison document","content":{"application/json":{"schema":{"type":"object","properties":{"missing":{"type":"array","items":{"type":"string"}},"unknown":{"type":"array","items":{"$ref":"#/components/schemas/UnknownSkillSlot"}}},"additionalProperties":true}}}},"400":{"description":"Malformed query, or fewer than 2 / more than 8 distinct identifiers","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"Too few of the requested skills are published to compare; the body's unknown array names each unresolved identifier with a next pointer into directory search","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Rate limit exceeded","headers":{"Retry-After":{"schema":{"type":"string"},"description":"Seconds to wait before retrying"}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"500":{"description":"Internal error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/directory/{slug}":{"get":{"tags":["Public directory"],"summary":"Get a published skill's public detail record","description":"Resolves by canonical publicId first, then by legacy slug. Legacy slugs are non-unique and cosmetic; publicId is the stable key.","security":[],"x-vettd-rate-limit":"directory-slug","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","pattern":"^[A-Za-z0-9_.-]{1,128}$"},"description":"Skill publicId (canonical) or legacy slug"}],"responses":{"200":{"description":"Public skill detail (findings, scanner runs, freshness)","content":{"application/json":{"schema":{"type":"object","properties":{"publisherSupplied":{"type":"array","items":{"type":"string"},"const":["name","description","author","version","license"],"description":"Field names on this body that are publisher-supplied and not verified by Vettd (#949)."}},"additionalProperties":true}}}},"404":{"description":"No published skill matches","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Rate limit exceeded","headers":{"Retry-After":{"schema":{"type":"string"},"description":"Seconds to wait before retrying"}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"500":{"description":"Internal error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/directory/stats":{"get":{"tags":["Public directory"],"summary":"Aggregate counts for the public directory","security":[],"x-vettd-rate-limit":"directory-stats","responses":{"200":{"description":"Directory-wide counts by badge status","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"429":{"description":"Rate limit exceeded","headers":{"Retry-After":{"schema":{"type":"string"},"description":"Seconds to wait before retrying"}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"500":{"description":"Internal error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/directory/random":{"get":{"tags":["Public directory"],"summary":"Get one random published skill card","security":[],"x-vettd-rate-limit":"directory-random","responses":{"200":{"description":"A random published skill, or null when the directory is empty","content":{"application/json":{"schema":{"type":"object","properties":{"skill":{"type":["object","null"],"additionalProperties":true}},"required":["skill"]}}}},"429":{"description":"Rate limit exceeded","headers":{"Retry-After":{"schema":{"type":"string"},"description":"Seconds to wait before retrying"}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"500":{"description":"Internal error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/reports/{owner}/{repo}":{"get":{"tags":["Public directory"],"summary":"Get a repository's public scan report","description":"Occurrence and certification report for a repository whose report visibility is public. Distinct from the repo skill list served by the /repos pages.","security":[],"x-vettd-rate-limit":"reports","parameters":[{"name":"owner","in":"path","required":true,"schema":{"type":"string","pattern":"^[A-Za-z0-9_.-]{1,100}$"}},{"name":"repo","in":"path","required":true,"schema":{"type":"string","pattern":"^[A-Za-z0-9_.-]{1,100}$"}}],"responses":{"200":{"description":"Public report (occurrences, latest snapshot, certification)","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"404":{"description":"No public report for this repository","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Rate limit exceeded","headers":{"Retry-After":{"schema":{"type":"string"},"description":"Seconds to wait before retrying"}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"500":{"description":"Internal error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/repos/{owner}/{repo}":{"get":{"tags":["Public directory"],"summary":"Get a repository's public details including its skill list","description":"Keyless repo detail: the published skill audits with grades, findings, and freshness: the record behind the public /repos page. Distinct from /api/reports/{owner}/{repo}, which serves the occurrence/certification report.","security":[],"x-vettd-rate-limit":"repos-detail","parameters":[{"name":"owner","in":"path","required":true,"schema":{"type":"string","pattern":"^[A-Za-z0-9_.-]{1,100}$"}},{"name":"repo","in":"path","required":true,"schema":{"type":"string","pattern":"^[A-Za-z0-9_.-]{1,100}$"}}],"responses":{"200":{"description":"Repository details with the published skill list","content":{"application/json":{"schema":{"type":"object","properties":{"publisherSupplied":{"type":"array","items":{"type":"string"},"const":["skills[].name","skills[].description","skills[].author","skills[].version","skills[].license"],"description":"Field names on this body that are publisher-supplied and not verified by Vettd (#949)."}},"additionalProperties":true}}}},"404":{"description":"No public repository matches","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Rate limit exceeded","headers":{"Retry-After":{"schema":{"type":"string"},"description":"Seconds to wait before retrying"}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"500":{"description":"Internal error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/contract":{"get":{"tags":["Public directory"],"summary":"Get the scanner data contract","description":"Machine-readable contract consumed by vettd-cli. Pass version=true for just the version string.","security":[],"x-vettd-rate-limit":"contract","parameters":[{"name":"version","in":"query","schema":{"type":"string","enum":["true"]},"description":"When \"true\", return only {version}"}],"responses":{"200":{"description":"Full contract document, or {version} when version=true","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"429":{"description":"Rate limit exceeded","headers":{"Retry-After":{"schema":{"type":"string"},"description":"Seconds to wait before retrying"}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"500":{"description":"Internal error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/health":{"get":{"tags":["Public directory"],"summary":"Liveness probe","description":"Deliberately unmetered so infrastructure probes can never be rate limited.","security":[],"x-vettd-rate-limit":"unmetered","responses":{"200":{"description":"Service is up","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","enum":["ok"]}},"required":["status"]}}}}}}},"/api/openapi.json":{"get":{"tags":["Public directory"],"summary":"This OpenAPI document","security":[],"x-vettd-rate-limit":"openapi","responses":{"200":{"description":"The canonical machine-readable API contract","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"429":{"description":"Rate limit exceeded","headers":{"Retry-After":{"schema":{"type":"string"},"description":"Seconds to wait before retrying"}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/agent.md":{"get":{"tags":["Public directory"],"summary":"Agent guide as raw markdown","description":"One-page usage guide for HTTPS-only agents: keyless operations, auth model, rate limits, and recovery semantics. Generated from this document and the rate-limit policy table at request time, so it cannot drift from either.","security":[],"x-vettd-rate-limit":"agent-guide","responses":{"200":{"description":"The guide","content":{"text/markdown":{"schema":{"type":"string"}}}},"429":{"description":"Rate limit exceeded","headers":{"Retry-After":{"schema":{"type":"string"},"description":"Seconds to wait before retrying"}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/skill/{publicId}/report.md":{"get":{"tags":["Public directory"],"summary":"Skill report as markdown","description":"The skill report rendered as text/markdown from the same record as the HTML page: Vettd assessment, provenance, and explicitly labeled publisher-supplied metadata. Legacy slugs 301 to the canonical publicId URL.","security":[],"x-vettd-rate-limit":"skill-report-md","parameters":[{"name":"publicId","in":"path","required":true,"schema":{"type":"string","pattern":"^[A-Za-z0-9_.-]{1,128}$"}}],"responses":{"200":{"description":"The report","content":{"text/markdown":{"schema":{"type":"string"}}}},"301":{"description":"Legacy slug; redirects to the canonical publicId URL"},"404":{"description":"No published skill matches","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Rate limit exceeded","headers":{"Retry-After":{"schema":{"type":"string"},"description":"Seconds to wait before retrying"}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"500":{"description":"Internal error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/repos/{owner}/{repo}/report.md":{"get":{"tags":["Public directory"],"summary":"Repository report as markdown","description":"The repository report rendered as text/markdown: published skill list, certification, and snapshot provenance from the same records as the HTML page and the JSON report.","security":[],"x-vettd-rate-limit":"repo-report-md","parameters":[{"name":"owner","in":"path","required":true,"schema":{"type":"string","pattern":"^[A-Za-z0-9_.-]{1,100}$"}},{"name":"repo","in":"path","required":true,"schema":{"type":"string","pattern":"^[A-Za-z0-9_.-]{1,100}$"}}],"responses":{"200":{"description":"The report","content":{"text/markdown":{"schema":{"type":"string"}}}},"404":{"description":"No public repository matches","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Rate limit exceeded","headers":{"Retry-After":{"schema":{"type":"string"},"description":"Seconds to wait before retrying"}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"500":{"description":"Internal error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/badge/{owner}/{repo}":{"get":{"tags":["Public directory"],"summary":"SVG certification badge for a repository","description":"Embeddable badge. A .svg suffix on the repo segment is accepted and stripped. Private or unknown repositories always render the gray unscanned badge so status never leaks.","security":[],"x-vettd-rate-limit":"badge","parameters":[{"name":"owner","in":"path","required":true,"schema":{"type":"string","pattern":"^[A-Za-z0-9_.-]{1,100}$"}},{"name":"repo","in":"path","required":true,"schema":{"type":"string","pattern":"^[A-Za-z0-9_.-]{1,100}$"}}],"responses":{"200":{"description":"Badge image","content":{"image/svg+xml":{"schema":{"type":"string"}}}},"429":{"description":"Rate limit exceeded","headers":{"Retry-After":{"schema":{"type":"string"},"description":"Seconds to wait before retrying"}},"content":{"text/plain":{"schema":{"type":"string"}}}},"500":{"description":"Internal error","content":{"text/plain":{"schema":{"type":"string"}}}}}}}}}