docs
Conventions
One envelope, one error shape, CORS open on everything including failures.
Response envelope
Directory endpoints return the same envelope. /health returns a bare status object, because a liveness check that needs unwrapping is not a liveness check.
json
{
"ok": true,
"data": [...],
"meta": {
"total": 232,
"page": 1,
"per_page": 20,
"total_pages": 12
}
}Errors
Failures carry a real status code and the same shape every time. Nothing returns 200 with an error inside it.
json
{
"ok": false,
"error": {
"message": "No page at /api/v1/nope",
"code": "route_not_found"
}
}| status | code | when |
|---|---|---|
| 400 | bad_request | A parameter is missing or malformed. |
| 404 | route_not_found | No such endpoint. The body names where to look instead. |
| 404 | not_found | The endpoint exists; the thing you asked for does not. |
| 422 | unprocessable | A submission whose claims did not stand up. The body carries the probe results. |
| 429 | rate_limited | Too many requests from this IP. Check the reset header. |
| 500 | server_error | Ours, not yours. |
Headers
http
X-BotFriendly: true
X-API-Version: v1
X-OpenAPI-Spec: /api/v1/openapi.json
X-MCP-Server: /api/mcp
Access-Control-Allow-Origin: *
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 99
X-RateLimit-Reset: 1714000000Rate limits
By IP, since there are no keys. 100 requests a minute across the API. Endpoints that reach out to third-party sites are lower: /check is 6 a minute, /verify 60 an hour, and submissions lower still.
note
A 429 always carries
X-RateLimit-Reset. Wait for it rather than retrying into the wall.