Documentation

Two steps: render the widget, verify the token on your server. Everything else is optional.

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

Widget

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

AttributeDefaultDescription
data-sitekeyRequired. Public key from the dashboard.
data-actionsubmitLabel for the protected action, shows up in analytics.
data-themeautoauto, light or dark.
data-callbackJS 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 -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

FieldTypeDescription
tokenstringRequired. The cg-token from the widget.
remoteipstringOptional but recommended. End-user IP.
actionstringOptional. 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"
}
FieldMeaning
score0.00 (certainly a bot) … 1.00 (certainly human).
actionOur recommendation: allow, challenge, block.
request_idQuote this in support tickets.

Error codes

CodeHTTPWhat to do
missing-input-secret401Authorization header absent.
invalid-input-secret401Wrong or revoked secret key.
invalid-input-response200Token malformed — treat as failure.
timeout-or-duplicate200Token expired or already used.
rate-limited429Back 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.

PlanVerify req/sBurst
Free1050
Pro2001000
Enterprisenegotiatednegotiated