Quickstart
Create a site key in the dashboard, then add the widget to the form you want protected. The widget produces a
single-use cg-token. Your backend exchanges that token for a verdict.
# 1. widget → browser gets cg-token
# 2. your form POSTs it to your backend
# 3. backend calls the verify endpoint with your secret key
# 4. allow / challenge / block based on the score
The script is 9 KB gzipped, has no dependencies and does not set cookies.
<script src ="https://capguard.ru/v1/cg.js" async defer ></script >
<form method ="post" action ="/signup" >
<input name ="email" type ="email" >
<div class ="cg-widget" data-sitekey ="cg_site_8f21c0" ></div >
<button >Create account</button >
</form >
Widget attributes
Attribute Default Description
data-sitekey— Required. Public key from the dashboard.
data-actionsubmitLabel for the protected action, shows up in analytics.
data-themeautoauto, light or dark.
data-callback— JS function name called with the token.
Verify endpoint
POST https://capguard.ru/api/v1/captcha/challenge/verify/
Authenticate with your secret key. Never expose it in the browser. Tokens are single-use and expire after
120 seconds.
cURL
Node.js
Python
Go
curl -X POST https://capguard.ru/api/v1/captcha/challenge/verify/ \
-H "Authorization: Bearer $CAPGUARD_KEY" \
-H "Content-Type: application/json" \
-d '{"token":"cg_tkn_3f9a1c8e","remoteip":"203.0.113.7"}'
const res = await fetch ("https://capguard.ru/api/v1/captcha/challenge/verify/" , {
method: "POST" ,
headers: {
"Authorization" : `Bearer ${process.env.CAPGUARD_KEY}` ,
"Content-Type" : "application/json"
},
body: JSON .stringify ({ token, remoteip: req.ip })
});
const v = await res.json ();
if (!v.success || v.score < 0.5 ) throw new Error ("blocked" );
import os, requests
r = requests.post (
"https://capguard.ru/api/v1/captcha/challenge/verify/" ,
headers={"Authorization" : f"Bearer {os.environ['CAPGUARD_KEY']}" },
json={"token" : token, "remoteip" : request.remote_addr},
timeout=3 ,
)
v = r.json ()
if not v["success" ] or v["score" ] < 0.5 :
raise PermissionError ("blocked" )
body, _ := json.Marshal (map [string ]string {"token" : token, "remoteip" : ip})
req, _ := http.NewRequest ("POST" , verifyURL, bytes.NewReader (body))
req.Header.Set ("Authorization" , "Bearer " +os.Getenv ("CAPGUARD_KEY" ))
req.Header.Set ("Content-Type" , "application/json" )
resp, err := http.DefaultClient.Do (req)
Request fields
Field Type Description
tokenstring Required. The cg-token from the widget.
remoteipstring Optional but recommended. End-user IP.
actionstring Optional. Must match the widget action if set.
Response fields
{
"success" : true ,
"score" : 0.92 ,
"action" : "allow" ,
"hostname" : "example.com" ,
"challenge_ts" : "2026-07-28T09:14:02Z" ,
"request_id" : "cgr_01J2K8Q4"
}
Field Meaning
score0.00 (certainly a bot) … 1.00 (certainly human).
actionOur recommendation: allow, challenge, block.
request_idQuote this in support tickets.
Error codes
Code HTTP What to do
missing-input-secret401 Authorization header absent.
invalid-input-secret401 Wrong or revoked secret key.
invalid-input-response200 Token malformed — treat as failure.
timeout-or-duplicate200 Token expired or already used.
rate-limited429 Back off, retry after Retry-After.
Server SDKs
npm i capguard # JS / TS
pip install capguard # Python 3.9+
go get capguard.ru/go/capguard
composer require capguard/sdk
Rate limits
Verification requests are limited per secret key. Exceeding the limit returns 429 with a
Retry-After header — never a silent failure.
Plan Verify req/s Burst
Free 10 50
Pro 200 1000
Enterprise negotiated negotiated