{"openapi":"3.1.0","info":{"title":"Install Safe public API","version":"1.0.0","description":"A drop-in npm registry proxy that strips vulnerable and malicious package versions from npm metadata in real time. One config line. Zero workflow changes.\n\nThis document covers the endpoints that are public by design: the free dependency scanner and the health check. The registry proxy itself speaks the npm registry protocol and is configured with `npm config set registry`, not called directly — see https://installsafe.io/docs.\n\nErrors share one envelope: a human `error` sentence, a stable `code`, an optional `hint`, and a `docs` URL.","contact":{"email":"support@installsafe.io","url":"https://installsafe.io/docs"},"termsOfService":"https://installsafe.io/privacy"},"externalDocs":{"url":"https://installsafe.io/docs","description":"Install Safe API documentation"},"servers":[{"url":"https://installsafe.io"}],"paths":{"/api/scan":{"post":{"operationId":"scanDependencies","summary":"Check every dependency in a manifest or lockfile against OSV.dev","description":"Accepts the text of a package.json, package-lock.json, pnpm-lock.yaml or bun.lock and returns every known vulnerability or malware advisory affecting the exact versions it names. Free, no authentication, nothing about the manifest is stored or logged. Rate limited to 10 scans per IP per 10 minutes; bodies over 8 MB are rejected.","tags":["scanner"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ScanRequest"},"example":{"manifest":"{\"dependencies\":{\"lodash\":\"4.17.20\"}}"}}}},"responses":{"200":{"description":"Scan completed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ScanResult"}}}},"400":{"description":"Body is not JSON, has no \"manifest\" string, or the manifest could not be parsed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"405":{"description":"Wrong method. The Allow header names the ones this endpoint accepts.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"Manifest larger than 8 MB.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"More than 10 scans from this IP in 10 minutes.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"Advisory data unavailable and no usable mirror. Retry shortly.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/health":{"get":{"operationId":"health","summary":"Liveness and advisory-mirror freshness","description":"Unauthenticated. Answers 200 while the app can reach its database, 503 otherwise. `mirror` reports the local OSV mirror snapshot, so a silently stale mirror is visible without a dashboard.","tags":["operations"],"responses":{"200":{"description":"Healthy.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Health"}}}},"503":{"description":"Database unreachable.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Health"}}}}}}}},"components":{"schemas":{"ScanRequest":{"type":"object","required":["manifest"],"properties":{"manifest":{"type":"string","description":"Full text of a package.json, package-lock.json, pnpm-lock.yaml or bun.lock. Lockfiles give exact versions; a bare package.json is resolved as a semver-range approximation."}}},"ScanResult":{"type":"object","required":["kind","scanned","truncated","vulnerable","noFix","findings","source"],"properties":{"kind":{"type":"string","enum":["npm-lock","pnpm-lock","bun-lock","manifest"],"description":"What the input was detected as."},"scanned":{"type":"integer","description":"Number of dependencies checked."},"truncated":{"type":"boolean","description":"True if the manifest had more dependencies than the scanner will check in one request."},"vulnerable":{"type":"integer","description":"Distinct name@version pairs with at least one finding."},"noFix":{"type":"integer","description":"Findings with no fixed version available."},"findings":{"type":"array","items":{"$ref":"#/components/schemas/Finding"}},"source":{"type":"string","enum":["live","mirror"],"description":"Whether OSV.dev answered directly or a local mirror was used."},"dataAsOf":{"type":"string","format":"date-time","description":"Only present when source is \"mirror\": the mirror snapshot time."}}},"Finding":{"type":"object","required":["name","version","severity","advisoryId","summary","fixedVersion","fixAvailable"],"properties":{"name":{"type":"string"},"version":{"type":"string","description":"The affected version as it appears in the manifest."},"severity":{"type":"string","enum":["critical","high","moderate","low","unknown"]},"advisoryId":{"type":"string","description":"OSV / GHSA identifier, e.g. GHSA-35jh-r3h4-6jhm."},"summary":{"type":"string"},"fixedVersion":{"type":["string","null"],"description":"Lowest version that resolves every advisory for this package, or null."},"fixAvailable":{"type":"boolean"}}},"Health":{"type":"object","required":["ok"],"properties":{"ok":{"type":"boolean"},"db":{"type":"string","description":"Present only on failure, e.g. \"unreachable\"."},"mirror":{"type":"object","description":"Local OSV advisory mirror, used when OSV.dev cannot be reached.","properties":{"status":{"type":"string"},"lastSyncAt":{"type":["string","null"],"format":"date-time"},"advisoryCount":{"type":"integer"},"stale":{"type":"boolean"}}}}},"Error":{"type":"object","required":["error","code","docs"],"description":"The envelope every machine-facing error on this host uses. `error` is a sentence for a human; `code` is the stable identifier to branch on; `hint` says what to do differently; `docs` points at the documentation.","properties":{"error":{"type":"string","description":"Human-readable description of what went wrong."},"code":{"type":"string","enum":["invalid_request","unauthorized","forbidden","not_found","method_not_allowed","payload_too_large","rate_limited","internal_error","upstream_unavailable"],"description":"Stable machine-readable error identifier."},"hint":{"type":"string","description":"What to change before retrying. Absent when there is nothing useful to say."},"docs":{"type":"string","format":"uri","description":"Documentation for this API."}}}}}}