⌘K
Scan private GitHub repos, npm packages, PyPI packages, Docker Hub images, and GHCR images with a read-only token.
A private scan runs the same pipeline as a public scan but the result stays in your account — no public permalink, no entry in the public registry, never readable by anyone else. Required for any scan that needs to download a non-public artifact.
Available on Developer, Team, and Business plans. A signed-in free-tier user submitting a private scan gets 402 SUBSCRIPTION_REQUIRED.
Visibility is fixed at submission time — you can't flip a public scan to private (or vice versa) after it runs.
| Target | credentials.type | Token format | Minimum scope |
|---|---|---|---|
| GitHub | github | ghp_… (classic) or github_pat_… (fine-grained) | Read-only on Contents + Metadata for the target repo |
| npm | npm | npm_… (granular access token) | read-only access to the target package |
| PyPI | pypi | pypi-… | Read-only on the project |
| Docker Hub | docker | Personal access token | Public Repo Read |
| GHCR (GitHub Container Registry) | ghcr | Same ghp_… / github_pat_… as GitHub | read:packages |
The type field must match the resolved input — submitting a github token against pypi:requests returns 400 CREDENTIAL_TYPE_MISMATCH before a scan slot is consumed.
Custom registries (Artifactory, Nexus, GitHub Packages on a custom URL) and self-hosted Git providers are on the roadmap.
curl -X POST https://api.mcpsafe.io/api/v1/scan \
-H "Authorization: Bearer mcpsafe_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"input": "github:my-org/private-mcp",
"mode": "deep",
"scan_visibility": "private",
"credentials": { "type": "github", "token": "ghp_xxxxxxxxxxxxxxxxxxxx" }
}'The response shape is identical to a public scan — scan_id and canonical_id only. Read the result with GET /api/v1/scan/{id} (your API key) or GET /scan/{id} from a signed-in browser session.
-d '{
"input": "docker:my-org/private-image:1.4.2",
"scan_visibility": "private",
"credentials": {
"type": "docker",
"username": "my-bot",
"token": "dckr_pat_xxxxxxxxxxxxxxxxxxxx"
}
}'If a scan fails, the failure event includes the rule path and error code but never echoes the credential.
| Path | Auth | What it returns |
|---|---|---|
GET /api/v1/scan/{id} | API key with scan:get or scan:read | Same as public, plus scan_visibility: "private" |
GET /scan/{id} | Session JWT (web app) | Same |
GET /user/scans | Session JWT | Paginated history of your private scans |
GET /api/v1/scans | API key with scan:list | Paginated history of your public scans (the API-key path doesn't surface private rows yet) |
Team-shared visibility: members of a Team or Business plan see all private scans submitted by other members of the same org — see Team API.
Private scans have a separate, smaller monthly budget than public scans because they cost more to run (token-driven downloads bypass the public registry cache).
| Tier | Private / month | Private deep / month |
|---|---|---|
| Developer | 70 | 20 |
| Team | 700 | 40 |
| Business | 4,000 | 120 |
Hitting the cap returns 429 RATE_LIMITED with error.details.window: "month". See API Overview — Rate limits for the full picture.
| Code | HTTP | Meaning |
|---|---|---|
PRIVATE_REQUIRES_SIGNIN | 401 | scan_visibility: "private" was requested without authentication |
SUBSCRIPTION_REQUIRED | 402 | Free-tier account tried to submit a private scan |
CREDENTIALS_PUBLIC_SCAN | 400 | credentials was supplied on a scan_visibility: "public" scan — credentials are private-scan only |
INVALID_CREDENTIALS | 400 | Wrong shape or empty token |
CREDENTIAL_TYPE_MISMATCH | 400 | credentials.type doesn't match the resolved target |
ORG_RATE_LIMITED | 429 | Org-shared monthly cap reached (Team / Business plans) |
Treat MCPSafe scan tokens like any other CI credential — rotate on a schedule (90 days is a reasonable default), revoke immediately on suspected leak, and never reuse the same token across MCPSafe and other services. We log the source IP of every API request and email you on first use from a new /24 prefix; if you get an unexpected "new location" email after a private scan, rotate the token.