App

Changelog · — A new IP page with IP history, Monocle Health Alerts, AI traffic in Explorer, and edge access tokens.

Policy API

The Policy API evaluates an encrypted Monocle Assessment against your application's policy and returns a decision: allowed or blocked, with the reason. Use it to enforce your policy on your own backend, without writing the decision logic yourself.

It works like the Decryption API, and adds the policy decision on top of the decrypted assessment.

Decryption APIPolicy API
Endpoint/api/v1/assessment/api/v1/policy
ReturnsThe decrypted assessmentA decision, its reason and the assessment
Decision logicYours, from the assessment fieldsYour policy, as configured in Monocle

With an SDK integration (JavaScript, React or Next.js), your backend calls the Policy API and acts on the decision. With an edge integration (Cloudflare, CloudFront or Fastly), Monocle calls it for you on the paths you enforce; see Assess & Enforce.


Request

Send a POST request to https://decrypt.mcl.spur.us/api/v1/policy, with your Secret Key in a TOKEN HTTP header. Find the Secret Key in the Monocle dashboard for your application.

The body is JSON:

FieldRequiredDescription
assessmentYesThe encrypted Monocle Assessment, as your frontend received it
ipNoThe visitor's public IP address, written the same way as in the assessment: 198.51.100.24, not ::ffff:198.51.100.24. Used by the IP mismatch check, which compares the two exactly.
cpdNoThe client-provided data you set on the assessment. Used by the CPD mismatch check.

The API also accepts the same fields as form data, with the Content-Type header set to exactly application/x-www-form-urlencoded. With a parameter added, such as ; charset=UTF-8 (which fetch and axios add when you send URLSearchParams), the API reads the body as JSON and returns 400. If you can't control the header, send JSON.

Note

Call the Policy API from your backend only. Your Secret Key must never reach the browser.

terminal
curl -X POST "https://decrypt.mcl.spur.us/api/v1/policy" \
-H "Content-Type: application/json" \
-H "TOKEN: <MONOCLE SECRET KEY>" \
--data '{"assessment": "<ENCRYPTED MONOCLE ASSESSMENT>", "ip": "<VISITOR IP>"}'

Response

A successful evaluation returns 200 with a JSON body:

FieldTypeDescription
allowedbooleantrue if the session passes your policy, false if your policy blocks it
reasonstringWhy, such as Assessment allowed or VPNs blocked. See Decision reasons.
decisionIdstringA unique ID for this decision. Log it with your own records.
assessmentobjectThe decrypted Monocle Assessment
json
{
"allowed": false,
"reason": "VPNs blocked",
"assessment": {
"vpn": true,
"proxied": false,
"anon": false,
"rdp": false,
"dch": false,
"cc": "US",
"ip": "198.51.100.24",
"ts": "2026-10-13T15:04:05Z",
"complete": true,
"id": "7c1e5a92-3f4b-4d8e-9a6c-2b0f8e1d4c37",
"sid": "example-form",
"service": "GOOGLE_ONE_VPN",
"ai_agentic": false,
"ai_crawling": false
},
"decisionId": "0b8f3c2e-6d41-4a7b-9e15-3c9a2f7d8e60"
}

Plan differences

  • Blocking needs the Team plan or above. On other plans, a session your policy would block comes back with allowed: true and the reason not entitled to block.
  • The full assessment needs the Pro plan or above. On other plans, assessment holds only id, ip, ipv6, ts and complete. To read the other fields, use the Decryption API.

See Plan availability on the Policies page.


Decision reasons

Monocle checks your policy's rules in the order below and stops at the first one that decides. A rule only applies if your policy turns it on. Assessment is incomplete and CPD mismatch have no switch on the Policies page: whether they apply comes from your Block Strategy.

reasonallowedWhen
Assessment is incompletefalseThe assessment didn't finish collecting before it was sent
Invalid assessment timestampfalseThe assessment's timestamp can't be read, so its age can't be checked
Assessment TTL has expiredfalseThe assessment is older than your policy's Max TTL
IP mismatchfalseThe ip you sent doesn't match the assessment's IPv4 or IPv6 address
CPD mismatchfalseThe cpd you sent doesn't match the assessment's client-provided data
Service in deny listfalseThe session's service is in your policy's Blocked services
Assessment allowed by Service labeltrueThe session's service is in your policy's Exempt services. Later rules aren't checked.
Country in deny listfalseThe session's country is on your policy's Geo Blocklist
Datacenter hosting not allowedfalseDatacenter traffic, with Deny datacenter on
RDP not allowedfalseRemote Desktop Protocol traffic, with Deny RDP on
Anonymous tunnels with Service labels blockedfalseAnonymous traffic from a named service, with Deny anonymous named on
Anonymous connections blockedfalseAnonymous traffic, with Deny anonymous on
VPNs blockedfalseVPN traffic, with Deny VPNs on
Proxied connections blockedfalseProxied traffic, with Deny proxies on
Country not in allowed listfalseYour policy has a Geo Allowlist, and the session's country isn't on it
Assessment allowedtrueNo rule blocked the session
not entitled to blocktrueYour policy would block the session, but your plan doesn't include blocking. See Plan differences.

