{"openapi":"3.0.3","info":{"title":"NeuroGuard API","version":"1.1.0","description":"Anti-bot protection using server HTTP signals, bounded browser telemetry, fingerprint reputation, and site/session-bound challenges."},"servers":[{"url":"https://api.neuroguard.pro","description":"Production"},{"url":"http://localhost:3000","description":"Development"}],"tags":[{"name":"Widget"},{"name":"Verification"},{"name":"Challenge"},{"name":"Captcha"},{"name":"Status"}],"paths":{"/api/v1/session":{"post":{"tags":["Widget"],"summary":"Issue a short-lived anonymous widget session","description":"The browser widget calls this endpoint before requesting a token. The signed session is bound to the site key; the one-time nonce is required by /api/v1/token.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["siteKey"],"properties":{"siteKey":{"type":"string"}}}}}},"responses":{"200":{"description":"Signed widget session","content":{"application/json":{"schema":{"type":"object","required":["sessionId","expiresAt","nonce","nonceExpiresAt"],"properties":{"sessionId":{"type":"string","description":"Signed anonymous widget session id."},"expiresAt":{"type":"string","format":"date-time"},"nonce":{"type":"string","description":"One-time signed token request nonce."},"nonceExpiresAt":{"type":"string","format":"date-time"}}}}}},"400":{"description":"Invalid request body"},"403":{"description":"Missing or unregistered production origin"}}}},"/api/v1/token":{"post":{"tags":["Widget"],"summary":"Evaluate trust and optionally issue a verification token","description":"Requires a signed widget session and one-time nonce from /api/v1/session. Challenge responses include the next nonce for retry/answer submission. Tokens are issued only for decision=allow with label=HUMAN.","parameters":[{"name":"siteKey","in":"query","required":true,"description":"Public site key. Also required in the JSON body; the query value enables origin-restricted CORS preflight.","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["siteKey","sessionId","nonce","telemetry"],"properties":{"siteKey":{"type":"string"},"sessionId":{"type":"string","description":"Signed widget session returned by /api/v1/session."},"nonce":{"type":"string","description":"One-time nonce returned by /api/v1/session or previous challenge response."},"telemetry":{"type":"object","additionalProperties":true},"challenge":{"type":"object","additionalProperties":true}}}}}},"responses":{"200":{"description":"Trust decision","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/AllowDecision"},{"$ref":"#/components/schemas/ChallengeDecision"},{"$ref":"#/components/schemas/BlockDecision"}]}}}},"400":{"description":"Invalid request, out-of-range telemetry, missing/invalid widget session, or missing/replayed nonce"},"403":{"description":"Missing or unregistered production origin"},"413":{"description":"Request body exceeds 32 KiB"},"429":{"description":"Rate limit exceeded"}}}},"/api/v1/verify":{"post":{"tags":["Verification"],"summary":"Consume and verify an allow token server-to-server","description":"Token consumption is atomic and single-use. Browser userAgent and widget session are part of session binding; the optional ip field is retained for compatibility and is not a hard token-binding input.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["siteKey","secretKey","token","sessionId","userAgent"],"properties":{"siteKey":{"type":"string"},"secretKey":{"type":"string"},"token":{"type":"string"},"sessionId":{"type":"string","description":"Widget session submitted with the protected form."},"userAgent":{"type":"string"},"ip":{"type":"string"}}}}}},"responses":{"200":{"description":"Verification result"}}}},"/api/v1/challenge":{"get":{"tags":["Challenge"],"summary":"Get a site/session-bound challenge","description":"Requires siteKey query parameter, valid registered Origin/Referer, and compatible Fetch Metadata. Hidden answers and slider targets are never returned."},"post":{"tags":["Challenge"],"summary":"Consume and validate a site/session-bound challenge answer"}},"/api/v1/captcha/verify":{"post":{"tags":["Captcha"],"summary":"Verify a third-party captcha token","description":"Requires the current widget session and one-time nonce. A low provider score returns decision=block and never creates a NeuroGuard verification token."}},"/api/v1/shield-js":{"get":{"tags":["Widget"],"summary":"Serve shield.js widget","description":"Serves the latest widget by default, or an immutable release when `version=v1.3.0` is provided. Unsupported versions return 404 and are not cached.","parameters":[{"name":"version","in":"query","required":false,"description":"Optional widget version, with or without `v` prefix. Current supported value: `v1.3.0`.","schema":{"type":"string","enum":["v1.3.0","1.3.0"]}}],"responses":{"200":{"description":"JavaScript widget","headers":{"X-NeuroGuard-Version":{"schema":{"type":"string"},"description":"Served widget version."},"X-NeuroGuard-Compatibility":{"schema":{"type":"string"},"description":"API/telemetry/session compatibility contract."},"Cache-Control":{"schema":{"type":"string"},"description":"Short cache for latest, immutable cache for explicit version."}},"content":{"application/javascript":{"schema":{"type":"string"}}}},"404":{"description":"Requested widget version is unavailable"}}}},"/api/v1/status":{"get":{"tags":["Status"],"summary":"Public API status"}},"/api/health/live":{"get":{"tags":["Status"],"summary":"Liveness probe"}},"/api/health/ready":{"get":{"tags":["Status"],"summary":"DB and Redis readiness probe"}},"/api/health":{"get":{"tags":["Status"],"summary":"Backward-compatible readiness alias"}}},"components":{"schemas":{"AllowDecision":{"type":"object","required":["decision","token","expiresAt","score","label","challengePassed"],"properties":{"decision":{"type":"string","enum":["allow"]},"token":{"type":"string"},"expiresAt":{"type":"string","format":"date-time"},"score":{"type":"integer","minimum":0,"maximum":100},"label":{"type":"string","enum":["HUMAN"]},"challengePassed":{"type":"boolean"}}},"ChallengeDecision":{"type":"object","required":["decision","score","label"],"properties":{"decision":{"type":"string","enum":["challenge"]},"needChallenge":{"type":"boolean"},"needCaptcha":{"type":"boolean"},"score":{"type":"integer","minimum":0,"maximum":100},"label":{"type":"string","enum":["HUMAN","BOT","AI_AGENT"]},"challenge":{"type":"object","description":"Public challenge fields only. Correct answers, sliderTarget, sliderTolerance, and internal thresholds are omitted."}}},"BlockDecision":{"type":"object","required":["decision","score","label","reason"],"properties":{"decision":{"type":"string","enum":["block"]},"score":{"type":"integer","minimum":0,"maximum":100},"label":{"type":"string","enum":["HUMAN","BOT","AI_AGENT"]},"reason":{"type":"string"},"challengePassed":{"type":"boolean"}}},"TrustDecision":{"type":"string","enum":["allow","challenge","block"]}}}}