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 and cookies only appear with a redirect decision. response_html contains base64-encoded HTML for the interstitial page.
  • 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 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.

On this page