Centinel AnalyticaCentinel Analytica

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 through
  • block: deny the request
  • redirect: return an interstitial challenge page to the browser
  • not_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
  • _centinel cookie (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/json
  • x-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): The referer header.
  • headers (object): The request headers.
  • cookie (string): The _centinel cookie 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_html appears with both block and redirect decisions and is always base64-encoded. For block it contains the deny page; for redirect it contains the interstitial challenge page.
  • cookies can appear on any decision (e.g. to set _centinel on allow, or challenge-state cookies on redirect).
  • crawler is optional; it only appears when the request is detected as a crawler and crawler metadata is enabled for your tenant.

crawler fields

FieldTypeRequiredDescription
idstringYesCrawler identifier (UUID).
namestringYesCrawler name (e.g. Googlebot).
access_allowedbooleanYesWhether this crawler was allowed (true for whitelisted, false otherwise).
categorystringNoHuman-readable category (e.g. Search Engine, AI Assistant). See Crawler categories.
rsl_categorystringYesMachine-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 Agentai-all
AI Assistantai-input
AI Searchai-input
AI Trainingai-train
Search Enginesearch
Accessibilityall
Advertisingall
Aggregatorall
Archiverall
Feed Readerall
Monitoringall
Otherall
Previewall
Researchall
Scraperall
Securityall
SEO Toolall
Social Mediaall

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 block or redirect, base64-decode response_html and return it as the body (with 403 for block, 200 for redirect by default).

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 with base64-decoded response_html as the body (see below)
      return;
    case 'redirect':
      // Return HTTP 200 with base64-decoded response_html as the body (see below)
      return;
  }
}

Handling block and redirect

Both decisions return a base64-encoded response_html. Decode it as UTF-8 and return it as the response body, together with any cookies from the cookies array and headers from the headers object. Use status 403 for block and 200 for redirect (or honour status_code if the validator returns one).

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.

On this page