sentinelsup 0.2.2__tar.gz → 0.2.3__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,7 +1,7 @@
1
- __pycache__/
2
- *.py[cod]
3
- *.egg-info/
4
- build/
5
- dist/
6
- .venv/
7
- .pytest_cache/
1
+ __pycache__/
2
+ *.py[cod]
3
+ *.egg-info/
4
+ build/
5
+ dist/
6
+ .venv/
7
+ .pytest_cache/
@@ -1,21 +1,21 @@
1
- MIT License
2
-
3
- Copyright (c) 2026 Sentinel Edge Networks LTD
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Sentinel Edge Networks LTD
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -1,12 +1,12 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sentinelsup
3
- Version: 0.2.2
4
- Summary: Sentinel — real-time fraud, VPN, proxy, and bot detection API. Free tier, sub-40ms response.
5
- Project-URL: Homepage, https://sntlhq.com
6
- Project-URL: Documentation, https://sntlhq.com/api
7
- Project-URL: Repository, https://github.com/sentinelsup/sentinel-python
8
- Project-URL: Issues, https://github.com/sentinelsup/sentinel-python/issues
9
- Author-email: Sentinel Edge Networks LTD <support@sntlhq.com>
3
+ Version: 0.2.3
4
+ Summary: Maskbreak — real-time fraud, VPN, proxy, and bot detection API. Free tier, sub-40ms response.
5
+ Project-URL: Homepage, https://maskbreak.com
6
+ Project-URL: Documentation, https://maskbreak.com/api
7
+ Project-URL: Repository, https://github.com/sentinelsup/maskbreak-python
8
+ Project-URL: Issues, https://github.com/sentinelsup/maskbreak-python/issues
9
+ Author-email: Sentinel Edge Networks LTD <support@maskbreak.com>
10
10
  License: MIT
11
11
  License-File: LICENSE
12
12
  Keywords: antidetect-browser,api,bot-detection,device-fingerprinting,fraud-detection,proxy-detection,sentinel,vpn-detection
@@ -27,9 +27,9 @@ Classifier: Typing :: Typed
27
27
  Requires-Python: >=3.8
28
28
  Description-Content-Type: text/markdown
29
29
 
30
- # sentinelsup — Sentinel Python SDK
30
+ # sentinelsup — Maskbreak Python SDK
31
31
 
