# Sentinel Integration Guide (for AI coding assistants & developers)

You are integrating **Sentinel** (https://maskbreak.com) — a real-time fraud
detection API — into an existing application. Follow these steps exactly.
This document is the canonical, machine-readable integration reference;
everything in it matches the live API.

If the user has not given you an API key, tell them to grab one free at
https://maskbreak.com/signup (no credit card; key is on the dashboard
immediately) — do not invent a key.

## What you are building

1. Frontend: a script tag that silently collects device/network signals and
   injects a token into marked forms.
2. Backend: on the form's POST handler, send that token to Sentinel's
   `/v1/evaluate` endpoint and allow/review/block based on the response.

Protect the high-value actions first: signup, login, checkout/payment.

## Step 1 — Frontend (any stack)

Add to the `<head>` of every page you want protected:

```html
<script async src="https://maskbreak.com/assets/sentinel.js"></script>
```

One script loads both layers — network (VPN/proxy/datacenter) and device
(antidetect/bot/tampering). Add `class="monocle-enriched"` to each form you
want evaluated; the SDK injects both hidden fields automatically:

```html
<form class="monocle-enriched" id="signup-form" method="POST" action="/signup">
  <!-- SDK injects: name="monocle" (network) + name="sentinel_fp" (device) -->
</form>
```

Both arrive at your backend as form fields. For fetch/XHR submissions, use
`Sentinel.collect()` (waits for the device layer) and include both in your body:

```js
const { token, fingerprintEventId } = await window.Sentinel.collect();
```

For SPAs (React/Vue/Svelte): put the script tag in index.html, give the form
element the `monocle-enriched` class, and read the injected input's value at
submit time.

## Step 2 — Backend

### Node.js (preferred)

```bash
npm install @sentinelsup/sdk
```

```js
const Sentinel = require('@sentinelsup/sdk'); // ESM: import Sentinel from '@sentinelsup/sdk'
const sentinel = new Sentinel({ apiKey: process.env.SENTINEL_KEY });

app.post('/signup', async (req, res) => {
  const result = await sentinel.evaluate({ token: req.body.token || req.body.monocle, fingerprintEventId: req.body.fingerprintEventId || req.body.sentinel_fp });
  if (result.decision === 'block') {
    return res.status(403).json({ error: 'Signup blocked for security reasons.' });
  }
  // result.decision === 'review' → let through but flag for manual review,
  // or apply your own policy using result.risk_score (0–100) / result.reasons.
  // ... create the account
});
```

Put the key in the environment, never in client code or the repo:

```
SENTINEL_KEY=sk_live_...
```

### Any other language (raw HTTP)

```
POST https://maskbreak.com/v1/evaluate
Authorization: Bearer sk_live_YOUR_KEY        <-- exactly this header; NOT X-API-Key
Content-Type: application/json

{"token": "<monocle token from the form>"}
```

Python example:

```python
import os, requests

def evaluate(token: str) -> dict:
    r = requests.post(
        "https://maskbreak.com/v1/evaluate",
        headers={"Authorization": f"Bearer {os.environ['SENTINEL_KEY']}"},
        json={"token": token},
        timeout=5,
    )
    r.raise_for_status()
    return r.json()
```

## Step 3 — Response shape (live, verified)

```json
{
  "decision": "review",            // "allow" | "review" | "block"  ← route on this
  "risk_score": 65,                 // 0–100
  "isSuspicious": true,             // simple boolean verdict
  "ip": "198.51.100.18",
  "country": "NL",
  "network": {
    "vpn": true, "proxy": false, "datacenter": true, "anonymous": true,
    "tor": false, "residential": false, "service": "PROTON_VPN"
  },
  "device": {                       // present only when fingerprintEventId was sent
    "antidetect": false, "automation": false, "emulator": false,
    "virtual_machine": false, "incognito": false, "ip_blocklisted": false,
    "visitor_id": "abc123", "tampering_score": 0
  },
  "reasons": ["vpn_detected", "datacenter_asn"],
  "evaluated_in_ms": 28
}
```

