lorica-sdk 0.1.0__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.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Lorica Labs, Inc.
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.
@@ -0,0 +1,133 @@
1
+ Metadata-Version: 2.4
2
+ Name: lorica-sdk
3
+ Version: 0.1.0
4
+ Summary: Python SDK for the Lorica biometric verification API
5
+ Author-email: Tristan Linardos <tristan@loricaapi.com>
6
+ License: MIT
7
+ Project-URL: Homepage, https://loricaapi.com
8
+ Project-URL: Documentation, https://loricaapi.com/docs
9
+ Project-URL: Repository, https://github.com/LeoMirren/lorica
10
+ Project-URL: Demo, https://loricaapi.com/demo
11
+ Keywords: biometric,verification,identity,liveness,jwt,api
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.8
17
+ Classifier: Programming Language :: Python :: 3.9
18
+ Classifier: Programming Language :: Python :: 3.10
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Topic :: Security
22
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
23
+ Requires-Python: >=3.8
24
+ Description-Content-Type: text/markdown
25
+ License-File: LICENSE
26
+ Dynamic: license-file
27
+
28
+ # lorica
29
+
30
+ Python SDK for the [Lorica](https://loricaapi.com) biometric verification API.
31
+
32
+ Prove a human did it. One API call before any high-risk action. 16 security layers. Under 2 seconds. $0.05.
33
+
34
+ ## Install
35
+
36
+ ```bash
37
+ pip install lorica
38
+ ```
39
+
40
+ ## Quick Start
41
+
42
+ ```python
43
+ from lorica import LoricaClient
44
+
45
+ client = LoricaClient("lrca_live_...")
46
+
47
+ # Enroll a user (once)
48
+ client.enroll(user_id="usr_123", image=face_base64)
49
+
50
+ # Verify before any high-risk action
51
+ result = client.verify(
52
+ user_id="usr_123",
53
+ image=face_base64,
54
+ action_context="wire_transfer_50k",
55
+ liveness_mode="passive" # or "dual_frame" or "motion"
56
+ )
57
+
58
+ if result["match"]:
59
+ jwt = result["token"] # signed proof of authorization
60
+ process_transfer(jwt)
61
+ else:
62
+ block_action(result["rejection_reason"])
63
+ ```
64
+
65
+ ## Read Image from File
66
+
67
+ ```python
68
+ image_b64 = LoricaClient.image_from_file("photo.jpg")
69
+ client.verify(user_id="usr_123", image=image_b64, action_context="trade")
70
+ ```
71
+
72
+ ## Multi-Party Verification
73
+
74
+ ```python
75
+ result = client.verify_multi(
76
+ verifications=[
77
+ {"user_id": "trader_001", "image": trader_face, "role": "trader"},
78
+ {"user_id": "risk_mgr", "image": manager_face, "role": "risk_manager"},
79
+ ],
80
+ action_context="otc_block_trade",
81
+ require_all=True
82
+ )
83
+ # result["all_verified"] == True, result["token"] = single JWT
84
+ ```
85
+
86
+ ## Pre-Action Identity Lock
87
+
88
+ ```python
89
+ lock = client.create_lock(
90
+ user_id="usr_123",
91
+ image=face_base64,
92
+ action_context="multi_venue_execution",
93
+ duration_seconds=120
94
+ )
95
+ # lock["session_id"], lock["expires_at"]
96
+
97
+ client.lock_heartbeat(lock["session_id"]) # extend
98
+ client.lock_revoke(lock["session_id"]) # revoke
99
+ ```
100
+
101
+ ## Analytics
102
+
103
+ ```python
104
+ client.stats(period="30d")
105
+ client.user_report(user_id="usr_123")
106
+ client.billing_usage()
107
+ client.thresholds() # AI-recommended thresholds
108
+ ```
109
+
110
+ ## Error Handling
111
+
112
+ ```python
113
+ from lorica import LoricaClient
114
+ from lorica.client import LoricaError
115
+
116
+ try:
117
+ result = client.verify(user_id="usr_123", image=face)
118
+ except LoricaError as e:
119
+ print(e.status_code) # 404
120
+ print(e.error_code) # "user_not_enrolled"
121
+ print(e.message) # "No enrollment found..."
122
+ ```
123
+
124
+ ## Links
125
+
126
+ - [Documentation](https://loricaapi.com/docs)
127
+ - [Live Demo](https://loricaapi.com/demo)
128
+ - [Security](https://loricaapi.com/security)
129
+ - [Blog](https://loricaapi.com/blog/)
130
+
131
+ ## License
132
+
133
+ MIT
@@ -0,0 +1,106 @@
1
+ # lorica
2
+
3
+ Python SDK for the [Lorica](https://loricaapi.com) biometric verification API.
4
+
5
+ Prove a human did it. One API call before any high-risk action. 16 security layers. Under 2 seconds. $0.05.
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ pip install lorica
11
+ ```
12
+
13
+ ## Quick Start
14
+
15
+ ```python
16
+ from lorica import LoricaClient
17
+
18
+ client = LoricaClient("lrca_live_...")
19
+
20
+ # Enroll a user (once)
21
+ client.enroll(user_id="usr_123", image=face_base64)
22
+
23
+ # Verify before any high-risk action
24
+ result = client.verify(
25
+ user_id="usr_123",
26
+ image=face_base64,
27
+ action_context="wire_transfer_50k",
28
+ liveness_mode="passive" # or "dual_frame" or "motion"
29
+ )
30
+
31
+ if result["match"]:
32
+ jwt = result["token"] # signed proof of authorization
33
+ process_transfer(jwt)
34
+ else:
35
+ block_action(result["rejection_reason"])
36
+ ```
37
+
38
+ ## Read Image from File
39
+
40
+ ```python
41
+ image_b64 = LoricaClient.image_from_file("photo.jpg")
42
+ client.verify(user_id="usr_123", image=image_b64, action_context="trade")
43
+ ```
44
+
45
+ ## Multi-Party Verification
46
+
47
+ ```python
48
+ result = client.verify_multi(
49
+ verifications=[
50
+ {"user_id": "trader_001", "image": trader_face, "role": "trader"},
51
+ {"user_id": "risk_mgr", "image": manager_face, "role": "risk_manager"},
52
+ ],
53
+ action_context="otc_block_trade",
54
+ require_all=True
55
+ )
56
+ # result["all_verified"] == True, result["token"] = single JWT
57
+ ```
58
+
59
+ ## Pre-Action Identity Lock
60
+
61
+ ```python
62
+ lock = client.create_lock(
63
+ user_id="usr_123",
64
+ image=face_base64,
65
+ action_context="multi_venue_execution",
66
+ duration_seconds=120
67
+ )
68
+ # lock["session_id"], lock["expires_at"]
69
+
70
+ client.lock_heartbeat(lock["session_id"]) # extend
71
+ client.lock_revoke(lock["session_id"]) # revoke
72
+ ```
73
+
74
+ ## Analytics
75
+
76
+ ```python
77
+ client.stats(period="30d")
78
+ client.user_report(user_id="usr_123")
79
+ client.billing_usage()
80
+ client.thresholds() # AI-recommended thresholds
81
+ ```
82
+
83
+ ## Error Handling
84
+
85
+ ```python
86
+ from lorica import LoricaClient
87
+ from lorica.client import LoricaError
88
+
89
+ try:
90
+ result = client.verify(user_id="usr_123", image=face)
91
+ except LoricaError as e:
92
+ print(e.status_code) # 404
93
+ print(e.error_code) # "user_not_enrolled"
94
+ print(e.message) # "No enrollment found..."
95
+ ```
96
+
97
+ ## Links
98
+
99
+ - [Documentation](https://loricaapi.com/docs)
100
+ - [Live Demo](https://loricaapi.com/demo)
101
+ - [Security](https://loricaapi.com/security)
102
+ - [Blog](https://loricaapi.com/blog/)
103
+
104
+ ## License
105
+
106
+ MIT
@@ -0,0 +1,6 @@
1
+ """Lorica — Biometric step-up verification API."""
2
+
3
+ from .client import LoricaClient
4
+
5
+ __version__ = "0.1.0"
6
+ __all__ = ["LoricaClient"]
@@ -0,0 +1,335 @@
1
+ """Lorica Python SDK client."""
2
+
3
+ import json
4
+ import base64
5
+ import urllib.request
6
+ import urllib.error
7
+ from typing import Optional
8
+
9
+
10
+ class LoricaError(Exception):
11
+ """Raised when the Lorica API returns an error."""
12
+ def __init__(self, status_code: int, error_code: str, message: str):
13
+ self.status_code = status_code
14
+ self.error_code = error_code
15
+ self.message = message
16
+ super().__init__(f"[{status_code}] {error_code}: {message}")
17
+
18
+
19
+ class LoricaClient:
20
+ """
21
+ Client for the Lorica biometric verification API.
22
+
23
+ Usage:
24
+ from lorica import LoricaClient
25
+
26
+ client = LoricaClient("lrca_live_...")
27
+
28
+ # Enroll a user
29
+ result = client.enroll(user_id="usr_123", image=base64_face)
30
+
31
+ # Verify before a high-risk action
32
+ result = client.verify(
33
+ user_id="usr_123",
34
+ image=base64_face,
35
+ action_context="wire_transfer_50k"
36
+ )
37
+
38
+ if result["match"]:
39
+ jwt = result["token"]
40
+ # proceed with action
41
+ """
42
+
43
+ DEFAULT_URL = "https://lorica-production.up.railway.app"
44
+
45
+ def __init__(self, api_key: str, base_url: Optional[str] = None, timeout: int = 30):
46
+ """
47
+ Initialize the Lorica client.
48
+
49
+ Args:
50
+ api_key: Your Lorica API key (starts with lrca_ or lorica-)
51
+ base_url: API base URL (default: production)
52
+ timeout: Request timeout in seconds (default: 30)
53
+ """
54
+ self.api_key = api_key
55
+ self.base_url = (base_url or self.DEFAULT_URL).rstrip("/")
56
+ self.timeout = timeout
57
+
58
+ def _request(self, method: str, path: str, body: dict = None) -> dict:
59
+ """Make an HTTP request to the Lorica API."""
60
+ url = f"{self.base_url}{path}"
61
+ headers = {
62
+ "X-API-Key": self.api_key,
63
+ "Content-Type": "application/json",
64
+ }
65
+ data = json.dumps(body).encode() if body else None
66
+ req = urllib.request.Request(url, data=data, headers=headers, method=method)
67
+ try:
68
+ with urllib.request.urlopen(req, timeout=self.timeout) as resp:
69
+ return json.loads(resp.read().decode())
70
+ except urllib.error.HTTPError as e:
71
+ try:
72
+ err_body = json.loads(e.read().decode())
73
+ error_code = err_body.get("error_code", "") or ""
74
+ if isinstance(err_body.get("detail"), dict):
75
+ error_code = err_body["detail"].get("error_code", error_code)
76
+ message = err_body["detail"].get("error_message", str(err_body))
77
+ else:
78
+ message = err_body.get("error_message", str(err_body))
79
+ except Exception:
80
+ error_code = "unknown"
81
+ message = str(e)
82
+ raise LoricaError(e.code, error_code, message)
83
+ except urllib.error.URLError as e:
84
+ raise LoricaError(0, "connection_error", str(e.reason))
85
+
86
+ # ── Core ──────────────────────────────────
87
+
88
+ def health(self) -> dict:
89
+ """Check API health status."""
90
+ return self._request("GET", "/health")
91
+
92
+ def enroll(self, user_id: str, image: str, images: list = None) -> dict:
93
+ """
94
+ Enroll a user with a face image.
95
+
96
+ Args:
97
+ user_id: Unique user identifier
98
+ image: Base64-encoded face image (for single enrollment)
99
+ images: List of base64-encoded images (for multi-image enrollment, 2-5)
100
+
101
+ Returns:
102
+ {"success": True, "user_id": "..."}
103
+ """
104
+ body = {"user_id": user_id}
105
+ if images:
106
+ body["images"] = images
107
+ else:
108
+ body["image"] = image
109
+ return self._request("POST", "/enroll", body)
110
+
111
+ def verify(
112
+ self,
113
+ user_id: str,
114
+ image: str,
115
+ action_context: str = "high_risk_action",
116
+ liveness_mode: str = "passive",
117
+ policy_id: str = None,
118
+ token_ttl_seconds: int = None,
119
+ ) -> dict:
120
+ """
121
+ Verify a user before a high-risk action.
122
+
123
+ Args:
124
+ user_id: The enrolled user to verify
125
+ image: Base64-encoded face image
126
+ action_context: What action is being authorized (e.g. "wire_transfer_50k")
127
+ liveness_mode: "passive", "dual_frame", or "motion"
128
+ policy_id: Optional policy to apply
129
+ token_ttl_seconds: Optional JWT TTL override (30-86400)
130
+
131
+ Returns:
132
+ {
133
+ "match": True/False,
134
+ "confidence": 0.94,
135
+ "confidence_level": "high",
136
+ "liveness_score": 0.91,
137
+ "token": "eyJ...",
138
+ "verification_id": "...",
139
+ ...
140
+ }
141
+ """
142
+ body = {
143
+ "user_id": user_id,
144
+ "image": image,
145
+ "action_context": action_context,
146
+ "liveness_mode": liveness_mode,
147
+ }
148
+ if policy_id:
149
+ body["policy_id"] = policy_id
150
+ if token_ttl_seconds:
151
+ body["token_ttl_seconds"] = token_ttl_seconds
152
+ return self._request("POST", "/verify", body)
153
+
154
+ def verify_multi(
155
+ self,
156
+ verifications: list,
157
+ action_context: str = "high_risk_action",
158
+ require_all: bool = True,
159
+ ) -> dict:
160
+ """
161
+ Multi-party verification. Two or more people verify together.
162
+
163
+ Args:
164
+ verifications: List of {"user_id": "...", "image": "...", "role": "..."}
165
+ action_context: What action is being authorized
166
+ require_all: Whether all parties must pass (default: True)
167
+
168
+ Returns:
169
+ {"all_verified": True, "token": "eyJ...", "parties": [...]}
170
+ """
171
+ return self._request("POST", "/verify-multi", {
172
+ "verifications": verifications,
173
+ "action_context": action_context,
174
+ "require_all": require_all,
175
+ })
176
+
177
+ def verify_batch(self, verifications: list) -> dict:
178
+ """
179
+ Batch verification. Process 1-50 verifications at once.
180
+
181
+ Args:
182
+ verifications: List of {"user_id": "...", "image": "...", "action_context": "..."}
183
+
184
+ Returns:
185
+ {"batch_id": "...", "total": 5, "passed": 4, "failed": 1, "results": [...], "token": "eyJ..."}
186
+ """
187
+ return self._request("POST", "/verify/batch", {"verifications": verifications})
188
+
189
+ # ── Locks ─────────────────────────────────
190
+
191
+ def create_lock(
192
+ self,
193
+ user_id: str,
194
+ image: str,
195
+ action_context: str,
196
+ duration_seconds: int = 60,
197
+ ) -> dict:
198
+ """
199
+ Create a pre-action identity lock.
200
+
201
+ Args:
202
+ user_id: The enrolled user
203
+ image: Base64-encoded face image
204
+ action_context: What action the lock covers
205
+ duration_seconds: How long the lock lasts (default: 60)
206
+
207
+ Returns:
208
+ {"session_id": "...", "status": "active", "expires_at": "..."}
209
+ """
210
+ return self._request("POST", "/lock", {
211
+ "user_id": user_id,
212
+ "image": image,
213
+ "action_context": action_context,
214
+ "duration_seconds": duration_seconds,
215
+ })
216
+
217
+ def lock_status(self, session_id: str) -> dict:
218
+ """Check lock status."""
219
+ return self._request("GET", f"/lock/{session_id}")
220
+
221
+ def lock_heartbeat(self, session_id: str) -> dict:
222
+ """Extend a lock."""
223
+ return self._request("POST", f"/lock/{session_id}/heartbeat")
224
+
225
+ def lock_revoke(self, session_id: str, reason: str = None) -> dict:
226
+ """Revoke a lock."""
227
+ body = {}
228
+ if reason:
229
+ body["reason"] = reason
230
+ return self._request("POST", f"/lock/{session_id}/revoke", body or None)
231
+
232
+ # ── Chains ────────────────────────────────
233
+
234
+ def create_chain(self, action_context: str = None, max_verifications: int = 50) -> dict:
235
+ """Create a verification chain."""
236
+ body = {"max_verifications": max_verifications}
237
+ if action_context:
238
+ body["action_context"] = action_context
239
+ return self._request("POST", "/chains", body)
240
+
241
+ def get_chain(self, chain_id: str) -> dict:
242
+ """Get chain status."""
243
+ return self._request("GET", f"/chains/{chain_id}")
244
+
245
+ def close_chain(self, chain_id: str) -> dict:
246
+ """Close a chain and generate chain JWT."""
247
+ return self._request("POST", f"/chains/{chain_id}/close")
248
+
249
+ def chain_jwt(self, chain_id: str) -> dict:
250
+ """Get the JWT for a closed chain."""
251
+ return self._request("GET", f"/chains/{chain_id}/jwt")
252
+
253
+ # ── Analytics ─────────────────────────────
254
+
255
+ def stats(self, period: str = "7d") -> dict:
256
+ """Get aggregate verification stats."""
257
+ return self._request("GET", f"/stats?period={period}")
258
+
259
+ def verifications(self, limit: int = 50, user_id: str = None) -> dict:
260
+ """Get verification history."""
261
+ path = f"/verifications?limit={limit}"
262
+ if user_id:
263
+ path += f"&user_id={user_id}"
264
+ return self._request("GET", path)
265
+
266
+ def user_report(self, user_id: str) -> dict:
267
+ """Get comprehensive user verification report."""
268
+ return self._request("GET", f"/users/{user_id}/report")
269
+
270
+ def billing_usage(self, period: str = None) -> dict:
271
+ """Get billing usage for current or specified period."""
272
+ path = "/billing/usage"
273
+ if period:
274
+ path += f"?period={period}"
275
+ return self._request("GET", path)
276
+
277
+ def thresholds(self) -> dict:
278
+ """Get AI-recommended verification thresholds."""
279
+ return self._request("GET", "/thresholds/recommend")
280
+
281
+ # ── Policies ──────────────────────────────
282
+
283
+ def create_policy(self, policy_id: str, **kwargs) -> dict:
284
+ """Create a verification policy."""
285
+ body = {"policy_id": policy_id, **kwargs}
286
+ return self._request("POST", "/policies", body)
287
+
288
+ def get_policy(self, policy_id: str) -> dict:
289
+ """Get a policy."""
290
+ return self._request("GET", f"/policies/{policy_id}")
291
+
292
+ def update_policy(self, policy_id: str, **kwargs) -> dict:
293
+ """Update a policy."""
294
+ return self._request("PUT", f"/policies/{policy_id}", kwargs)
295
+
296
+ def delete_policy(self, policy_id: str) -> dict:
297
+ """Delete a policy."""
298
+ return self._request("DELETE", f"/policies/{policy_id}")
299
+
300
+ # ── Actions ───────────────────────────────
301
+
302
+ def create_action(self, action_id: str, **kwargs) -> dict:
303
+ """Register a custom action."""
304
+ body = {"action_id": action_id, **kwargs}
305
+ return self._request("POST", "/actions", body)
306
+
307
+ def list_actions(self) -> dict:
308
+ """List all registered actions."""
309
+ return self._request("GET", "/actions")
310
+
311
+ def delete_action(self, action_id: str) -> dict:
312
+ """Delete a custom action."""
313
+ return self._request("DELETE", f"/actions/{action_id}")
314
+
315
+ # ── Webhooks ──────────────────────────────
316
+
317
+ def create_webhook(self, url: str, events: list) -> dict:
318
+ """Register a webhook."""
319
+ return self._request("POST", "/webhooks", {"url": url, "events": events})
320
+
321
+ def list_webhooks(self) -> dict:
322
+ """List all webhooks."""
323
+ return self._request("GET", "/webhooks")
324
+
325
+ def delete_webhook(self, webhook_id: str) -> dict:
326
+ """Delete a webhook."""
327
+ return self._request("DELETE", f"/webhooks/{webhook_id}")
328
+
329
+ # ── Helpers ───────────────────────────────
330
+
331
+ @staticmethod
332
+ def image_from_file(path: str) -> str:
333
+ """Read an image file and return base64-encoded string."""
334
+ with open(path, "rb") as f:
335
+ return base64.b64encode(f.read()).decode()
@@ -0,0 +1,133 @@
1
+ Metadata-Version: 2.4
2
+ Name: lorica-sdk
3
+ Version: 0.1.0
4
+ Summary: Python SDK for the Lorica biometric verification API
5
+ Author-email: Tristan Linardos <tristan@loricaapi.com>
6
+ License: MIT
7
+ Project-URL: Homepage, https://loricaapi.com
8
+ Project-URL: Documentation, https://loricaapi.com/docs
9
+ Project-URL: Repository, https://github.com/LeoMirren/lorica
10
+ Project-URL: Demo, https://loricaapi.com/demo
11
+ Keywords: biometric,verification,identity,liveness,jwt,api
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.8
17
+ Classifier: Programming Language :: Python :: 3.9
18
+ Classifier: Programming Language :: Python :: 3.10
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Topic :: Security
22
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
23
+ Requires-Python: >=3.8
24
+ Description-Content-Type: text/markdown
25
+ License-File: LICENSE
26
+ Dynamic: license-file
27
+
28
+ # lorica
29
+
30
+ Python SDK for the [Lorica](https://loricaapi.com) biometric verification API.
31
+
32
+ Prove a human did it. One API call before any high-risk action. 16 security layers. Under 2 seconds. $0.05.
33
+
34
+ ## Install
35
+
36
+ ```bash
37
+ pip install lorica
38
+ ```
39
+
40
+ ## Quick Start
41
+
42
+ ```python
43
+ from lorica import LoricaClient
44
+
45
+ client = LoricaClient("lrca_live_...")
46
+
47
+ # Enroll a user (once)
48
+ client.enroll(user_id="usr_123", image=face_base64)
49
+
50
+ # Verify before any high-risk action
51
+ result = client.verify(
52
+ user_id="usr_123",
53
+ image=face_base64,
54
+ action_context="wire_transfer_50k",
55
+ liveness_mode="passive" # or "dual_frame" or "motion"
56
+ )
57
+
58
+ if result["match"]:
59
+ jwt = result["token"] # signed proof of authorization
60
+ process_transfer(jwt)
61
+ else:
62
+ block_action(result["rejection_reason"])
63
+ ```
64
+
65
+ ## Read Image from File
66
+
67
+ ```python
68
+ image_b64 = LoricaClient.image_from_file("photo.jpg")
69
+ client.verify(user_id="usr_123", image=image_b64, action_context="trade")
70
+ ```
71
+
72
+ ## Multi-Party Verification
73
+
74
+ ```python
75
+ result = client.verify_multi(
76
+ verifications=[
77
+ {"user_id": "trader_001", "image": trader_face, "role": "trader"},
78
+ {"user_id": "risk_mgr", "image": manager_face, "role": "risk_manager"},
79
+ ],
80
+ action_context="otc_block_trade",
81
+ require_all=True
82
+ )
83
+ # result["all_verified"] == True, result["token"] = single JWT
84
+ ```
85
+
86
+ ## Pre-Action Identity Lock
87
+
88
+ ```python
89
+ lock = client.create_lock(
90
+ user_id="usr_123",
91
+ image=face_base64,
92
+ action_context="multi_venue_execution",
93
+ duration_seconds=120
94
+ )
95
+ # lock["session_id"], lock["expires_at"]
96
+
97
+ client.lock_heartbeat(lock["session_id"]) # extend
98
+ client.lock_revoke(lock["session_id"]) # revoke
99
+ ```
100
+
101
+ ## Analytics
102
+
103
+ ```python
104
+ client.stats(period="30d")
105
+ client.user_report(user_id="usr_123")
106
+ client.billing_usage()
107
+ client.thresholds() # AI-recommended thresholds
108
+ ```
109
+
110
+ ## Error Handling
111
+
112
+ ```python
113
+ from lorica import LoricaClient
114
+ from lorica.client import LoricaError
115
+
116
+ try:
117
+ result = client.verify(user_id="usr_123", image=face)
118
+ except LoricaError as e:
119
+ print(e.status_code) # 404
120
+ print(e.error_code) # "user_not_enrolled"
121
+ print(e.message) # "No enrollment found..."
122
+ ```
123
+
124
+ ## Links
125
+
126
+ - [Documentation](https://loricaapi.com/docs)
127
+ - [Live Demo](https://loricaapi.com/demo)
128
+ - [Security](https://loricaapi.com/security)
129
+ - [Blog](https://loricaapi.com/blog/)
130
+
131
+ ## License
132
+
133
+ MIT
@@ -0,0 +1,10 @@
1
+ LICENSE
2
+ README.md
3
+ pyproject.toml
4
+ lorica/__init__.py
5
+ lorica/client.py
6
+ lorica_sdk.egg-info/PKG-INFO
7
+ lorica_sdk.egg-info/SOURCES.txt
8
+ lorica_sdk.egg-info/dependency_links.txt
9
+ lorica_sdk.egg-info/top_level.txt
10
+ tests/test_client.py
@@ -0,0 +1 @@
1
+ lorica
@@ -0,0 +1,37 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68.0", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "lorica-sdk"
7
+ version = "0.1.0"
8
+ description = "Python SDK for the Lorica biometric verification API"
9
+ readme = "README.md"
10
+ license = {text = "MIT"}
11
+ requires-python = ">=3.8"
12
+ authors = [
13
+ {name = "Tristan Linardos", email = "tristan@loricaapi.com"},
14
+ ]
15
+ keywords = ["biometric", "verification", "identity", "liveness", "jwt", "api"]
16
+ classifiers = [
17
+ "Development Status :: 3 - Alpha",
18
+ "Intended Audience :: Developers",
19
+ "License :: OSI Approved :: MIT License",
20
+ "Programming Language :: Python :: 3",
21
+ "Programming Language :: Python :: 3.8",
22
+ "Programming Language :: Python :: 3.9",
23
+ "Programming Language :: Python :: 3.10",
24
+ "Programming Language :: Python :: 3.11",
25
+ "Programming Language :: Python :: 3.12",
26
+ "Topic :: Security",
27
+ "Topic :: Software Development :: Libraries :: Python Modules",
28
+ ]
29
+
30
+ [project.urls]
31
+ Homepage = "https://loricaapi.com"
32
+ Documentation = "https://loricaapi.com/docs"
33
+ Repository = "https://github.com/LeoMirren/lorica"
34
+ Demo = "https://loricaapi.com/demo"
35
+
36
+ [tool.setuptools.packages.find]
37
+ include = ["lorica*"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,25 @@
1
+ """Basic tests for the Lorica Python SDK."""
2
+ from lorica import LoricaClient
3
+
4
+ def test_init():
5
+ c = LoricaClient("test-key")
6
+ assert c.api_key == "test-key"
7
+ assert c.base_url == LoricaClient.DEFAULT_URL
8
+ assert c.timeout == 30
9
+
10
+ def test_custom_url():
11
+ c = LoricaClient("test-key", base_url="http://localhost:8000")
12
+ assert c.base_url == "http://localhost:8000"
13
+
14
+ def test_health():
15
+ c = LoricaClient("lorica-dev-key-001")
16
+ h = c.health()
17
+ assert h["status"] == "ok"
18
+ assert "version" in h
19
+ print(f"Health: {h['status']} v{h['version']}")
20
+
21
+ if __name__ == "__main__":
22
+ test_init()
23
+ test_custom_url()
24
+ test_health()
25
+ print("All tests passed!")