The IP mismatch check only runs when you send ip. The CPD mismatch check only runs when you send cpd and the assessment carries client-provided data.


Errors

Errors return a plain-text body, not JSON. Branch on the status code, not the body: the bodies below end with a newline, and a 429 can come back as an HTML page.

StatusBodyCause
400Bad request: missing monocle bundleThe request has no assessment
400Bad requestThe body can't be read, or the assessment can't be decrypted with your application's key
401UnauthorizedThe TOKEN header is missing, or isn't a valid Secret Key. A 401 on a key that was working can also come from a problem on Monocle's side, so retry later before you rotate the key.
404Policy not configured for this deploymentYour application has no policy. Set one on the Policies page.
429VariesThe Policy API is briefly at capacity. There's no per-key request limit.
500Internal server errorA problem on Monocle's side

A 400 for an assessment that can't be decrypted usually means it was sent to the wrong application's key, or it was altered or made up. Treat it as a failed check, not as an outage.


Handling failures

Decide what your backend does when the Policy API doesn't answer in time or returns an error other than 400:

  • Fail open: let the session through, and log that no decision was made. This keeps your application available if Monocle can't be reached.
  • Fail closed: block the session, or step it up to extra verification. This is stricter, but an outage then affects your visitors.

Set a timeout on the request either way, so a slow answer doesn't hold up your visitor. Monocle's own edge integrations wait up to five seconds and fail open.

The Node library (@spur.us/monocle-backend) doesn't take a timeout, and its errors don't carry the status code. To do both, call the API with fetch, as in the login example.

A 429 or 5xx is temporary. Handle it the same way as a timeout, and retry with a backoff rather than straight away.


Example: enforce on login

Send the assessment with the login form, call the Policy API from your login handler, and act on the status code and allowed. This example uses fetch (Node 18 or later), so it can set a timeout and read the status code:

javascript
async function checkLogin(req) {
// A dual-stack server reports IPv4 visitors as ::ffff:198.51.100.24.
const ip = (req.ip || "").replace(/^::ffff:/, "");
let res;
try {
res = await fetch("https://decrypt.mcl.spur.us/api/v1/policy", {
method: "POST",
headers: {
"Content-Type": "application/json",
TOKEN: process.env.MONOCLE_SECRET_KEY,
},
body: JSON.stringify({ assessment: req.body.monocle || "", ip }),
signal: AbortSignal.timeout(5000),
});
} catch (error) {
// Timeout or network error: no decision. This example fails open and logs it.
console.warn("Monocle policy unavailable", error);
return "allow";
}
if (res.status === 400) {
// Missing, altered or made-up assessment: treat it as a failed check.
return "step-up";
}
if (!res.ok) {
// 429 or 5xx: temporary. 401 or 404: check the Secret Key and policy.
// No decision either way: fail open and log it.
console.warn("Monocle policy unavailable", res.status);
return "allow";
}
const decision = await res.json();
console.info("Monocle decision", decision.decisionId, decision.reason);
// Blocked sessions get step-up MFA here. Return "block" to refuse them instead.
return decision.allowed ? "allow" : "step-up";
}

A 400 means the assessment is missing, altered or made up, so the example treats it as a failed check. Any other error fails open and is logged, so a timeout or outage doesn't lock visitors out.

req.ip is the address Express sees. Behind a load balancer or proxy, set Express's trust proxy setting so it's the visitor's IP rather than the balancer's.

Log decisionId and reason with your own records, so you can match a login to Monocle's decision later.


FAQ

Can I read my policy configuration through the API?

No. The Policy API only evaluates assessments. View and change your policy on the Policies page.

Can I exempt specific users, such as a synthetic monitoring account?

Not in the policy. Policy rules are based on the network a session comes from, not on who the user is. To exempt specific users, check for them in your own backend before or after you call the Policy API.