32
- Official Python SDK for [Sentinel](https://sntlhq.com) — a real-time fraud detection API that flags VPNs, residential proxies, antidetect browsers (Kameleo, GoLogin, Multilogin), Tor exit nodes, and AI bots in under 40 ms.
32
+ Official Python SDK for [Maskbreak](https://maskbreak.com) — a real-time fraud detection API that flags VPNs, residential proxies, antidetect browsers (Kameleo, GoLogin, Multilogin), Tor exit nodes, and AI bots in under 40 ms.
33
33
 
34
34
  [![PyPI](https://img.shields.io/pypi/v/sentinelsup.svg)](https://pypi.org/project/sentinelsup/)
35
35
  [![Python versions](https://img.shields.io/pypi/pyversions/sentinelsup.svg)](https://pypi.org/project/sentinelsup/)
@@ -43,12 +43,12 @@ Using Claude Code, Cursor, Copilot, or any AI coding assistant? Paste this one
43
43
  prompt and it wires the whole integration — frontend script, backend check,
44
44
  env var, and a test:
45
45
 
46
- > Fetch https://sntlhq.com/integrate.md and follow it to add Sentinel fraud
46
+ > Fetch https://maskbreak.com/integrate.md and follow it to add Maskbreak fraud
47
47
  > protection to this app — protect signup, login, and checkout. My API key
48
48
  > is sk_live_YOUR_KEY; put it in a SENTINEL_KEY env var, never in
49
49
  > client-side code. Then show me how to test it.
50
50
 
51
- [`integrate.md`](https://sntlhq.com/integrate.md) is the canonical
51
+ [`integrate.md`](https://maskbreak.com/integrate.md) is the canonical
52
52
  machine-readable integration guide, kept in sync with the live API.
53
53
 
54
54
  ## Install
@@ -57,7 +57,7 @@ machine-readable integration guide, kept in sync with the live API.
57
57
  pip install sentinelsup
58
58
  ```
59
59
 
60
- Python 3.8+. Get a free API key (no credit card) at [sntlhq.com/signup](https://sntlhq.com/signup).
60
+ Python 3.8+. Get a free API key (no credit card) at [maskbreak.com/signup](https://maskbreak.com/signup).
61
61
 
62
62
  ## Quick start
63
63
 
@@ -119,18 +119,18 @@ class EvaluateResult:
119
119
  Try the live sample (same shape, no key needed):
120
120
 
121
121
  ```bash
122
- curl "https://sntlhq.com/v1/evaluate/sample?scenario=vpn"
122
+ curl "https://maskbreak.com/v1/evaluate/sample?scenario=vpn"
123
123
  ```
124
124
 
125
- Or use the [interactive playground](https://sntlhq.com/api#playground).
125
+ Or use the [interactive playground](https://maskbreak.com/api#playground).
126
126
 
127
127
  ## Frontend setup
128
128
 
129
- Add the Sentinel SDK to your frontend. One script loads **both** layers —
129
+ Add the Maskbreak SDK to your frontend. One script loads **both** layers —
130
130
  network (VPN/proxy/datacenter) and device (antidetect/bot/tampering):
131
131
 
132
132
  ```html
133
- <script async src="https://sntlhq.com/assets/sentinel.js"></script>
133
+ <script async src="https://maskbreak.com/assets/sentinel.js"></script>
134
134
 
135
135
  <!-- Add class="monocle-enriched" to any form you want evaluated -->
136
136
  <form class="monocle-enriched" id="signup-form">
@@ -206,12 +206,12 @@ Runnable versions live in [`examples/`](./examples/).
206
206
 
207
207
  ## API
208
208
 
209
- ### `Sentinel(api_key=None, endpoint="https://sntlhq.com", timeout=5.0)`
209
+ ### `Sentinel(api_key=None, endpoint="https://maskbreak.com", timeout=5.0)`
210
210
 
211
211
  | Option | Default | Description |
212
212
  |--------|---------|-------------|
213
213
  | `api_key` | `$SENTINEL_KEY` (falls back to `$SENTINEL_API_KEY`) | Your key starting with `sk_live_` |
214
- | `endpoint` | `https://sntlhq.com` | Override base URL (for testing) |
214
+ | `endpoint` | `https://maskbreak.com` | Override base URL (for testing) |
215
215
  | `timeout` | `5.0` | Per-request timeout in seconds |
216
216
 
217
217
  ### `sentinel.evaluate(token, fingerprint_event_id=None, account_id=None, email=None)`
@@ -265,9 +265,9 @@ except SentinelError as e:
265
265
  ## Rate limits
266
266
 
267
267
  Free tier: **1,000 requests/hour** per API key. No monthly cap, no credit
268
- card. Upgrade at [sntlhq.com](https://sntlhq.com) when you need more.
268
+ card. Upgrade at [maskbreak.com](https://maskbreak.com) when you need more.
269
269
 
270
- ## What Sentinel detects
270
+ ## What Maskbreak detects
271
271
 
272
272
  VPNs (commercial + self-hosted) · residential proxies (Bright Data, IPRoyal,
273
273
  and similar networks) · datacenter IPs · Tor exit nodes · antidetect browsers
@@ -277,10 +277,10 @@ virtual machines · browser tampering.
277
277
 
278
278
  ## Related
279
279
 
280
- - **Node.js SDK** — [`@sentinelsup/sdk`](https://github.com/sentinelsup/sentinel-node) on npm
281
- - **API docs** — [sntlhq.com/api](https://sntlhq.com/api)
282
- - **Free IP lookup tool** — [sntlhq.com/ip-lookup](https://sntlhq.com/ip-lookup)
280
+ - **Node.js SDK** — [`@sentinelsup/sdk`](https://github.com/sentinelsup/maskbreak-node) on npm
281
+ - **API docs** — [maskbreak.com/api](https://maskbreak.com/api)
282
+ - **Free IP lookup tool** — [maskbreak.com/ip-lookup](https://maskbreak.com/ip-lookup)
283
283
 
284
284
  ## License
285
285
 
286
- MIT © [Sentinel Edge Networks LTD](https://sntlhq.com). See [LICENSE](LICENSE).
286
+ MIT © [Sentinel Edge Networks LTD](https://maskbreak.com). See [LICENSE](LICENSE).
@@ -1,6 +1,6 @@
1
- # sentinelsup — Sentinel Python SDK
1
+ # sentinelsup — Maskbreak Python SDK
2
2
 
3
- Official Python SDK for [Sentinel](https://sntlhq.com) — a real-time fraud detection API that flags VPNs, residential proxies, antidetect browsers (Kameleo, GoLogin, Multilogin), Tor exit nodes, and AI bots in under 40 ms.
3
+ Official Python SDK for [Maskbreak](https://maskbreak.com) — a real-time fraud detection API that flags VPNs, residential proxies, antidetect browsers (Kameleo, GoLogin, Multilogin), Tor exit nodes, and AI bots in under 40 ms.
4
4
 
5
5
  [![PyPI](https://img.shields.io/pypi/v/sentinelsup.svg)](https://pypi.org/project/sentinelsup/)
6
6
  [![Python versions](https://img.shields.io/pypi/pyversions/sentinelsup.svg)](https://pypi.org/project/sentinelsup/)
@@ -14,12 +14,12 @@ Using Claude Code, Cursor, Copilot, or any AI coding assistant? Paste this one
14
14
  prompt and it wires the whole integration — frontend script, backend check,
15
15
  env var, and a test:
16
16
 
17
- > Fetch https://sntlhq.com/integrate.md and follow it to add Sentinel fraud
17
+ > Fetch https://maskbreak.com/integrate.md and follow it to add Maskbreak fraud
18
18
  > protection to this app — protect signup, login, and checkout. My API key
19
19
  > is sk_live_YOUR_KEY; put it in a SENTINEL_KEY env var, never in
20
20
  > client-side code. Then show me how to test it.
21
21
 
22
- [`integrate.md`](https://sntlhq.com/integrate.md) is the canonical
22
+ [`integrate.md`](https://maskbreak.com/integrate.md) is the canonical
23
23
  machine-readable integration guide, kept in sync with the live API.
24
24
 
25
25
  ## Install
@@ -28,7 +28,7 @@ machine-readable integration guide, kept in sync with the live API.
28
28
  pip install sentinelsup
29
29
  ```
30
30
 
31
- Python 3.8+. Get a free API key (no credit card) at [sntlhq.com/signup](https://sntlhq.com/signup).
31
+ Python 3.8+. Get a free API key (no credit card) at [maskbreak.com/signup](https://maskbreak.com/signup).
32
32
 
33
33
  ## Quick start
34
34
 
@@ -90,18 +90,18 @@ class EvaluateResult:
90
90
  Try the live sample (same shape, no key needed):
91
91
 
92
92
  ```bash
93
- curl "https://sntlhq.com/v1/evaluate/sample?scenario=vpn"
93
+ curl "https://maskbreak.com/v1/evaluate/sample?scenario=vpn"
94
94
  ```
95
95
 
96
- Or use the [interactive playground](https://sntlhq.com/api#playground).
96
+ Or use the [interactive playground](https://maskbreak.com/api#playground).
97
97
 
98
98
  ## Frontend setup
99
99
 
100
- Add the Sentinel SDK to your frontend. One script loads **both** layers —
100
+ Add the Maskbreak SDK to your frontend. One script loads **both** layers —
101
101
  network (VPN/proxy/datacenter) and device (antidetect/bot/tampering):
102
102
 
103
103
  ```html
104
- <script async src="https://sntlhq.com/assets/sentinel.js"></script>
104
+ <script async src="https://maskbreak.com/assets/sentinel.js"></script>
105
105
 
106
106
  <!-- Add class="monocle-enriched" to any form you want evaluated -->
107
107
  <form class="monocle-enriched" id="signup-form">
@@ -177,12 +177,12 @@ Runnable versions live in [`examples/`](./examples/).
177
177
 
178
178
  ## API
179
179
 
180
- ### `Sentinel(api_key=None, endpoint="https://sntlhq.com", timeout=5.0)`
180
+ ### `Sentinel(api_key=None, endpoint="https://maskbreak.com", timeout=5.0)`
181
181
 
182
182
  | Option | Default | Description |
183
183
  |--------|---------|-------------|
184
184
  | `api_key` | `$SENTINEL_KEY` (falls back to `$SENTINEL_API_KEY`) | Your key starting with `sk_live_` |
185
- | `endpoint` | `https://sntlhq.com` | Override base URL (for testing) |
185
+ | `endpoint` | `https://maskbreak.com` | Override base URL (for testing) |
186
186
  | `timeout` | `5.0` | Per-request timeout in seconds |
187
187
 
188
188
  ### `sentinel.evaluate(token, fingerprint_event_id=None, account_id=None, email=None)`
@@ -236,9 +236,9 @@ except SentinelError as e:
236
236
  ## Rate limits
237
237
 
238
238
  Free tier: **1,000 requests/hour** per API key. No monthly cap, no credit
239
- card. Upgrade at [sntlhq.com](https://sntlhq.com) when you need more.
239
+ card. Upgrade at [maskbreak.com](https://maskbreak.com) when you need more.
240
240
 
241
- ## What Sentinel detects
241
+ ## What Maskbreak detects
242
242
 
243
243
  VPNs (commercial + self-hosted) · residential proxies (Bright Data, IPRoyal,
244
244
  and similar networks) · datacenter IPs · Tor exit nodes · antidetect browsers
@@ -248,10 +248,10 @@ virtual machines · browser tampering.
248
248
 
249
249
  ## Related
250
250
 
251
- - **Node.js SDK** — [`@sentinelsup/sdk`](https://github.com/sentinelsup/sentinel-node) on npm
252
- - **API docs** — [sntlhq.com/api](https://sntlhq.com/api)
253
- - **Free IP lookup tool** — [sntlhq.com/ip-lookup](https://sntlhq.com/ip-lookup)
251
+ - **Node.js SDK** — [`@sentinelsup/sdk`](https://github.com/sentinelsup/maskbreak-node) on npm
252
+ - **API docs** — [maskbreak.com/api](https://maskbreak.com/api)
253
+ - **Free IP lookup tool** — [maskbreak.com/ip-lookup](https://maskbreak.com/ip-lookup)
254
254
 
255
255
  ## License
256
256
 
257
- MIT © [Sentinel Edge Networks LTD](https://sntlhq.com). See [LICENSE](LICENSE).
257
+ MIT © [Sentinel Edge Networks LTD](https://maskbreak.com). See [LICENSE](LICENSE).
@@ -1,48 +1,48 @@
1
- """Django middleware example — score every request to high-value endpoints.
2
-
3
- Add to settings.py MIDDLEWARE:
4
- "yourapp.middleware.SentinelMiddleware",
5
-
6
- Then ensure your frontend forwards the Sentinel token via the
7
- X-Sentinel-Token header (set after the SDK injects it on the client).
8
- """
9
-
10
- import logging
11
-
12
- from django.http import JsonResponse
13
-
14
- from sentinel import Sentinel, SentinelError
15
-
16
- log = logging.getLogger(__name__)
17
- _GUARDED_PATHS = ("/api/checkout", "/api/withdraw", "/api/transfer")
18
-
19
-
20
- class SentinelMiddleware:
21
- def __init__(self, get_response):
22
- self.get_response = get_response
23
- self.sentinel = Sentinel() # reads SENTINEL_API_KEY
24
-
25
- def __call__(self, request):
26
- if not request.path.startswith(_GUARDED_PATHS):
27
- return self.get_response(request)
28
-
29
- token = request.META.get("HTTP_X_SENTINEL_TOKEN")
30
- if not token:
31
- return JsonResponse({"error": "missing X-Sentinel-Token"}, status=400)
32
-
33
- try:
34
- result = self.sentinel.evaluate(token=token)
35
- except SentinelError as e:
36
- log.warning("Sentinel error: %s", e)
37
- # Fail open on infra problems; switch to fail-closed for finance flows.
38
- return self.get_response(request)
39
-
40
- if result.is_blocked:
41
- return JsonResponse(
42
- {"error": "blocked", "risk_score": result.risk_score, "reasons": result.reasons},
43
- status=403,
44
- )
45
-
46
- # Stash on request so the view can read decision/score
47
- request.sentinel = result
48
- return self.get_response(request)
1
+ """Django middleware example — score every request to high-value endpoints.
2
+
3
+ Add to settings.py MIDDLEWARE:
4
+ "yourapp.middleware.SentinelMiddleware",
5
+
6
+ Then ensure your frontend forwards the Sentinel token via the
7
+ X-Sentinel-Token header (set after the SDK injects it on the client).
8
+ """
9
+
10
+ import logging
11
+
12
+ from django.http import JsonResponse
13
+
14
+ from sentinel import Sentinel, SentinelError
15
+
16
+ log = logging.getLogger(__name__)
17
+ _GUARDED_PATHS = ("/api/checkout", "/api/withdraw", "/api/transfer")
18
+
19
+
20
+ class SentinelMiddleware:
21
+ def __init__(self, get_response):
22
+ self.get_response = get_response
23
+ self.sentinel = Sentinel() # reads SENTINEL_API_KEY
24
+
25
+ def __call__(self, request):
26
+ if not request.path.startswith(_GUARDED_PATHS):
27
+ return self.get_response(request)
28
+
29
+ token = request.META.get("HTTP_X_SENTINEL_TOKEN")
30
+ if not token:
31
+ return JsonResponse({"error": "missing X-Sentinel-Token"}, status=400)
32
+
33
+ try:
34
+ result = self.sentinel.evaluate(token=token)
35
+ except SentinelError as e:
36
+ log.warning("Sentinel error: %s", e)
37
+ # Fail open on infra problems; switch to fail-closed for finance flows.
38
+ return self.get_response(request)
39
+
40
+ if result.is_blocked:
41
+ return JsonResponse(
42
+ {"error": "blocked", "risk_score": result.risk_score, "reasons": result.reasons},
43
+ status=403,
44
+ )
45
+
46
+ # Stash on request so the view can read decision/score
47
+ request.sentinel = result
48
+ return self.get_response(request)
@@ -1,46 +1,46 @@
1
- """Flask example — block VPN/proxy signups using Sentinel.
2
-
3
- Run:
4
- pip install flask sentinelsup
5
- export SENTINEL_API_KEY=sk_live_...
6
- python flask_signup_guard.py
7
- """
8
-
9
- import os
10
- from flask import Flask, abort, jsonify, request
11
-
12
- from sentinel import Sentinel, SentinelError
13
-
14
- app = Flask(__name__)
15
- sentinel = Sentinel() # reads SENTINEL_API_KEY
16
-
17
-
18
- @app.route("/signup", methods=["POST"])
19
- def signup() -> object:
20
- payload = request.get_json(force=True) or {}
21
- email = (payload.get("email") or "").strip().lower()
22
- token = payload.get("sentinelToken")
23
-
24
- if not email or not token:
25
- abort(400, "missing email or sentinelToken")
26
-
27
- try:
28
- result = sentinel.evaluate(token=token)
29
- except SentinelError as e:
30
- # Fail open if Sentinel is down — log and continue.
31
- app.logger.warning("Sentinel unavailable: %s", e)
32
- result = None
33
-
34
- if result and result.is_blocked:
35
- return jsonify({"error": "Signup blocked", "reasons": result.reasons}), 403
36
-
37
- if result and result.decision == "review":
38
- # Soft challenge: email verification, manual review, slower onboarding, etc.
39
- return jsonify({"ok": True, "needs_verification": True})
40
-
41
- # Normal signup flow
42
- return jsonify({"ok": True})
43
-
44
-
45
- if __name__ == "__main__":
46
- app.run(port=int(os.environ.get("PORT", 5000)), debug=False)
1
+ """Flask example — block VPN/proxy signups using Sentinel.
2
+
3
+ Run:
4
+ pip install flask sentinelsup
5
+ export SENTINEL_API_KEY=sk_live_...
6
+ python flask_signup_guard.py
7
+ """
8
+
9
+ import os
10
+ from flask import Flask, abort, jsonify, request
11
+
12
+ from sentinel import Sentinel, SentinelError
13
+
14
+ app = Flask(__name__)
15
+ sentinel = Sentinel() # reads SENTINEL_API_KEY
16
+
17
+
18
+ @app.route("/signup", methods=["POST"])
19
+ def signup() -> object:
20
+ payload = request.get_json(force=True) or {}
21
+ email = (payload.get("email") or "").strip().lower()
22
+ token = payload.get("sentinelToken")
23
+
24
+ if not email or not token:
25
+ abort(400, "missing email or sentinelToken")
26
+
27
+ try:
28
+ result = sentinel.evaluate(token=token)
29
+ except SentinelError as e:
30
+ # Fail open if Sentinel is down — log and continue.
31
+ app.logger.warning("Sentinel unavailable: %s", e)
32
+ result = None
33
+
34
+ if result and result.is_blocked:
35
+ return jsonify({"error": "Signup blocked", "reasons": result.reasons}), 403
36
+
37
+ if result and result.decision == "review":
38
+ # Soft challenge: email verification, manual review, slower onboarding, etc.
39
+ return jsonify({"ok": True, "needs_verification": True})
40
+
41
+ # Normal signup flow
42
+ return jsonify({"ok": True})
43
+
44
+
45
+ if __name__ == "__main__":
46
+ app.run(port=int(os.environ.get("PORT", 5000)), debug=False)
@@ -1,48 +1,48 @@
1
- [build-system]
2
- requires = ["hatchling"]
3
- build-backend = "hatchling.build"
4
-
5
- [project]
6
- # NOTE: "sentinel-sdk" on PyPI is squatted by an unrelated third party
7
- # ("NAST0R"). Never publish or document that name — users following it
8
- # would install a stranger's code. "sentinelsup" matches the npm scope
9
- # (@sentinelsup/sdk).
10
- name = "sentinelsup"
11
- version = "0.2.2"
12
- description = "Sentinel — real-time fraud, VPN, proxy, and bot detection API. Free tier, sub-40ms response."
13
- readme = "README.md"
14
- requires-python = ">=3.8"
15
- license = { text = "MIT" }
16
- authors = [
17
- { name = "Sentinel Edge Networks LTD", email = "support@sntlhq.com" }
18
- ]
19
- keywords = [
20
- "fraud-detection", "vpn-detection", "proxy-detection", "bot-detection",
21
- "antidetect-browser", "device-fingerprinting", "sentinel", "api"
22
- ]
23
- classifiers = [
24
- "Development Status :: 4 - Beta",
25
- "Intended Audience :: Developers",
26
- "License :: OSI Approved :: MIT License",
27
- "Programming Language :: Python :: 3",
28
- "Programming Language :: Python :: 3 :: Only",
29
- "Programming Language :: Python :: 3.8",
30
- "Programming Language :: Python :: 3.9",
31
- "Programming Language :: Python :: 3.10",
32
- "Programming Language :: Python :: 3.11",
33
- "Programming Language :: Python :: 3.12",
34
- "Programming Language :: Python :: 3.13",
35
- "Topic :: Internet :: WWW/HTTP",
36
- "Topic :: Security",
37
- "Typing :: Typed",
38
- ]
39
- dependencies = []
40
-
41
- [project.urls]
42
- Homepage = "https://sntlhq.com"
43
- Documentation = "https://sntlhq.com/api"
44
- Repository = "https://github.com/sentinelsup/sentinel-python"
45
- Issues = "https://github.com/sentinelsup/sentinel-python/issues"
46
-
47
- [tool.hatch.build.targets.wheel]
48
- packages = ["sentinel"]
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ # NOTE: "sentinel-sdk" on PyPI is squatted by an unrelated third party
7
+ # ("NAST0R"). Never publish or document that name — users following it
8
+ # would install a stranger's code. "sentinelsup" matches the npm scope
9
+ # (@sentinelsup/sdk).
10
+ name = "sentinelsup"
11
+ version = "0.2.3"
12
+ description = "Maskbreak — real-time fraud, VPN, proxy, and bot detection API. Free tier, sub-40ms response."
13
+ readme = "README.md"
14
+ requires-python = ">=3.8"
15
+ license = { text = "MIT" }
16
+ authors = [
17
+ { name = "Sentinel Edge Networks LTD", email = "support@maskbreak.com" }
18
+ ]
19
+ keywords = [
20
+ "fraud-detection", "vpn-detection", "proxy-detection", "bot-detection",
21
+ "antidetect-browser", "device-fingerprinting", "sentinel", "api"
22
+ ]
23
+ classifiers = [
24
+ "Development Status :: 4 - Beta",
25
+ "Intended Audience :: Developers",
26
+ "License :: OSI Approved :: MIT License",
27
+ "Programming Language :: Python :: 3",
28
+ "Programming Language :: Python :: 3 :: Only",
29
+ "Programming Language :: Python :: 3.8",
30
+ "Programming Language :: Python :: 3.9",
31
+ "Programming Language :: Python :: 3.10",
32
+ "Programming Language :: Python :: 3.11",
33
+ "Programming Language :: Python :: 3.12",
34
+ "Programming Language :: Python :: 3.13",
35
+ "Topic :: Internet :: WWW/HTTP",
36
+ "Topic :: Security",
37
+ "Typing :: Typed",
38
+ ]
39
+ dependencies = []
40
+
41
+ [project.urls]
42
+ Homepage = "https://maskbreak.com"
43
+ Documentation = "https://maskbreak.com/api"
44
+ Repository = "https://github.com/sentinelsup/maskbreak-python"
45
+ Issues = "https://github.com/sentinelsup/maskbreak-python/issues"
46
+
47
+ [tool.hatch.build.targets.wheel]
48
+ packages = ["sentinel"]
@@ -1,208 +1,208 @@
1
- """
2
- Sentinel Python SDK — thin, dependency-free wrapper around the Sentinel
3
- fraud detection API at https://sntlhq.com/v1/evaluate.
4
-
5
- Usage:
6
- from sentinel import Sentinel
7
- s = Sentinel() # reads SENTINEL_KEY or SENTINEL_API_KEY from the env
8
- result = s.evaluate(token=request.json["sentinelToken"])
9
- if result.is_blocked:
10
- abort(403)
11
- """
12
-
13
- import json
14
- import os
15
- from dataclasses import dataclass, field
16
- from typing import Any, Dict, Optional
17
- from urllib import error, parse, request
18
-
19
- DEFAULT_ENDPOINT = "https://sntlhq.com"
20
- DEFAULT_TIMEOUT = 5.0
21
- __version__ = "0.2.1"
22
-
23
-
24
- class SentinelError(Exception):
25
- """Raised on any Sentinel API or transport failure."""
26
-
27
- def __init__(
28
- self,
29
- message: str,
30
- status: Optional[int] = None,
31
- body: Optional[Dict[str, Any]] = None,
32
- ) -> None:
33
- super().__init__(message)
34
- self.status = status
35
- self.body = body or {}
36
-
37
-
38
- @dataclass
39
- class EvaluateResult:
40
- """Structured response from /v1/evaluate."""
41
-
42
- decision: Optional[str] = None
43
- risk_score: Optional[int] = None
44
- ip: Optional[str] = None
45
- country: Optional[str] = None
46
- network: Dict[str, Any] = field(default_factory=dict)
47
- device: Dict[str, Any] = field(default_factory=dict)
48
- reasons: list = field(default_factory=list)
49
- email: Optional[Dict[str, Any]] = None
50
- decision_source: Optional[str] = None
51
- engine_decision: Optional[str] = None
52
- test: bool = False
53
- raw: Dict[str, Any] = field(default_factory=dict)
54
-
55
- @property
56
- def is_suspicious(self) -> bool:
57
- """True if the decision is anything other than 'allow'."""
58
- return self.decision is not None and self.decision != "allow"
59
-
60
- @property
61
- def is_blocked(self) -> bool:
62
- return self.decision == "block"
63
-
64
-
65
- class Sentinel:
66
- """Sentinel API client. Pass an API key from https://sntlhq.com/dashboard."""
67
-
68
- def __init__(
69
- self,
70
- api_key: Optional[str] = None,
71
- endpoint: str = DEFAULT_ENDPOINT,
72
- timeout: float = DEFAULT_TIMEOUT,
73
- ) -> None:
74
- # SENTINEL_KEY is the name every doc surface uses; SENTINEL_API_KEY
75
- # is kept for existing installs that already adopted it.
76
- api_key = api_key or os.environ.get("SENTINEL_KEY") or os.environ.get("SENTINEL_API_KEY")
77
- if not api_key or not isinstance(api_key, str):
78
- raise SentinelError(
79
- "Sentinel: api_key is required. "
80
- "Pass it explicitly or set SENTINEL_KEY. "
81
- "Get one free at https://sntlhq.com/signup"
82
- )
83
- self.api_key = api_key
84
- self.endpoint = endpoint.rstrip("/")
85
- self.timeout = timeout
86
-
87
- def _request(
88
- self,
89
- path: str,
90
- payload: Optional[Dict[str, Any]] = None,
91
- ) -> Dict[str, Any]:
92
- """Shared transport: auth header, timeout, JSON parsing, error mapping."""
93
- body = json.dumps(payload).encode("utf-8") if payload is not None else None
94
- req = request.Request(
95
- f"{self.endpoint}{path}",
96
- data=body,
97
- method="POST" if payload is not None else "GET",
98
- headers={
99
- "Authorization": f"Bearer {self.api_key}",
100
- "Content-Type": "application/json",
101
- "User-Agent": f"sentinel-python/{__version__}",
102
- },
103
- )
104
-
105
- try:
106
- with request.urlopen(req, timeout=self.timeout) as resp:
107
- raw = resp.read().decode("utf-8")
108
- return json.loads(raw) if raw else {}
109
- except error.HTTPError as e:
110
- try:
111
- err_body = json.loads(e.read().decode("utf-8"))
112
- except Exception:
113
- err_body = {}
114
- msg = err_body.get("error") if isinstance(err_body, dict) else None
115
- raise SentinelError(
116
- f"Sentinel: API returned {e.code}"
117
- + (f" — {msg}" if msg else ""),
118
- status=e.code,
119
- body=err_body,
120
- ) from None
121
- except error.URLError as e:
122
- raise SentinelError(f"Sentinel: network error — {e.reason}") from None
123
- except Exception as e:
124
- raise SentinelError(f"Sentinel: unexpected error — {e}") from None
125
-
126
- def evaluate(
127
- self,
128
- token: str,
129
- fingerprint_event_id: Optional[str] = None,
130
- account_id: Optional[str] = None,
131
- email: Optional[str] = None,
132
- ) -> EvaluateResult:
133
- """Evaluate a visitor session for fraud signals.
134
-
135
- Args:
136
- token: Sentinel client-side token from the frontend SDK.
137
- fingerprint_event_id: Optional Fingerprint event id for device signals.
138
- account_id: Optional account/user id — enables multi-accounting
139
- detection (device.linked_accounts / device.multi_account).
140
- email: Optional signup email. Adds ``email.disposable`` to the raw
141
- response (burner domains escalate allow to review). Checked
142
- transiently — never stored or logged.
143
-
144
- Returns:
145
- EvaluateResult with decision, risk_score, network, device, and reasons.
146
-
147
- Raises:
148
- SentinelError: on network failure, timeout, or non-2xx response.
149
- """
150
- if not token or not isinstance(token, str):
151
- raise SentinelError(
152
- "Sentinel.evaluate: token (client-side Sentinel token) is required"
153
- )
154
-
155
- payload: Dict[str, Any] = {"token": token}
156
- if fingerprint_event_id:
157
- payload["fingerprintEventId"] = fingerprint_event_id
158
- if account_id:
159
- payload["accountId"] = account_id
160
- if email:
161
- payload["email"] = email
162
-
163
- data = self._request("/v1/evaluate", payload)
164
-
165
- return EvaluateResult(
166
- decision=data.get("decision"),
167
- risk_score=data.get("risk_score"),
168
- ip=data.get("ip"),
169
- country=data.get("country"),
170
- network=data.get("network") or {},
171
- device=data.get("device") or {},
172
- reasons=data.get("reasons") or [],
173
- email=data.get("email"),
174
- decision_source=data.get("decision_source"),
175
- engine_decision=data.get("engine_decision"),
176
- test=bool(data.get("test")),
177
- raw=data,
178
- )
179
-
180
- def lookup(self, ip: str) -> Dict[str, Any]:
181
- """Look up an arbitrary public IP address — no browser token needed.
182
-
183
- Wraps ``GET /v1/lookup/{ip}``. Useful for batch scoring, log
184
- enrichment, and server-side screening. Shares the per-key hourly
185
- quota with :meth:`evaluate`.
186
-
187
- Args:
188
- ip: Public IPv4 or IPv6 address, e.g. ``"185.220.101.34"``.
189
-
190
- Returns:
191
- The raw response dict: ``verdict`` ('allow' | 'review' | 'block'),
192
- ``risk_score`` (0-100), ``known``, ``signals``
193
- ({vpn, proxied, tor, dch, anon} or None), ``network``
194
- ({asn, org, country, city}), ``latency_ms``. Note ``known: False``
195
- means our feeds hold no data for the IP — it is NOT a clean
196
- guarantee.
197
-
198
- Raises:
199
- SentinelError: on network failure, timeout, or non-2xx response.
200
- """
201
- if not ip or not isinstance(ip, str):
202
- raise SentinelError(
203
- "Sentinel.lookup: ip (public IPv4 or IPv6 address) is required"
204
- )
205
- return self._request(f"/v1/lookup/{parse.quote(ip.strip(), safe='')}")
206
-
207
-
208
- __all__ = ["Sentinel", "SentinelError", "EvaluateResult", "__version__"]
1
+ """
2
+ Sentinel Python SDK — thin, dependency-free wrapper around the Sentinel
3
+ fraud detection API at https://maskbreak.com/v1/evaluate.
4
+
5
+ Usage:
6
+ from sentinel import Sentinel
7
+ s = Sentinel() # reads SENTINEL_KEY or SENTINEL_API_KEY from the env
8
+ result = s.evaluate(token=request.json["sentinelToken"])
9
+ if result.is_blocked:
10
+ abort(403)
11
+ """
12
+
13
+ import json
14
+ import os
15
+ from dataclasses import dataclass, field
16
+ from typing import Any, Dict, Optional
17
+ from urllib import error, parse, request
18
+
19
+ DEFAULT_ENDPOINT = "https://maskbreak.com"
20
+ DEFAULT_TIMEOUT = 5.0
21
+ __version__ = "0.2.3"
22
+
23
+
24
+ class SentinelError(Exception):
25
+ """Raised on any Sentinel API or transport failure."""
26
+
27
+ def __init__(
28
+ self,
29
+ message: str,
30
+ status: Optional[int] = None,
31
+ body: Optional[Dict[str, Any]] = None,
32
+ ) -> None:
33
+ super().__init__(message)
34
+ self.status = status
35
+ self.body = body or {}
36
+
37
+
38
+ @dataclass
39
+ class EvaluateResult:
40
+ """Structured response from /v1/evaluate."""
41
+
42
+ decision: Optional[str] = None
43
+ risk_score: Optional[int] = None
44
+ ip: Optional[str] = None
45
+ country: Optional[str] = None
46
+ network: Dict[str, Any] = field(default_factory=dict)
47
+ device: Dict[str, Any] = field(default_factory=dict)
48
+ reasons: list = field(default_factory=list)
49
+ email: Optional[Dict[str, Any]] = None
50
+ decision_source: Optional[str] = None
51
+ engine_decision: Optional[str] = None
52
+ test: bool = False
53
+ raw: Dict[str, Any] = field(default_factory=dict)
54
+
55
+ @property
56
+ def is_suspicious(self) -> bool:
57
+ """True if the decision is anything other than 'allow'."""
58
+ return self.decision is not None and self.decision != "allow"
59
+
60
+ @property
61
+ def is_blocked(self) -> bool:
62
+ return self.decision == "block"
63
+
64
+
65
+ class Sentinel:
66
+ """Sentinel API client. Pass an API key from https://maskbreak.com/dashboard."""
67
+
68
+ def __init__(
69
+ self,
70
+ api_key: Optional[str] = None,
71
+ endpoint: str = DEFAULT_ENDPOINT,
72
+ timeout: float = DEFAULT_TIMEOUT,
73
+ ) -> None:
74
+ # SENTINEL_KEY is the name every doc surface uses; SENTINEL_API_KEY
75
+ # is kept for existing installs that already adopted it.
76
+ api_key = api_key or os.environ.get("SENTINEL_KEY") or os.environ.get("SENTINEL_API_KEY")
77
+ if not api_key or not isinstance(api_key, str):
78
+ raise SentinelError(
79
+ "Sentinel: api_key is required. "
80
+ "Pass it explicitly or set SENTINEL_KEY. "
81
+ "Get one free at https://maskbreak.com/signup"
82
+ )
83
+ self.api_key = api_key
84
+ self.endpoint = endpoint.rstrip("/")
85
+ self.timeout = timeout
86
+
87
+ def _request(
88
+ self,
89
+ path: str,
90
+ payload: Optional[Dict[str, Any]] = None,
91
+ ) -> Dict[str, Any]:
92
+ """Shared transport: auth header, timeout, JSON parsing, error mapping."""
93
+ body = json.dumps(payload).encode("utf-8") if payload is not None else None
94
+ req = request.Request(
95
+ f"{self.endpoint}{path}",
96
+ data=body,
97
+ method="POST" if payload is not None else "GET",
98
+ headers={
99
+ "Authorization": f"Bearer {self.api_key}",
100
+ "Content-Type": "application/json",
101
+ "User-Agent": f"sentinel-python/{__version__}",
102
+ },
103
+ )
104
+
105
+ try:
106
+ with request.urlopen(req, timeout=self.timeout) as resp:
107
+ raw = resp.read().decode("utf-8")
108
+ return json.loads(raw) if raw else {}
109
+ except error.HTTPError as e:
110
+ try:
111
+ err_body = json.loads(e.read().decode("utf-8"))
112
+ except Exception:
113
+ err_body = {}
114
+ msg = err_body.get("error") if isinstance(err_body, dict) else None
115
+ raise SentinelError(
116
+ f"Sentinel: API returned {e.code}"
117
+ + (f" — {msg}" if msg else ""),
118
+ status=e.code,
119
+ body=err_body,
120
+ ) from None
121
+ except error.URLError as e:
122
+ raise SentinelError(f"Sentinel: network error — {e.reason}") from None
123
+ except Exception as e:
124
+ raise SentinelError(f"Sentinel: unexpected error — {e}") from None
125
+
126
+ def evaluate(
127
+ self,
128
+ token: str,
129
+ fingerprint_event_id: Optional[str] = None,
130
+ account_id: Optional[str] = None,
131
+ email: Optional[str] = None,
132
+ ) -> EvaluateResult:
133
+ """Evaluate a visitor session for fraud signals.
134
+
135
+ Args:
136
+ token: Sentinel client-side token from the frontend SDK.
137
+ fingerprint_event_id: Optional Fingerprint event id for device signals.
138
+ account_id: Optional account/user id — enables multi-accounting
139
+ detection (device.linked_accounts / device.multi_account).
140
+ email: Optional signup email. Adds ``email.disposable`` to the raw
141
+ response (burner domains escalate allow to review). Checked
142
+ transiently — never stored or logged.
143
+
144
+ Returns:
145
+ EvaluateResult with decision, risk_score, network, device, and reasons.
146
+
147
+ Raises:
148
+ SentinelError: on network failure, timeout, or non-2xx response.
149
+ """
150
+ if not token or not isinstance(token, str):
151
+ raise SentinelError(
152
+ "Sentinel.evaluate: token (client-side Sentinel token) is required"
153
+ )
154
+
155
+ payload: Dict[str, Any] = {"token": token}
156
+ if fingerprint_event_id:
157
+ payload["fingerprintEventId"] = fingerprint_event_id
158
+ if account_id:
159
+ payload["accountId"] = account_id
160
+ if email:
161
+ payload["email"] = email
162
+
163
+ data = self._request("/v1/evaluate", payload)
164
+
165
+ return EvaluateResult(
166
+ decision=data.get("decision"),
167
+ risk_score=data.get("risk_score"),
168
+ ip=data.get("ip"),
169
+ country=data.get("country"),
170
+ network=data.get("network") or {},
171
+ device=data.get("device") or {},
172
+ reasons=data.get("reasons") or [],
173
+ email=data.get("email"),
174
+ decision_source=data.get("decision_source"),
175
+ engine_decision=data.get("engine_decision"),
176
+ test=bool(data.get("test")),
177
+ raw=data,
178
+ )
179
+
180
+ def lookup(self, ip: str) -> Dict[str, Any]:
181
+ """Look up an arbitrary public IP address — no browser token needed.
182
+
183
+ Wraps ``GET /v1/lookup/{ip}``. Useful for batch scoring, log
184
+ enrichment, and server-side screening. Shares the per-key hourly
185
+ quota with :meth:`evaluate`.
186
+
187
+ Args:
188
+ ip: Public IPv4 or IPv6 address, e.g. ``"185.220.101.34"``.
189
+
190
+ Returns:
191
+ The raw response dict: ``verdict`` ('allow' | 'review' | 'block'),
192
+ ``risk_score`` (0-100), ``known``, ``signals``
193
+ ({vpn, proxied, tor, dch, anon} or None), ``network``
194
+ ({asn, org, country, city}), ``latency_ms``. Note ``known: False``
195
+ means our feeds hold no data for the IP — it is NOT a clean
196
+ guarantee.
197
+
198
+ Raises:
199
+ SentinelError: on network failure, timeout, or non-2xx response.
200
+ """
201
+ if not ip or not isinstance(ip, str):
202
+ raise SentinelError(
203
+ "Sentinel.lookup: ip (public IPv4 or IPv6 address) is required"
204
+ )
205
+ return self._request(f"/v1/lookup/{parse.quote(ip.strip(), safe='')}")
206
+
207
+
208
+ __all__ = ["Sentinel", "SentinelError", "EvaluateResult", "__version__"]