REST API
Base URL: https://api.protogrid.dev (a local stack answers at http://localhost:8080). All responses are JSON and carry next_actions. Anonymous access is rate limited per IP (20 requests per minute); a free API key raises that to 60 per minute and 5,000 per day. The x-ratelimit-* and retry-after headers say when to back off. Server names are reverse-DNS (io.github.owner/name) and may be given URL-encoded (io.github.owner%2Fname) or with the literal slash.
GET /v1/search
Section titled “GET /v1/search”| param | type | notes |
|---|---|---|
q |
string, required | intent in plain words, 1–500 chars |
limit |
1–50 | default 10 |
class |
comma list | R0,R1,R2,L0,unknown |
transport |
streamable-http | sse |
keeps servers with a non-templated remote of that transport |
category |
string | one of the fixed taxonomy ids |
min_trust |
0–100 | |
flags |
comma list | every listed trust flag must be present |
exclude_flags |
comma list | any listed flag excludes a server, e.g. multi-version-spam,duplicate-repo |
min_quality |
0–100 | minimum quality score; servers that are not scored are left out |
quality_flags |
comma list | every listed quality flag must be present |
exclude_quality_flags |
comma list | any listed quality flag excludes a server, e.g. known-vulns |
owner_verified |
true | false |
true keeps only servers whose owner proved control of the namespace (not an audit) |
Hybrid search: tool-level and server-level keyword and semantic candidates, fused by reciprocal rank, re-ranked by trust, grouped by server. A short query that names a publisher (stripe, github) puts that publisher’s own servers first, and a server whose title or name matches it next. Quality and a verified owner never change the order; filter on them instead. mode says hybrid or keyword (fallback when no embedding model is loaded).
Result fields: name, title, description, connection_class, autonomous, human_steps, categories, trust_score, trust_flags[], quality_score, quality_label (see quality labels), quality_flags[], owner_verified, tool_count, score, matched_tools[] {name, description}.
GET /v1/servers/{name}
Section titled “GET /v1/servers/{name}”The descriptor. ?schemas=true includes tool input/output schemas. 404 when unknown.
GET /v1/servers/{name}/tools
Section titled “GET /v1/servers/{name}/tools”Current tools with full schemas, ordered by name. limit (1–100, default 100), cursor (the next_cursor of the previous page).
{ "server": "…", "count": 100, "tools": [{ "name": "…", "description": "…", "input_schema": { }, "output_schema": null, "annotations": { }, "source": "probe", "observed_at": "…" }], "next_cursor": "…", "next_actions": [ ] }GET /v1/servers/{name}/connection
Section titled “GET /v1/servers/{name}/connection”target = mcpServers (default) | vscode | cursor | claude-code-cli | codex-toml | gemini | goose.
Returns server, key (the config key, last segment of the name), kind (remote | package | bundle), class, autonomous, human_steps, target, content_type (application/json for config objects, text/plain for the CLI, TOML and YAML targets), connection, secrets[] {name, where}, and for remotes auth_type and oauth metadata when known.
| status | when |
|---|---|
403 blocked |
on the blocklist |
410 deleted |
deleted from the official registry |
409 no_connection |
no usable remote or package |
Examples per target:
# claude-code-cliclaude mcp add --transport http agentdm https://api.agentdm.ai/mcp/v1/grid --header "Authorization: ${AGENTDM_TOKEN}"
# codex-toml[mcp_servers.agentdm]url = "https://api.agentdm.ai/mcp/v1/grid"env_http_headers = { "Authorization" = "AGENTDM_TOKEN" }// cursor{ "mcpServers": { "docs-mcp": { "type": "http", "url": "https://tandem.ac/mcp" } }, "deeplink": "cursor://anysphere.cursor-deeplink/mcp/install?name=docs-mcp&config=eyJ0eXBlIjoi…" }GET /v1/servers/{name}/quality
Section titled “GET /v1/servers/{name}/quality”The descriptor’s quality block (see quality score) plus history, the daily score for the last days (1–400, default 90), blocked (true when the listing is on the blocklist, whatever its score) and owner (the descriptor’s owner-verified block).
curl -s 'https://api.protogrid.dev/v1/servers/dev.protogrid%2Fregistry/quality?days=30'GET /v1/servers/{name}/changes
Section titled “GET /v1/servers/{name}/changes”Tool-definition history, newest first: tool, kind (added, removed, changed), observed_at, fields (what changed), description_before and description_after when the description changed, description_similarity (share of words in common), new_links (sites the definition names that the server never named before, see new sites), and both definition hashes.
limit (1–200, default 50) and before (the next_before of the previous page).
A server’s first observation is a baseline and records nothing.
GET /v1/servers/{name}/dependencies
Section titled “GET /v1/servers/{name}/dependencies”For each npm or PyPI package of the server: status (resolved, incomplete, unknown to the resolver, or pending), dependency_count, the official MCP SDK in the graph (mcp_sdk), and every known advisory with id, severity, summary, the affected package and version, relation (direct or indirect), fixed_in (the lowest published fix above that version, or null) and a url on osv.dev.
Advisories are matched daily; graphs are read on each new release and refreshed monthly, daily for monitored servers (see Quality score); the attribution field carries the data credit.
curl -s 'https://api.protogrid.dev/v1/servers/dev.protogrid%2Fregistry/dependencies'GET /v1/servers/{name}/gate
Section titled “GET /v1/servers/{name}/gate”Pass or fail for one server against rules you choose, with every rule it broke, so an agent, a gateway or a pipeline applies one rule in one place.
Name at least one rule; without any the answer is 400 no_rules.
| rule | type | fails when |
|---|---|---|
min_quality |
0–100 | the quality score is lower, or the server is not scored |
min_trust |
0–100 | the trust score is lower, or not computed |
no_fail |
comma list of check ids | any of these checks fails, e.g. tools.hidden_text,stability.rug_pull; an unknown id is a 400 that lists the valid ones |
exclude_quality_flags |
comma list | any of these quality flags is raised |
exclude_flags |
comma list | any of these trust flags is raised |
class |
comma list | the connection class is not one of these |
owner_verified |
true |
no owner proved control of the namespace |
A blocked or deleted listing never passes.
The answer is 200 whether the gate passes or not: pass, failed (each with rule and detail), the rules it applied, the server’s quality (score, label, flags), trust, class, owner_verified and tools_hash.
Pin tools_hash to notice when the tools change after the gate passed.
curl -s 'https://api.protogrid.dev/v1/servers/dev.protogrid%2Fregistry/gate?min_quality=75&no_fail=tools.hidden_text&exclude_quality_flags=known-vulns,hidden-text'POST /v1/check
Section titled “POST /v1/check”Probes a remote MCP server URL once and reports its quality checks and its readiness for the Claude and OpenAI directories (see Check your server).
Body { "url": "https://mcp.example.com/mcp" }; ?wait=N (0 to 25 seconds) holds the request until the result is in.
curl -s -X POST 'https://api.protogrid.dev/v1/check?wait=25' -H 'content-type: application/json' -d '{"url":"https://mcp.example.com/mcp"}'The answer is 202 with status queued or running, a Location and retry-after while the probe runs, and 200 with status done and result once it finished.
result holds probe (outcome, protocol, a summary of the OAuth metadata, the tools without schemas, redirects), quality (the same checks as the descriptor, stability na) and readiness (one entry per directory with every requirement, its status, detail and source).
server names the catalog server when the URL is one of its remotes, and page is the shareable result page.
The same URL within 5 minutes returns the recent check.
With an API key, "fresh": true in the body runs a new check anyway (for CI right after a deploy); it counts against your hourly allowance, and a check of the same URL that has not finished yet is still returned.
A refused URL answers 400 invalid_url with a reason; an exhausted allowance answers 429 check_quota_exceeded with scope (you, host or global) and retry-after.
GET /v1/check/{id}
Section titled “GET /v1/check/{id}”Reads a check; ?wait=N as above.
Finished results never change and are cacheable for an hour; unknown ids answer 404.
Results are kept 30 days and every check response carries X-Robots-Tag: noindex.
Caching
Section titled “Caching”Every successful GET carries Cache-Control: public, max-age=…, stale-while-revalidate=… and a weak ETag; If-None-Match answers 304. Defaults: search 60 s, server descriptors, tools and connections 300 s, /v0.1 300 s. /mcp, /healthz, errors and non-GET responses are no-store. Cached responses do not carry the per-IP rate-limit headers; a 429 always does, with retry-after.
Errors
Section titled “Errors”Validation errors answer 400 with { "error": "bad_request", "issues": { "fieldErrors": { … } } }. Unknown routes answer 404 { "error": "not_found" }. Rate limiting answers 429 with retry-after; an exhausted daily quota answers 429 daily_quota_exceeded, and a bad API key 401 invalid_api_key (see API keys and limits).
GET /healthz
Section titled “GET /healthz”{ "ok": true, "search": "hybrid" | "keyword", "mcp": "/mcp" }.