Developer docs
Public API reference for Botproof / ShieldWall. Create a site in the console, embed the widget, redeem tokens with /siteverify. Sample keys below are placeholders (sw_site_live_XXXX / sw_secret_live_XXXX).
Quickstart
- Create an org + site in the console; copy the public site key and secret (secret shown once).
- Embed
ShieldWallWidgetwith your site key and action. - On your backend, redeem
responseTokenviaPOST /api/v1/siteverify.
Widget (React)
The widget shows an ALTCHA-style checkbox while it solves PoW and collects signals. On PASS you receive a short-lived responseToken for server redeem.
import { useState } from "react";
import { ShieldWallWidget } from "@shieldwall/client";
export function LoginForm() {
const [token, setToken] = useState<string | null>(null);
return (
<form
onSubmit={async (e) => {
e.preventDefault();
await fetch("/api/login", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
/* credentials… */
shieldwallToken: token,
}),
});
}}
>
<ShieldWallWidget
apiBase="https://api.botproof.app/api/captcha"
siteKey="sw_site_live_XXXX"
action="login"
onVerified={setToken}
/>
<button type="submit" disabled={!token}>Sign in</button>
</form>
);
}POST /api/captcha/challenge
Issues an HMAC-signed nonce + difficulty. Prefer the SDK (it calls this for you). When tenancy is enabled, unknown or disabled site keys return SITE_KEY_INVALID.
POST /api/captcha/verify
Browser posts PoW solution + behavioral / fingerprint signals. Prefer ShieldWallWidget — it builds the payload. Response includes verdict, score, optional responseToken (when siteKey was bound), and SW-901 reasonCodes.
// Shape (SDK builds this — do not hand-roll in production)
POST https://api.botproof.app/api/captcha/verify
{
"nonce": "…",
"timestamp": 0,
"difficulty": 3,
"seed": 0,
"signature": "…",
"powNonce": 0,
"powSolveTimeMs": 120,
"siteKey": "sw_site_live_XXXX",
"action": "login",
"mode": "probabilistic",
"algo": "sha256-chain"
/* + behavior / fingerprint fields from the SDK */
}POST /api/v1/siteverify
Server-to-server redeem (reCAPTCHA-compatible). Never trust the browser alone. Tokens are single-use (~2 min TTL).
// Example success body
{
"success": true,
"verdict": "PASSED",
"score": 0.92,
"action": "login",
"hostname": "example.com",
"challenge_ts": "2026-07-25T00:00:00.0000000Z",
"error-codes": null,
"reason-codes": []
}Client IP & proxies
Call /challenge and /verify from the browser, directly against https://api.botproof.app. Only the public site key travels to the page; your secret key stays on your server and is used solely by /siteverify. Tell us your site's origin when you create it so it is added to the CORS allowlist.
If you instead proxy those two calls through your own backend, every visitor reaches us as your server's outbound address. IP reputation, velocity, ASN signals and the geographic reports in Sentinel then key off your datacenter rather than the user — and a shared datacenter address is itself a bot signal, so legitimate traffic starts drawing CHALLENGE_REQUIRED.
- Do not send
CF-Connecting-IP. Our API sits behind Cloudflare, which rejects any inbound request already carrying that reserved header with an HTMLerror code: 1000403. It is not a workaround. - Do not send raw
X-Forwarded-Forand expect it to be trusted — it is spoofable from any caller, so we ignore it. - If a same-origin proxy is a hard requirement for your integration, contact us. There is a secret-authenticated declared-IP header for first-party integrations; it is disabled by default and enabled per tenant after review.
Verdicts
- PASSED (score ≥70) — accept the request.
- CHALLENGE_REQUIRED (40–69) — show accessible code / WebAuthn fallback.
- REJECTED (<40) — block; inspect
reason-codes/ Sentinel.
Reason codes (SW-901)
Additive stable codes on verify (reasonCodes) and siteverify (reason-codes). Legacy siteverify error-codes stay reCAPTCHA-compatible. Enumerated in OpenAPI as components.schemas.ReasonCode. Treat unknown codes as informational.
missing-input-secret/invalid-input-secretmissing-input-response/invalid-input-responsetimeout-or-duplicate— token expired or already redeemedinvalid-signature/pow-failed— verify integritylow-confidence-score— score below pass thresholdhostname-mismatch— origin not on the site allow-listautomation/honeypot-triggered/unexpected-environment
Full catalog: docs/REASON-CODES.md and docs/openapi/public-api.yaml → ReasonCode.
OpenAPI
Machine-readable public contract (challenge, verify, siteverify only — no ops/admin): docs/openapi/public-api.yaml. Language samples on this page are built from publicApiContract.ts, which mirrors the YAML example bodies — change both together when the public request shape changes.
Idle: “I'm not a robot” · Verifying: “Verifying… N%” · Success: “Verified” · Escalation: “Extra check required”. Brand footer: SHIELDWALL / Privacy protected.
Interactive states: Playground. Changelog: /changelog.