⌘K
MCPSafe REST API — base URL, authentication, scopes, IP allowlist, rate limits, and response format.
The MCPSafe API is a REST API that returns JSON. You can use it to scan packages programmatically, retrieve results, and integrate into CI/CD pipelines.
Looking for the formal spec? Every endpoint is documented in the interactive OpenAPI reference (Redoc + try-it-out).
Want to try it now? Import the Postman collection — pre-wired endpoints, auth header, and example bodies. After import, set the api_key collection variable to a key minted in your API keys dashboard.
https://api.mcpsafe.io
All paths in this documentation are relative to the base URL.
Two authentication methods, depending on the route:
Mint a key from your API keys dashboard and pass it as a Bearer token. Keys start with the mcpsafe_ prefix and are only shown once at creation — store them in your secret manager immediately.
curl https://api.mcpsafe.io/api/v1/scan \
-X POST \
-H "Authorization: Bearer mcpsafe_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{"input": "@modelcontextprotocol/server-everything", "mode": "fast"}'API keys are a paid feature (Developer, Team, Business plans). Free-tier accounts get a 402 SUBSCRIPTION_REQUIRED if they try to mint one.
Legacy mcp_ prefix
Keys minted before 2026-04-29 use the mcp_ prefix. They keep working until they expire (max 365 days from mint date). New keys use mcpsafe_ and the legacy prefix is fully retired on 2027-04-29.
Routes consumed directly by the web UI accept a NextAuth session token. You typically don't need this — the web app handles it for you.
Each API key carries one or more scopes. The authorizer rejects requests where the route's required scope isn't held. Pick the narrowest set that covers your use case — a leaked key only does what its scopes allow.
| Scope | Allows |
|---|---|
scan:submit | POST /api/v1/scan — submit a scan |
scan:list | GET /api/v1/scans — list your scan history |
scan:get | GET /api/v1/scan/{id} — read a specific scan result |
registry:read | GET /api/v1/registry/* — browse the public registry |
watchlist:read | reserved — GET /api/v1/watchlist/* will land in a later release |
| Scope | Equivalent to |
|---|---|
scan:read | scan:list + scan:get + registry:read + watchlist:read |
scan:write | scan:submit + everything in scan:read |
A key minted only with scan:list cannot submit scans even if the bearer is otherwise authenticated. The dashboard defaults new keys to scan:submit + scan:list + scan:get — the typical CI/CD shape (submit, then poll). Add scan:write (or pick scan:submit + scan:read) for full read+write.
When creating a key, you can optionally pin it to a list of source IP CIDRs. Requests from any IP outside the list are rejected with the same shape as missing scope (no information leak about which check failed).
ipaddress.ip_networkThis is high-leverage for CI runners, fixed bastions, and office VPNs. Less useful for laptops on dynamic IPs.
The first time an API key is used from a /24 prefix it hasn't been seen from before, the user gets a "new location" email with the source IP and key label. Subsequent requests from the same /24 don't re-trigger. This catches leaked keys quickly without spamming users on legitimate IP changes.
If you ever get a "new location" email you didn't expect, revoke the key from the dashboard immediately.
Most responses are wrapped in a standard envelope:
{
"success": true,
"data": { ... }
}Errors follow the same shape with success: false:
{
"success": false,
"error": {
"code": "RATE_LIMITED",
"message": "Rate limit reached. Please try again in 15 seconds.",
"details": {
"window": "minute",
"tier": "dev",
"current": 11,
"limit": 10,
"retry_after_seconds": 15
}
}
}Stable v1 endpoints under /api/v1/* (registry, stats, recent scans) return data directly per the OpenAPI spec — see the interactive reference for exact shapes.
API-key requests are bucketed at two layers:
Caps depend on your subscription tier. Monthly is the binding cost ceiling — daily and weekly allow bursts within it.
| Tier | Public scans / month | Deep scans / month | Burst (req / min) |
|---|---|---|---|
| Anonymous (no auth) | — (10 / day cap) | 0 | 3 |
| Free (signed in) | — (20 / day cap) | 6 / week | 5 |
| Developer | 200 | 20 | 10 |
| Team | 2,000 | 60 | 20 |
| Business | 20,000 | 180 | 30 |
Anonymous and Free tiers have no monthly cap — they're bucketed at the day/week level so casual scanning stays free, but sustained throughput requires a paid plan. Anonymous deep scans are blocked at the API layer (sign in to enable).
The per-key cap and per-user budget use the same numbers; the per-key bucket exists for leak isolation, not extra quota.
Private scans have separate, smaller budgets because they cost more to run:
| Tier | Private scans / month | Private deep / month |
|---|---|---|
| Developer | 70 | 20 |
| Team | 700 | 40 |
| Business | 4,000 | 120 |
An edge abuse-protection rule also blocks any single Authorization header value making more than 8,000 requests per 5 minutes on /api/v1/*. This is well above any legitimate paid usage and only fires on abuse patterns.
429 Too Many Requests
When rate-limited, retry after the Retry-After header (in seconds). The response body's error.details includes window, current, limit, and retry_after_seconds so you can pace yourself precisely.
Headers X-RateLimit-Limit, X-RateLimit-Remaining, and X-RateLimit-Reset are emitted on every API-key response (including successful ones) so CI clients can pace before hitting 429.
Every API key has an expiry date. Default 90 days, maximum 365 days. There is no "never expires" option — rotation is mandatory hygiene. Revoke from the dashboard the moment you suspect a key has leaked, then mint a new one with a fresh prefix.
last_used is recorded on every authenticated request (throttled to one write per minute per key) so you can spot stale keys before purging them.
A maximum of 10 active keys per account is enforced at create time. Revoke unused keys to free slots.
| Method | Path | Auth | Description |
|---|---|---|---|
| POST | /api/v1/scan | API key (scan:submit or scan:write) | Submit a scan |
| GET | /api/v1/scan/{id} | API key (scan:get or scan:read) | Read scan result by ID |
| GET | /api/v1/scans | API key (scan:list or scan:read) | List your public scan history |
| GET | /scan/{id} | None | Read scan result by ID (unauthenticated, public-only) |
| GET | /scan/{id}/stream | None | SSE progress stream |
| GET | /scan/{id}/consensus | None | LLM judge panel verdicts (deep scans only) |
| GET | /scans | Session JWT | List your public scan history (web UI) |
| GET | /user/scans | Session JWT | List your private scan history |
| GET | /versions | None | Version history for a package |
| Method | Path | Auth | Description |
|---|---|---|---|
| GET | /api/v1/registry | None | Browse the public registry |
| GET | /api/v1/registry/{source}/{package} | None | Single registry record |
| GET | /api/v1/scans/recent | None | Recently completed public scans |
| GET | /api/v1/rules/catalog | None | Live snapshot of the rule catalog |
| GET | /api/v1/stats | None | Ecosystem stats (packages scanned, rules live) |
| GET | /api/v1/stats/state-of-mcp | None | State of MCP report data |
| GET | /api/v1/compare | None | Side-by-side package comparison |
Public endpoints (Auth = None) don't require a key, but if you supply one, your usage still counts toward the per-user aggregate cap.
Team management, invites, billing, and Stripe-portal sessions are exposed under /team/*, /billing/*, /checkout/session, /subscription, and /me. These endpoints authenticate with a NextAuth session JWT, not an API key — they're intended for the web app. See Team & billing for the full surface.
See POST /scan, GET /scan/:id, Private scans, and Team & billing for full details.