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
- all request headers
_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 all request headers. The more headers you send, the better the detection accuracy.
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/checkout",
"method": "GET",
"ip": "1.2.3.4",
"cookie": "1234567890",
"referrer": "https://example.com",
"headers": {
"Host": "example.com",
"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",
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
"Accept-Language": "en-US,en;q=0.9",
"Accept-Encoding": "gzip, deflate, br",
"Sec-Fetch-Dest": "document",
"Sec-Fetch-Mode": "navigate",
"Sec-Fetch-Site": "none"
}
}'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" }],
"headers": {
"Header-Name": "header value",
"Another-Header": "another value"
},
"crawler": {
"id": "string",
"name": "string",
"access_allowed": true,
"category": "string",
"rsl_category": "string"
}
}Response fields:
status_code(integer): The HTTP status code to return to the client (e.g.,200,403,302).request_id(string): Unique identifier for this validation request, useful for debugging and support.headers(object): HTTP headers to add to your response to the client. Present on every response regardless of decision.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",
"headers": {
"Header-Name": "header value",
"Another-Header": "another value"
},
"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
Check decision and respond accordingly.
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).
Set the headers from the response on your HTTP response to the client.
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 the cookies from the cookies array on your response, then base64-decode response_html (UTF-8) and return it as the body.
Example in Node.js:
const html = Buffer.from(response.response_html, 'base64').toString('utf8');Handling headers
Every validation response includes a headers object. Set these on the response you send back to the client -- don't hardcode Content-Type or other headers yourself.
For block and redirect decisions, set them on the error or challenge response. For allow and not_matched, set them on the upstream response before passing it through.
Example in Node.js (Express):
// Apply all headers from the validator response
for (const [name, value] of Object.entries(response.headers)) {
res.setHeader(name, value);
}Example in a Cloudflare Worker:
const headers = new Headers();
for (const [name, value] of Object.entries(response.headers)) {
headers.set(name, value);
}
return new Response(body, { status, headers });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.