Validation
When a request hits a protected endpoint, call the validation API to get a decision on whether to allow it.
Overview
For each request to a protected endpoint, your backend should call the validation API and act on the decision:
allow: let the request throughblock: deny the requestredirect: return an interstitial challenge page to the browsernot_matched: the URL didn't match a protected endpoint (treat as allow)
The validation request should include:
- request URL + method
- client IP
- request headers (especially
User-Agent) _centinelcookie (set by the browser script), if present
If you haven't added the browser script yet, start here:
Validate a request
Endpoint
- Method:
POST - URL:
https://validator.centinelanalytica.com/validate
Headers
content-type(string):application/jsonx-api-key(string): Your secret API key (server-only).
Body
url(string): The full URL the user requested.method(string): The request method (GET,POST, etc.).ip(string): The client IP address.referrer(string): Therefererheader.headers(object): The request headers.cookie(string): The_centinelcookie value (used for session tracking across interstitial and continuous protection).
Tips
- IP: if you're behind a CDN/reverse proxy, pass the real client IP (not the proxy IP).
- Headers: include
User-Agent(and any other headers you rely on for request attribution).
Keep x-api-key server-only
Don't call /validate from the browser. The x-api-key is your secret key and must only be used server-side.
Request
curl -X POST 'https://validator.centinelanalytica.com/validate' \
-H 'content-type: application/json' \
-H 'x-api-key: API_KEY' \
-d '{"url": "https://example.com", "method": "GET", "ip": "127.0.0.1", "cookie": "1234567890", "referrer": "https://example.com", "headers": { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36" }}'Response
{
"success": true,
"status_code": 200,
"request_id": "string",
"decision": "allow" | "block" | "redirect" | "not_matched",
"response_html": "string",
"cookies": [{ "name": "string", "value": "string", "path": "string", "domain": "string" }],
"crawler": {
"id": "string",
"name": "string",
"access_allowed": true,
"category": "string",
"rsl_category": "string"
}
}Response fields:
status_code(integer): The HTTP status code your middleware should use when responding to the client. This can be any integer value (e.g., 200, 403, 302).request_id(string): Unique identifier for this validation request, useful for debugging and support.response_htmlandcookiesonly appear with aredirectdecision.response_htmlcontains base64-encoded HTML for the interstitial page.crawleris optional—it only appears when the request is detected as a crawler and crawler metadata is enabled for your tenant.
crawler fields
| Field | Type | Required | Description |
|---|---|---|---|
id | string | Yes | Crawler identifier (UUID). |
name | string | Yes | Crawler name (e.g. Googlebot). |
access_allowed | boolean | Yes | Whether this crawler was allowed (true for whitelisted, false otherwise). |
category | string | No | Human-readable category (e.g. Search Engine, AI Assistant). See Crawler categories. |
rsl_category | string | Yes | Machine-readable category key (e.g. search, ai-input). See Crawler categories. |
Example (crawler detected)
{
"success": true,
"status_code": 200,
"request_id": "req_abc123xyz789",
"decision": "allow",
"crawler": {
"id": "4a7d7c8a-3fcb-4b91-8f7e-3f6ac1f5b6c1",
"name": "Googlebot",
"access_allowed": true,
"category": "Search Engine",
"rsl_category": "search"
}
}Crawler categories
When crawler is present, crawler.category (human-readable) and crawler.rsl_category (machine-readable) can be:
| Category (human-readable) | rsl_category (machine-readable) |
|---|---|
| AI Agent | ai-all |
| AI Assistant | ai-input |
| AI Search | ai-input |
| AI Training | ai-train |
| Search Engine | search |
| Accessibility | all |
| Advertising | all |
| Aggregator | all |
| Archiver | all |
| Feed Reader | all |
| Monitoring | all |
| Other | all |
| Preview | all |
| Research | all |
| Scraper | all |
| Security | all |
| SEO Tool | all |
| Social Media | all |
Handling decisions
Use the decision field to decide how to respond.
Call /validate from your backend with request metadata (url, method, ip, headers, _centinel cookie).
Read decision and act on it (allow, block, redirect, not_matched).
If decision is redirect, set returned cookies and return the decoded HTML.
type ValidateDecision = 'allow' | 'block' | 'redirect' | 'not_matched';
function enforceDecision(decision: ValidateDecision) {
switch (decision) {
case 'allow':
case 'not_matched':
// Let the request through
return;
case 'block':
// Return HTTP 403 (or your preferred deny response)
throw new Error('Blocked by Centinel Analytica');
case 'redirect':
// Return the interstitial HTML to the browser (see below)
return;
}
}Handling redirect
When you get a redirect decision:
- Set cookies from the
cookiesarray on your HTTP response. - Base64-decode
response_html(then UTF-8 decode) and return it as the response body.
Example in Node.js:
const html = Buffer.from(response.response_html, 'base64').toString('utf8');Error response
{
"success": false,
"status_code": 400,
"request_id": "string",
"message": "string"
}You'll only get an error if the validation request was malformed. Check the message for details.
Decisions explained
Allow
The request passed validation. Let it through.
Not matched
The request URL didn't match any protected endpoint. Let it through.
Block
The request failed validation. Block it.
Redirect
Show the interstitial challenge page. This happens when additional verification is needed.
See Handling redirect for decoding and cookie handling.