sentinelsup 0.1.0__py3-none-any.whl

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.
sentinel/__init__.py ADDED
@@ -0,0 +1,156 @@
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(api_key=os.environ["SENTINEL_KEY"])
8
+ result = s.evaluate(token=request.json["sentinelToken"])
9
+ if result.is_suspicious:
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, request
18
+
19
+ DEFAULT_ENDPOINT = "https://sntlhq.com"
20
+ DEFAULT_TIMEOUT = 5.0
21
+ __version__ = "0.1.0"
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
+ asn: Optional[str] = None
47
+ asn_org: Optional[str] = None
48
+ network: Dict[str, Any] = field(default_factory=dict)
49
+ device: Dict[str, Any] = field(default_factory=dict)
50
+ reasons: list = field(default_factory=list)
51
+ raw: Dict[str, Any] = field(default_factory=dict)
52
+
53
+ @property
54
+ def is_suspicious(self) -> bool:
55
+ """True if the decision is anything other than 'allow'."""
56
+ return self.decision is not None and self.decision != "allow"
57
+
58
+ @property
59
+ def is_blocked(self) -> bool:
60
+ return self.decision == "block"
61
+
62
+
63
+ class Sentinel:
64
+ """Sentinel API client. Pass an API key from https://sntlhq.com/dashboard."""
65
+
66
+ def __init__(
67
+ self,
68
+ api_key: Optional[str] = None,
69
+ endpoint: str = DEFAULT_ENDPOINT,
70
+ timeout: float = DEFAULT_TIMEOUT,
71
+ ) -> None:
72
+ api_key = api_key or os.environ.get("SENTINEL_API_KEY")
73
+ if not api_key or not isinstance(api_key, str):
74
+ raise SentinelError(
75
+ "Sentinel: api_key is required. "
76
+ "Pass it explicitly or set SENTINEL_API_KEY. "
77
+ "Get one free at https://sntlhq.com/signup"
78
+ )
79
+ self.api_key = api_key
80
+ self.endpoint = endpoint.rstrip("/")
81
+ self.timeout = timeout
82
+
83
+ def evaluate(
84
+ self,
85
+ token: str,
86
+ fingerprint_event_id: Optional[str] = None,
87
+ ) -> EvaluateResult:
88
+ """Evaluate a visitor session for fraud signals.
89
+
90
+ Args:
91
+ token: Sentinel client-side token from the frontend SDK.
92
+ fingerprint_event_id: Optional Fingerprint event id for device signals.
93
+
94
+ Returns:
95
+ EvaluateResult with decision, risk_score, network, device, and reasons.
96
+
97
+ Raises:
98
+ SentinelError: on network failure, timeout, or non-2xx response.
99
+ """
100
+ if not token or not isinstance(token, str):
101
+ raise SentinelError(
102
+ "Sentinel.evaluate: token (client-side Sentinel token) is required"
103
+ )
104
+
105
+ payload: Dict[str, Any] = {"token": token}
106
+ if fingerprint_event_id:
107
+ payload["fingerprintEventId"] = fingerprint_event_id
108
+
109
+ body = json.dumps(payload).encode("utf-8")
110
+ req = request.Request(
111
+ f"{self.endpoint}/v1/evaluate",
112
+ data=body,
113
+ method="POST",
114
+ headers={
115
+ "Authorization": f"Bearer {self.api_key}",
116
+ "Content-Type": "application/json",
117
+ "User-Agent": f"sentinel-python/{__version__}",
118
+ },
119
+ )
120
+
121
+ try:
122
+ with request.urlopen(req, timeout=self.timeout) as resp:
123
+ raw = resp.read().decode("utf-8")
124
+ data = json.loads(raw) if raw else {}
125
+ except error.HTTPError as e:
126
+ try:
127
+ err_body = json.loads(e.read().decode("utf-8"))
128
+ except Exception:
129
+ err_body = {}
130
+ msg = err_body.get("error") if isinstance(err_body, dict) else None
131
+ raise SentinelError(
132
+ f"Sentinel: API returned {e.code}"
133
+ + (f" — {msg}" if msg else ""),
134
+ status=e.code,
135
+ body=err_body,
136
+ ) from None
137
+ except error.URLError as e:
138
+ raise SentinelError(f"Sentinel: network error — {e.reason}") from None
139
+ except Exception as e:
140
+ raise SentinelError(f"Sentinel: unexpected error — {e}") from None
141
+
142
+ return EvaluateResult(
143
+ decision=data.get("decision"),
144
+ risk_score=data.get("risk_score"),
145
+ ip=data.get("ip"),
146
+ country=data.get("country"),
147
+ asn=data.get("asn"),
148
+ asn_org=data.get("asn_org"),
149
+ network=data.get("network") or {},
150
+ device=data.get("device") or {},
151
+ reasons=data.get("reasons") or [],
152
+ raw=data,
153
+ )
154
+
155
+
156
+ __all__ = ["Sentinel", "SentinelError", "EvaluateResult", "__version__"]
@@ -0,0 +1,177 @@
1
+ Metadata-Version: 2.4
2
+ Name: sentinelsup
3
+ Version: 0.1.0
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>
10
+ License: MIT
11
+ License-File: LICENSE
12
+ Keywords: antidetect-browser,api,bot-detection,device-fingerprinting,fraud-detection,proxy-detection,sentinel,vpn-detection
13
+ Classifier: Development Status :: 4 - Beta
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: License :: OSI Approved :: MIT License
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3 :: Only
18
+ Classifier: Programming Language :: Python :: 3.8
19
+ Classifier: Programming Language :: Python :: 3.9
20
+ Classifier: Programming Language :: Python :: 3.10
21
+ Classifier: Programming Language :: Python :: 3.11
22
+ Classifier: Programming Language :: Python :: 3.12
23
+ Classifier: Programming Language :: Python :: 3.13
24
+ Classifier: Topic :: Internet :: WWW/HTTP
25
+ Classifier: Topic :: Security
26
+ Classifier: Typing :: Typed
27
+ Requires-Python: >=3.8
28
+ Description-Content-Type: text/markdown
29
+
30
+ # Sentinel Python SDK
31
+
32
+ Real-time fraud, VPN, proxy, and bot detection — free tier, sub-40ms global response.
33
+
34
+ Zero dependencies. Just the standard library.
35
+
36
+ ## Install
37
+
38
+ ```bash
39
+ pip install sentinelsup
40
+ ```
41
+
42
+ ## Quick start
43
+
44
+ ```python
45
+ import os
46
+ from sentinel import Sentinel
47
+
48
+ s = Sentinel(api_key=os.environ["SENTINEL_API_KEY"])
49
+
50
+ result = s.evaluate(token=request.json["sentinelToken"])
51
+
52
+ if result.is_suspicious:
53
+ return abort(403, "Sentinel flagged this session")
54
+
55
+ print(result.decision) # 'allow' | 'review' | 'block'
56
+ print(result.risk_score) # 0..100
57
+ print(result.network) # {'vpn': True, 'proxy': False, 'datacenter': True, ...}
58
+ print(result.reasons) # ['ip_in_known_vpn_range', 'datacenter_asn', ...]
59
+ ```
60
+
61
+ ## Get your API key
62
+
63
+ Sign up at [sntlhq.com/signup](https://sntlhq.com/signup) — free, no credit card.
64
+
65
+ ## Flask example — block VPN/proxy signups
66
+
67
+ ```python
68
+ from flask import Flask, request, abort, jsonify
69
+ from sentinel import Sentinel, SentinelError
70
+
71
+ app = Flask(__name__)
72
+ sentinel = Sentinel() # reads SENTINEL_API_KEY from env
73
+
74
+ @app.route("/signup", methods=["POST"])
75
+ def signup():
76
+ data = request.get_json()
77
+ try:
78
+ result = sentinel.evaluate(token=data["sentinelToken"])
79
+ except SentinelError as e:
80
+ # Fail open OR fail closed — your call. Logged either way.
81
+ app.logger.warning("Sentinel error: %s", e)
82
+ result = None
83
+
84
+ if result and result.is_blocked:
85
+ abort(403, "Signup blocked")
86
+
87
+ # ... your normal signup flow
88
+ return jsonify({"ok": True})
89
+ ```
90
+
91
+ ## Django example — middleware for high-value endpoints
92
+
93
+ ```python
94
+ from django.http import JsonResponse
95
+ from sentinel import Sentinel
96
+
97
+ sentinel = Sentinel() # reads SENTINEL_API_KEY from env
98
+
99
+ class FraudCheckMiddleware:
100
+ def __init__(self, get_response):
101
+ self.get_response = get_response
102
+
103
+ def __call__(self, request):
104
+ if request.path.startswith("/api/checkout"):
105
+ token = request.META.get("HTTP_X_SENTINEL_TOKEN")
106
+ if token:
107
+ try:
108
+ result = sentinel.evaluate(token=token)
109
+ if result.is_blocked:
110
+ return JsonResponse({"error": "blocked"}, status=403)
111
+ except Exception:
112
+ pass # fail open
113
+ return self.get_response(request)
114
+ ```
115
+
116
+ ## Configuration
117
+
118
+ ```python
119
+ Sentinel(
120
+ api_key="sk_live_...", # required (or via SENTINEL_API_KEY env var)
121
+ endpoint="https://sntlhq.com", # override for testing
122
+ timeout=5.0, # seconds
123
+ )
124
+ ```
125
+
126
+ ## Response shape
127
+
128
+ ```python
129
+ @dataclass
130
+ class EvaluateResult:
131
+ decision: str | None # 'allow' | 'review' | 'block'
132
+ risk_score: int | None # 0..100
133
+ ip: str | None
134
+ country: str | None # ISO-2
135
+ asn: str | None # 'AS16509'
136
+ asn_org: str | None # 'Amazon.com, Inc.'
137
+ network: dict # {vpn, proxy, datacenter, anonymous, residential}
138
+ device: dict # {headless, automation, fingerprint_age_days}
139
+ reasons: list[str]
140
+ raw: dict # full upstream response
141
+
142
+ is_suspicious: bool # True if decision != 'allow'
143
+ is_blocked: bool # True if decision == 'block'
144
+ ```
145
+
146
+ ## Errors
147
+
148
+ All failures raise `SentinelError`. The exception carries `.status` (HTTP code) and `.body` (parsed error body) when available.
149
+
150
+ ```python
151
+ from sentinel import Sentinel, SentinelError
152
+
153
+ try:
154
+ result = sentinel.evaluate(token=tok)
155
+ except SentinelError as e:
156
+ if e.status == 429:
157
+ # back off
158
+ pass
159
+ elif e.status and 400 <= e.status < 500:
160
+ # bad input, won't recover by retrying
161
+ pass
162
+ else:
163
+ # transient — retry once or fail open
164
+ pass
165
+ ```
166
+
167
+ ## Try the API without signing up
168
+
169
+ ```bash
170
+ curl https://sntlhq.com/v1/evaluate/sample?scenario=vpn
171
+ ```
172
+
173
+ Or use the [interactive playground](https://sntlhq.com/api#playground).
174
+
175
+ ## License
176
+
177
+ MIT. See [LICENSE](LICENSE).
@@ -0,0 +1,5 @@
1
+ sentinel/__init__.py,sha256=wL5SAk19iw6NiOtk412o5qF6auLtUOfj7qiUwumcW0o,5265
2
+ sentinelsup-0.1.0.dist-info/METADATA,sha256=AA96z-2OP_RMFNJZ_FWmqC5x1UVMrNW4qz4eJbapIJs,5330
3
+ sentinelsup-0.1.0.dist-info/WHEEL,sha256=mffPy8wBnZQn2VnJUU5jE99KsxaSfiyMHV9Yt0aLVxs,87
4
+ sentinelsup-0.1.0.dist-info/licenses/LICENSE,sha256=jjWOHwHW6MDmLG8090VDWh61uzewugSs3UyHm03_CF4,1104
5
+ sentinelsup-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.30.1
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +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.