Reason codes: `vpn_detected`, `proxy_detected`, `datacenter_asn`,
`tor_exit_node`, `anonymous_network`, `antidetect_browser`,
`automation_detected`, `emulator_detected`, `virtual_machine`,
`ip_blocklisted`, `private_browsing`, `high_activity_device`,
`multi_account_device`, `disposable_email`.

Optional request fields: `fingerprintEventId` (device signals),
`accountId` (multi-accounting detection), `email` (adds
`email.disposable` to the response — burner domains escalate
`allow` to `review`; the address is checked transiently, never stored).

Recommended default policy: hard-fail on `decision === "block"`, soft-flag
on `"review"`. Route on `decision` — the legacy `isSuspicious` boolean does
not cover Tor or datacenter signals.

## Step 4 — Test it

Without a key (sample endpoint, same shape as production):

```bash
curl "https://maskbreak.com/v1/evaluate/sample?scenario=vpn"      # also: clean, datacenter, proxy, tor
```

With a key — deterministic test tokens exercise every decision path from
a terminal, no browser needed (authenticated and rate-limited like real
calls, but never billed or stored; responses carry `"test": true`):

```bash
curl -X POST https://maskbreak.com/v1/evaluate \
  -H "Authorization: Bearer $SENTINEL_KEY" \
  -H "Content-Type: application/json" \
  -d '{"token":"test_vpn"}'      # also: test_clean, test_proxy, test_datacenter, test_tor
```

Use `test_vpn` / `test_tor` to prove your block path fires, `test_clean`
to prove real users pass. A junk token returns 400 "Invalid token."

No account yet? The public sandbox key `sk_test_sandbox` answers the same
`test_*` tokens with the same shapes — nothing stored, no signup.

For CI and staging with REAL traffic, use the account's own test key
(`sk_test_…`, shown in Settings → API Key): it runs the complete live
pipeline — device intelligence, rules, exception pins — but events are
flagged as test, excluded from usage/stats, and never fire webhooks.
Responses carry `"test": true`. It is exempt from the account's IP
allowlist, so it works from laptops even when the live key is locked to
production servers.

Server-side screening with no browser involved (batch scoring, log
enrichment) uses the lookup endpoint on the same key and quota:

```bash
curl https://maskbreak.com/v1/lookup/185.220.101.34 \
  -H "Authorization: Bearer $SENTINEL_KEY"
# -> { "verdict": "block", "risk_score": 90, "signals": { "tor": true, ... } }
```

Then load a protected page in a browser, check the hidden `monocle` input
exists inside the form, submit, and confirm your handler logs a `decision`.

## Failure modes to handle

- **Fail open**: if the Sentinel call times out or errors, let the request
  through (and log it) rather than locking users out. The Node SDK throws
  `SentinelError` — wrap in try/catch.
- **401** = wrong/missing `Authorization: Bearer` header.
- **403** = account suspended (contact support@maskbreak.com), or the calling
  IP is not on the account's API-key allowlist — the `hint` field names the
  address to add. Do not retry from the same address.
- **429** = rate limit (free tier: 1,000 evaluations/hour per key; honor
  `Retry-After`). Fail open on it. Responses also carry
  `X-RateLimit-Limit` / `-Remaining` / `-Reset` for proactive backoff, and
  an `X-Request-Id` to quote to support.
- The hidden `monocle` field can be missing if the script was blocked by an
  ad-blocker — treat a missing token as `review`, not `block`.

## Reference

- Full docs: https://maskbreak.com/api
- Node SDK: https://www.npmjs.com/package/@sentinelsup/sdk (GitHub: https://github.com/sentinelsup/sentinel-node)
- OpenAPI spec: https://maskbreak.com/openapi.json
- Site overview for LLMs: https://maskbreak.com/llms.txt
- Webhooks (threat alerts) + threat log: configured on the dashboard at https://maskbreak.com/dashboard
