Developer documentation

Server-side verification API

The API is JSON over HTTPS. Two endpoints matter: the browser calls puzzle, your server calls siteverify.
Both exist under /api/v1/;
the short aliases /api/puzzle and /api/verify point to the same handlers.

Authentication

Server calls carry the API key in the X-API-Key header.
API keys are created in your account, shown once, and stored as a hash on our side.
One key verifies responses for every sitekey of your account.
Treat it like a password;
if it leaks, revoke it and create a new one.

HTTP
X-API-Key: ck_YOUR_API_KEY

POST /api/v1/siteverify

ParameterTypeDescription
responsestringRequired. The value of the hidden field (cdp-captcha-solution).
sitekeystringOptional. When given, the response must belong to this sitekey.

Parameters go in a JSON body or as form data.
Example:

cURL
curl -X POST 'https://cdpcaptcha.com/api/v1/siteverify' \
  -H 'X-API-Key: ck_YOUR_API_KEY' \
  -H 'Content-Type: application/json' \
  -d '{"response": "…", "sitekey": "YOUR_SITEKEY"}'

Successful verification

JSON
{
  "success": true,
  "data": {
    "verification_id": "cv_8e1d0a2c4b6f9a11",
    "challenge": {
      "solved_at": "2026-10-13T10:12:04Z",
      "issued_at": "2026-10-13T10:11:58Z",
      "sitekey": "YOUR_SITEKEY",
      "difficulty_bits": 14
    }
  }
}

Failed verification

JSON
{
  "success": false,
  "error": {
    "code": "response_duplicate",
    "detail": "The response was already used."
  }
}

Error codes

CodeHTTPMeaning
auth_required401The X-API-Key header is missing.
auth_invalid401The API key is unknown or revoked.
sitekey_invalid400The sitekey is unknown, revoked, or belongs to another account.
response_missing400No response parameter.
response_invalid200Wrong format, bad signature, unknown puzzle or wrong solution.
response_timeout200The puzzle expired.
response_duplicate200The response was already used.
bad_request400Malformed request (for example a response larger than 16 KB).
method_not_allowed405Use POST.
Status 200 with success: false is the normal answer for a bad token. Decide on the success field, never on the status code alone. A 401 or 400 means your integration is misconfigured.

When the API is unreachable

If your server cannot reach the endpoint at all (timeout, connection error), decide consciously: rejecting every form during an outage can be worse than accepting a little spam.
The PHP client has a fail_open option for that case and logs the transport error.
It never accepts an invalid token.

GET /api/v1/puzzle

Called by the widget.
You normally never call it yourself.

ParameterDescription
sitekeyRequired.
difficultyOnly honoured for the playground sitekey.
JSON
{
  "success": true,
  "data": {
    "puzzle": "AQ4IAGjs…",
    "bits": 14,
    "count": 8,
    "expires_at": 1760350318,
    "difficulty": 4,
    "mode": "zero-click"
  }
}

Errors: sitekey_missing / sitekey_invalid (400), rate_limited (429).
The response carries CORS headers for the origins allowed for the sitekey;
an empty origin list allows every origin.

GET /api/v1/health

JSON
{ "ok": true, "version": "1.0.0" }