zakadi 0.0.1__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,6 @@
1
+ __pycache__/
2
+ *.egg-info/
3
+ dist/
4
+ build/
5
+ .venv/
6
+ .DS_Store
@@ -0,0 +1,3 @@
1
+ ## 0.0.1
2
+
3
+ - Initial release: shared protocol constants of the Zakadi protocol (zakadi.v1) with type annotations. No client yet.
zakadi-0.0.1/LICENSE ADDED
@@ -0,0 +1,5 @@
1
+ Copyright (c) 2026 Moses Gameli. All rights reserved.
2
+
3
+ This is a pre-release package. The licence under which the Zakadi SDKs and this
4
+ package are distributed has not been finalised; until it is, no rights are
5
+ granted beyond downloading and evaluating the package. See https://zakadi.dev.
zakadi-0.0.1/PKG-INFO ADDED
@@ -0,0 +1,39 @@
1
+ Metadata-Version: 2.5
2
+ Name: zakadi
3
+ Version: 0.0.1
4
+ Summary: Zakadi face-liveness API client for Python (pre-release): shared protocol constants now, the server-side client for sessions, results and webhook verification next.
5
+ Project-URL: Homepage, https://zakadi.dev
6
+ Project-URL: Documentation, https://docs.zakadi.dev
7
+ Project-URL: Repository, https://github.com/zakadihq/zakadi-python
8
+ Project-URL: Issues, https://github.com/zakadihq/zakadi-python/issues
9
+ Author: Moses Gameli
10
+ License: Copyright (c) 2026 Moses Gameli. All rights reserved.
11
+
12
+ This is a pre-release package. The licence under which the Zakadi SDKs and this
13
+ package are distributed has not been finalised; until it is, no rights are
14
+ granted beyond downloading and evaluating the package. See https://zakadi.dev.
15
+ License-File: LICENSE
16
+ Keywords: biometrics,identity-verification,kyc,liveness,zakadi
17
+ Classifier: Development Status :: 1 - Planning
18
+ Classifier: Intended Audience :: Developers
19
+ Classifier: Programming Language :: Python :: 3
20
+ Classifier: Programming Language :: Python :: 3 :: Only
21
+ Classifier: Topic :: Security
22
+ Classifier: Typing :: Typed
23
+ Requires-Python: >=3.10
24
+ Description-Content-Type: text/markdown
25
+
26
+ # zakadi
27
+
28
+ Zakadi face-liveness API client for Python. Zakadi is an active face liveness check delivered as a short automated video call; relying parties create sessions and read verdicts server to server.
29
+
30
+ Pre-release. This version ships the shared protocol constants (session states and statuses, decisions and bands, SDK error codes, terminal states, close codes, challenge kinds, webhook event types) with type annotations. The client for sessions, results, evidence and webhook signature verification follows in a later release; its surface is:
31
+
32
+ ```python
33
+ from zakadi import Zakadi
34
+ client = Zakadi(api_key="zk_live_...", base_url="https://api.zakadi.dev")
35
+ s = client.sessions.create(user_ref="cust-88213", locale="en-NG", channel="android")
36
+ r = client.sessions.result(s.session_id)
37
+ ```
38
+
39
+ Links: https://zakadi.dev (documentation), https://github.com/zakadihq/zakadi-python (source).
zakadi-0.0.1/README.md ADDED
@@ -0,0 +1,14 @@
1
+ # zakadi
2
+
3
+ Zakadi face-liveness API client for Python. Zakadi is an active face liveness check delivered as a short automated video call; relying parties create sessions and read verdicts server to server.
4
+
5
+ Pre-release. This version ships the shared protocol constants (session states and statuses, decisions and bands, SDK error codes, terminal states, close codes, challenge kinds, webhook event types) with type annotations. The client for sessions, results, evidence and webhook signature verification follows in a later release; its surface is:
6
+
7
+ ```python
8
+ from zakadi import Zakadi
9
+ client = Zakadi(api_key="zk_live_...", base_url="https://api.zakadi.dev")
10
+ s = client.sessions.create(user_ref="cust-88213", locale="en-NG", channel="android")
11
+ r = client.sessions.result(s.session_id)
12
+ ```
13
+
14
+ Links: https://zakadi.dev (documentation), https://github.com/zakadihq/zakadi-python (source).
@@ -0,0 +1,34 @@
1
+ [build-system]
2
+ requires = ["hatchling>=1.25"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "zakadi"
7
+ version = "0.0.1"
8
+ description = "Zakadi face-liveness API client for Python (pre-release): shared protocol constants now, the server-side client for sessions, results and webhook verification next."
9
+ readme = "README.md"
10
+ requires-python = ">=3.10"
11
+ license = { file = "LICENSE" }
12
+ authors = [{ name = "Moses Gameli" }]
13
+ keywords = ["zakadi", "liveness", "biometrics", "identity-verification", "kyc"]
14
+ classifiers = [
15
+ "Development Status :: 1 - Planning",
16
+ "Intended Audience :: Developers",
17
+ "Programming Language :: Python :: 3",
18
+ "Programming Language :: Python :: 3 :: Only",
19
+ "Topic :: Security",
20
+ "Typing :: Typed",
21
+ ]
22
+ dependencies = []
23
+
24
+ [project.urls]
25
+ Homepage = "https://zakadi.dev"
26
+ Documentation = "https://docs.zakadi.dev"
27
+ Repository = "https://github.com/zakadihq/zakadi-python"
28
+ Issues = "https://github.com/zakadihq/zakadi-python/issues"
29
+
30
+ [tool.hatch.build.targets.wheel]
31
+ packages = ["src/zakadi"]
32
+
33
+ [tool.hatch.build.targets.sdist]
34
+ include = ["src/zakadi", "tests", "README.md", "CHANGELOG.md", "LICENSE", "pyproject.toml"]
@@ -0,0 +1,41 @@
1
+ """Zakadi face-liveness API client for Python.
2
+
3
+ Pre-release. This version ships the shared protocol constants of the Zakadi
4
+ protocol (``zakadi.v1``); the server-side client (sessions, results, webhook
5
+ verification) follows in a later release. See https://zakadi.dev.
6
+ """
7
+
8
+ from zakadi.protocol import (
9
+ SUBPROTOCOL,
10
+ Band,
11
+ ChallengeKind,
12
+ CloseCode,
13
+ Decision,
14
+ EndOutcome,
15
+ EndReason,
16
+ ErrorCode,
17
+ SessionState,
18
+ SessionStatus,
19
+ TerminalState,
20
+ WEBHOOK_EVENTS,
21
+ terminal_state_for_end,
22
+ )
23
+
24
+ __version__ = "0.0.1"
25
+
26
+ __all__ = [
27
+ "__version__",
28
+ "SUBPROTOCOL",
29
+ "Band",
30
+ "ChallengeKind",
31
+ "CloseCode",
32
+ "Decision",
33
+ "EndOutcome",
34
+ "EndReason",
35
+ "ErrorCode",
36
+ "SessionState",
37
+ "SessionStatus",
38
+ "TerminalState",
39
+ "WEBHOOK_EVENTS",
40
+ "terminal_state_for_end",
41
+ ]
@@ -0,0 +1,195 @@
1
+ """Constants shared by every Zakadi client and server (protocol ``zakadi.v1``)."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from enum import Enum, IntEnum
6
+
7
+ #: The WebSocket subprotocol every Zakadi client requests.
8
+ SUBPROTOCOL = "zakadi.v1"
9
+
10
+
11
+ class SessionState(str, Enum):
12
+ """Client-side session states, in lifecycle order."""
13
+
14
+ IDLE = "idle"
15
+ CONSENT = "consent"
16
+ PERMISSION = "permission"
17
+ CONNECTING = "connecting"
18
+ ACTIVE = "active"
19
+ ENDED = "ended"
20
+ ERROR = "error"
21
+
22
+
23
+ class SessionStatus(str, Enum):
24
+ """Server-side session status as returned by the Zakadi API."""
25
+
26
+ CREATED = "created"
27
+ CONNECTED = "connected"
28
+ IN_PROGRESS = "in_progress"
29
+ VERIFYING = "verifying"
30
+ PASSED = "passed"
31
+ FAILED = "failed"
32
+ INCONCLUSIVE = "inconclusive"
33
+ EXPIRED = "expired"
34
+ ABORTED = "aborted"
35
+
36
+
37
+ class Decision(str, Enum):
38
+ """Verdict decision in a result."""
39
+
40
+ PASS = "pass"
41
+ FAIL = "fail"
42
+ INCONCLUSIVE = "inconclusive"
43
+
44
+
45
+ class Band(str, Enum):
46
+ """Assurance band of a session."""
47
+
48
+ A = "A"
49
+ B = "B"
50
+ C = "C"
51
+
52
+
53
+ class ErrorCode(str, Enum):
54
+ """SDK error codes. Runtime conditions only; API misuse raises a usage error."""
55
+
56
+ CONSENT_DECLINED = "consent_declined"
57
+ CANCELLED = "cancelled"
58
+ PERMISSION_DENIED = "permission_denied"
59
+ UNSUPPORTED_DEVICE = "unsupported_device"
60
+ SDK_DISABLED = "sdk_disabled"
61
+ PACK_UNAVAILABLE = "pack_unavailable"
62
+ NETWORK_UNAVAILABLE = "network_unavailable"
63
+ AUTH_ERROR = "auth_error"
64
+ SESSION_EXPIRED = "session_expired"
65
+ SESSION_USED = "session_used"
66
+ MAX_DURATION = "max_duration"
67
+ ADMISSION_REJECTED = "admission_rejected"
68
+ NETWORK_FLOOR = "network_floor"
69
+ PROTOCOL_ERROR = "protocol_error"
70
+ INTERRUPTED = "interrupted"
71
+ CAPTURE_ERROR = "capture_error"
72
+ ENCODER_ERROR = "encoder_error"
73
+ INTERNAL = "internal"
74
+
75
+
76
+ class TerminalState(str, Enum):
77
+ """Terminal UI states of a session on the client."""
78
+
79
+ COMPLETED = "completed"
80
+ INCOMPLETE = "incomplete"
81
+ DISCONNECTED = "disconnected"
82
+ NETWORK_FLOOR = "network_floor"
83
+ CANCELLED = "cancelled"
84
+ ERROR = "error"
85
+ UNSUPPORTED_DEVICE = "unsupported_device"
86
+ PERMISSION_DENIED = "permission_denied"
87
+ INTERRUPTED = "interrupted"
88
+ SDK_DISABLED = "sdk_disabled"
89
+
90
+ @property
91
+ def cue(self) -> str:
92
+ """The prompt-pack cue the SDK may play locally for this state."""
93
+ return _TERMINAL_CUES[self]
94
+
95
+ @property
96
+ def offers_redial(self) -> bool:
97
+ """Whether the SDK offers a redial action in this state."""
98
+ return self in _REDIAL_STATES
99
+
100
+
101
+ _TERMINAL_CUES: dict[TerminalState, str] = {
102
+ TerminalState.COMPLETED: "done.thanks",
103
+ TerminalState.INCOMPLETE: "fail.one_more_step",
104
+ TerminalState.DISCONNECTED: "net.dropped",
105
+ TerminalState.NETWORK_FLOOR: "net.slow",
106
+ TerminalState.CANCELLED: "end.cancelled",
107
+ TerminalState.ERROR: "end.error",
108
+ TerminalState.UNSUPPORTED_DEVICE: "end.unsupported",
109
+ TerminalState.PERMISSION_DENIED: "end.permission",
110
+ TerminalState.INTERRUPTED: "end.interrupted",
111
+ TerminalState.SDK_DISABLED: "end.disabled",
112
+ }
113
+
114
+ _REDIAL_STATES = frozenset(
115
+ {
116
+ TerminalState.INCOMPLETE,
117
+ TerminalState.DISCONNECTED,
118
+ TerminalState.NETWORK_FLOOR,
119
+ TerminalState.ERROR,
120
+ TerminalState.INTERRUPTED,
121
+ }
122
+ )
123
+
124
+
125
+ class EndOutcome(str, Enum):
126
+ """Outcome carried by the server's ``end`` message."""
127
+
128
+ COMPLETED = "completed"
129
+ ABORTED = "aborted"
130
+
131
+
132
+ class EndReason(str, Enum):
133
+ """Reason carried by the server's ``end`` message."""
134
+
135
+ OK = "ok"
136
+ FLOOR_BREACHED = "floor_breached"
137
+ MAX_DURATION = "max_duration"
138
+ USER_CANCEL = "user_cancel"
139
+ ATTEMPTS_EXHAUSTED = "attempts_exhausted"
140
+ SERVER_ERROR = "server_error"
141
+ ADMISSION = "admission"
142
+
143
+
144
+ def terminal_state_for_end(reason: EndReason) -> TerminalState:
145
+ """Map an ``end`` reason to the terminal UI state the SDK shows."""
146
+ return _END_TO_TERMINAL[reason]
147
+
148
+
149
+ _END_TO_TERMINAL: dict[EndReason, TerminalState] = {
150
+ EndReason.OK: TerminalState.COMPLETED,
151
+ EndReason.ATTEMPTS_EXHAUSTED: TerminalState.INCOMPLETE,
152
+ EndReason.MAX_DURATION: TerminalState.INCOMPLETE,
153
+ EndReason.FLOOR_BREACHED: TerminalState.NETWORK_FLOOR,
154
+ EndReason.USER_CANCEL: TerminalState.CANCELLED,
155
+ EndReason.SERVER_ERROR: TerminalState.ERROR,
156
+ EndReason.ADMISSION: TerminalState.ERROR,
157
+ }
158
+
159
+
160
+ class CloseCode(IntEnum):
161
+ """Application-level WebSocket close codes."""
162
+
163
+ NORMAL = 1000
164
+ TOKEN_INVALID = 4001
165
+ TOKEN_EXPIRED = 4002
166
+ SESSION_NOT_FOUND = 4003
167
+ SESSION_USED = 4004
168
+ UNSUPPORTED_CAPABILITIES = 4005
169
+ PROTOCOL_VIOLATION = 4006
170
+ MEDIA_FLOOR_BREACHED = 4007
171
+ ADMISSION_REJECTED = 4008
172
+ MAX_DURATION_EXCEEDED = 4009
173
+ CANCELLED_BY_USER = 4010
174
+ INTERNAL_ERROR = 4011
175
+
176
+
177
+ class ChallengeKind(str, Enum):
178
+ """Challenge kinds the server may issue."""
179
+
180
+ HEAD_TURN = "head_turn"
181
+ DISTANCE = "distance"
182
+ FINGERS = "fingers"
183
+ DIGITS = "digits"
184
+ BLINK = "blink"
185
+ EXPRESSION = "expression"
186
+ HAND_OVER_FACE = "hand_over_face"
187
+ LOOK_PROFILE = "look_profile"
188
+
189
+
190
+ #: Webhook event types delivered to relying parties.
191
+ WEBHOOK_EVENTS: tuple[str, ...] = (
192
+ "zakadi.session.completed",
193
+ "zakadi.session.aborted",
194
+ "zakadi.session.expired",
195
+ )
File without changes
@@ -0,0 +1,37 @@
1
+ import unittest
2
+
3
+ from zakadi import (
4
+ SUBPROTOCOL,
5
+ CloseCode,
6
+ EndReason,
7
+ ErrorCode,
8
+ TerminalState,
9
+ __version__,
10
+ terminal_state_for_end,
11
+ )
12
+
13
+
14
+ class ProtocolTests(unittest.TestCase):
15
+ def test_subprotocol(self) -> None:
16
+ self.assertEqual(SUBPROTOCOL, "zakadi.v1")
17
+ self.assertEqual(__version__, "0.0.1")
18
+
19
+ def test_error_codes(self) -> None:
20
+ self.assertEqual(len(ErrorCode), 18)
21
+ self.assertEqual(ErrorCode("interrupted"), ErrorCode.INTERRUPTED)
22
+ with self.assertRaises(ValueError):
23
+ ErrorCode("not_a_code")
24
+
25
+ def test_redial(self) -> None:
26
+ self.assertTrue(TerminalState.INCOMPLETE.offers_redial)
27
+ self.assertFalse(TerminalState.COMPLETED.offers_redial)
28
+ self.assertEqual(TerminalState.INCOMPLETE.cue, "fail.one_more_step")
29
+
30
+ def test_end_mapping(self) -> None:
31
+ self.assertEqual(terminal_state_for_end(EndReason.OK), TerminalState.COMPLETED)
32
+ self.assertEqual(terminal_state_for_end(EndReason.MAX_DURATION), TerminalState.INCOMPLETE)
33
+ self.assertEqual(CloseCode.CANCELLED_BY_USER, 4010)
34
+
35
+
36
+ if __name__ == "__main__":
37
+ unittest.main()