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.
X-API-Key: ck_YOUR_API_KEYPOST /api/v1/siteverify
| Parameter | Type | Description |
|---|---|---|
response | string | Required. The value of the hidden field (cdp-captcha-solution). |
sitekey | string | Optional. When given, the response must belong to this sitekey. |
Parameters go in a JSON body or as form data.
Example:
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
{
"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
{
"success": false,
"error": {
"code": "response_duplicate",
"detail": "The response was already used."
}
}Error codes
| Code | HTTP | Meaning |
|---|---|---|
auth_required | 401 | The X-API-Key header is missing. |
auth_invalid | 401 | The API key is unknown or revoked. |
sitekey_invalid | 400 | The sitekey is unknown, revoked, or belongs to another account. |
response_missing | 400 | No response parameter. |
response_invalid | 200 | Wrong format, bad signature, unknown puzzle or wrong solution. |
response_timeout | 200 | The puzzle expired. |
response_duplicate | 200 | The response was already used. |
bad_request | 400 | Malformed request (for example a response larger than 16 KB). |
method_not_allowed | 405 | Use POST. |
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.
| Parameter | Description |
|---|---|
sitekey | Required. |
difficulty | Only honoured for the playground sitekey. |
{
"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
{ "ok": true, "version": "1.0.0" }