API v1
Stable, versioned API for submitting scans, fetching reports, and managing webhooks. Source of truth: the OpenAPI 3.1 document at /docs/api/openapi.json.
Authentication
Every /api/v1/* call requires an API key. Create one at Account → API keys and include it in every request:
Authorization: Bearer ar_live_<32 hex>
Keys are SHA-256 hashed at rest. We display the raw key exactly once at creation. Lost keys can be revoked and replaced.
Response envelope
Every successful response is wrapped in a stable envelope:
{
"data": { ... endpoint-specific shape ... },
"meta": {
"requestId": "req_…",
"generatedAt": "2026-04-27T12:34:56.000Z"
}
}Send your own correlation id by setting X-AgentReserve-Request-Id on the request — we echo it back in meta.requestId and the response header.
Errors share a parallel shape:
{
"error": { "code": "RATE_LIMITED", "message": "…" },
"meta": { "requestId": "req_…", "generatedAt": "…" }
}Rate limits
- free: 10 scans/hr · 100 scans/month · max 100 results per directory page.
- pro: 60 scans/hr · 5000 scans/month · max 500 results per directory page.
- enterprise: unlimited scans/hr · unlimited scans/month · max 500 results per directory page.
Exceeded limits return HTTP 429 with Retry-After in seconds.
Submit a scan
curl -X POST https://agentreserve.dev/api/v1/scans \
-H "Authorization: Bearer ar_live_…" \
-H "Content-Type: application/json" \
-d '{ "url": "https://mcp.example.com" }'Response shape:
{
"data": {
"scanId": "scn_…",
"status": "SUCCEEDED",
"cached": false,
"report": {
"publicSlug": "abcdef1234",
"score": 88,
"grade": "B",
"verdict": "allow",
"riskLevel": "low",
"topReasons": [...],
"generatedAt": "2026-04-27T…"
},
"_links": {
"self": "https://agentreserve.dev/api/v1/scans/scn_…",
"report": "https://agentreserve.dev/reports/abcdef1234",
"export_json": "https://agentreserve.dev/api/v1/scans/scn_…/export?format=json",
"export_csv": "https://agentreserve.dev/api/v1/scans/scn_…/export?format=csv",
"export_pdf": "https://agentreserve.dev/api/v1/scans/scn_…/export?format=pdf"
}
},
"meta": { "requestId": "req_…", "generatedAt": "…" }
}Get a scan
curl https://agentreserve.dev/api/v1/scans/scn_… \ -H "Authorization: Bearer ar_live_…"
Returns the scan + its scored Report (when finished). Stored probe payloads are deliberately omitted from the v1 surface — they are internal storage and not part of the stable contract.
List servers
curl 'https://agentreserve.dev/api/v1/servers?verdict=allow&page=1&pageSize=50' \ -H "Authorization: Bearer ar_live_…"
Filters: verdict, risk, grade, dcr=yes|no, recency=30, q=hostname. Sort: score (default), recency, popularity.
Free tier: pageSize capped at 100. Pro+ may request up to 500 per page.
Get a server
curl https://agentreserve.dev/api/v1/servers/mcp.example.com \ -H "Authorization: Bearer ar_live_…"
Exports
# JSON (free) curl https://agentreserve.dev/api/v1/scans/scn_…/export?format=json \ -H "Authorization: Bearer ar_live_…" # CSV (free) — one row per RuleResult curl https://agentreserve.dev/api/v1/scans/scn_…/export?format=csv \ -H "Authorization: Bearer ar_live_…" \ -o report.csv # PDF (Pro+) — single-page summary card curl https://agentreserve.dev/api/v1/scans/scn_…/export?format=pdf \ -H "Authorization: Bearer ar_live_…" \ -o report.pdf
Webhooks
Manage subscriptions at Account → Webhooks. Each delivery is a POST with this body:
{
"id": "whd_…", // delivery id, useful for receiver-side dedupe
"type": "scan.succeeded",
"timestamp": "2026-04-27T…",
"data": {
"scanId": "scn_…",
"hostname": "mcp.example.com",
"serverUrl": "https://mcp.example.com",
"publicSlug": "abcdef1234",
"score": 88,
"grade": "B",
"verdict": "allow",
"riskLevel": "low",
"reportUrl": "https://agentreserve.dev/reports/abcdef1234"
}
}Available events:
- scan.succeeded
- scan.failed
- scan.score_changed
- scan.verdict_changed
Verifying signatures
Every delivery includes X-AgentReserve-Signature: t=<ts>,v1=<hex>. Re-compute on the receiver:
hmac_sha256(secret, "{ts}.{rawRequestBody}") === sigReject signatures whose ts drifts more than 5 minutes from your wall clock.
Retries
Non-2xx responses retry on backoff: 1m, 5m, 30m, 2h. After 5 consecutive delivery failures we mark the webhook auto-disabled and email the owner. Re-enable from the UI; the failure counter resets.
Idempotency
Receivers should be idempotent on data.scanId + type, or on the top-level id (delivery id). Our retries may deliver the same body twice if the receiver returned 5xx after processing.
Versioning
The /api/v1/* response shapes are a public contract. We will not break them without shipping /api/v2/* in parallel and offering a public deprecation window. Adding new fields to existing responses is not a breaking change — clients should ignore unknown fields.