ontiver 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.
ontiver-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,238 @@
1
+ Metadata-Version: 2.4
2
+ Name: ontiver
3
+ Version: 0.1.0
4
+ Summary: Official Python SDK for the Ontiver API
5
+ Author: Ontiver
6
+ License-Expression: LicenseRef-Proprietary
7
+ Project-URL: Homepage, https://ontiver.com
8
+ Project-URL: Documentation, https://docs.ontiver.com
9
+ Project-URL: Repository, https://github.com/joinverza/Ontiver
10
+ Keywords: ontiver,kyc,aml,identity,proofs,webhooks
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.11
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Classifier: Typing :: Typed
17
+ Requires-Python: >=3.11
18
+ Description-Content-Type: text/markdown
19
+ Requires-Dist: httpx<1,>=0.27
20
+ Provides-Extra: test
21
+ Requires-Dist: pytest>=8; extra == "test"
22
+ Provides-Extra: build
23
+ Requires-Dist: build>=1.2; extra == "build"
24
+ Requires-Dist: twine>=5; extra == "build"
25
+
26
+ # Ontiver Python SDK
27
+
28
+ Official Python SDK for integrating Ontiver verification, screening, proofs, and webhooks into enterprise backends.
29
+
30
+ The dashboard remains the control plane for API keys, scopes, billing, team roles, webhooks, and audit logs. The SDK is the integration layer for Django, Flask, FastAPI, workers, and internal services.
31
+
32
+ ## Install Locally
33
+
34
+ From this repository:
35
+
36
+ ```bash
37
+ cd sdks/python
38
+ python -m pip install -e ".[test]"
39
+ ```
40
+
41
+ After publication:
42
+
43
+ ```bash
44
+ python -m pip install ontiver
45
+ ```
46
+
47
+ ## Configuration
48
+
49
+ Create an API key from the Ontiver enterprise dashboard and store it in your secret manager.
50
+
51
+ ```bash
52
+ ONTIVER_API_KEY=prod_xxx
53
+ ONTIVER_BASE_URL=https://api.ontiver.com
54
+ ONTIVER_ENVIRONMENT=production
55
+ ONTIVER_WEBHOOK_SECRET=whsec_xxx
56
+ ```
57
+
58
+ Use sandbox keys in non-production environments:
59
+
60
+ ```bash
61
+ ONTIVER_API_KEY=sb_xxx
62
+ ONTIVER_ENVIRONMENT=sandbox
63
+ ```
64
+
65
+ ## Quickstart
66
+
67
+ ```python
68
+ from ontiver import OntiverClient
69
+
70
+ client = OntiverClient.from_env()
71
+
72
+ startup_check = client.validate_api_key()
73
+ print(startup_check["valid"])
74
+ ```
75
+
76
+ ## Verification
77
+
78
+ SDK v1 routes flexible verification payloads to:
79
+
80
+ ```text
81
+ POST /api/v1/banking/kyc/individual/verify
82
+ GET /api/v1/banking/kyc/individual/{verificationId}
83
+ ```
84
+
85
+ ```python
86
+ result = client.verifications.create(
87
+ {
88
+ "customerId": "cust_123",
89
+ "firstName": "Ada",
90
+ "lastName": "Okafor",
91
+ "country": "NG",
92
+ "idDocumentType": "passport",
93
+ "idDocumentNumber": "A12345678",
94
+ },
95
+ idempotency_key="verify-cust-123",
96
+ )
97
+
98
+ status = client.verifications.get(result["verificationId"])
99
+ ```
100
+
101
+ ## Screening
102
+
103
+ ```python
104
+ screening = client.screening.sanctions_check(
105
+ first_name="Ada",
106
+ last_name="Okafor",
107
+ nationality="NG",
108
+ idempotency_key="sanctions-cust-123",
109
+ )
110
+ ```
111
+
112
+ Supported methods:
113
+
114
+ - `client.screening.pep_check(...)`
115
+ - `client.screening.sanctions_check(...)`
116
+ - `client.screening.adverse_media_check(first_name, last_name, ...)`
117
+
118
+ ## Proofs
119
+
120
+ ```python
121
+ proof = client.proofs.get_for_verification("ver_123")
122
+
123
+ verified = client.proofs.verify(
124
+ proof_id=proof["proofId"],
125
+ proof=proof.get("proof"),
126
+ )
127
+ ```
128
+
129
+ Supported methods:
130
+
131
+ - `client.proofs.generate(...)`
132
+ - `client.proofs.get_for_verification(verification_id)`
133
+ - `client.proofs.verify(proof_id, proof=None)`
134
+ - `client.proofs.disclose(proof_id, fields=[...])`
135
+
136
+ ## Webhooks
137
+
138
+ Register a webhook:
139
+
140
+ ```python
141
+ webhook = client.webhooks.register(
142
+ "https://example.com/webhooks/ontiver",
143
+ ["verification.completed", "proof.issued"],
144
+ )
145
+
146
+ # Store this once. It will not be returned by list endpoints.
147
+ secret = webhook["secret"]
148
+ ```
149
+
150
+ Verify webhook signatures using the raw request body:
151
+
152
+ ```python
153
+ from ontiver.webhooks import verify_webhook_signature
154
+
155
+ is_valid = verify_webhook_signature(raw_body, request.headers["X-Ontiver-Signature"], secret)
156
+ ```
157
+
158
+ Header format:
159
+
160
+ ```text
161
+ X-Ontiver-Signature: sha256=<hex_digest>
162
+ ```
163
+
164
+ ## Raw Request Escape Hatch
165
+
166
+ Use `raw_request()` for routes that do not have first-class wrappers yet:
167
+
168
+ ```python
169
+ result = client.raw_request(
170
+ "POST",
171
+ "/aml/smile-checks",
172
+ json={"fullName": "Ada Okafor", "countries": ["NG"]},
173
+ idempotency_key="aml-cust-123",
174
+ )
175
+ ```
176
+
177
+ ## Errors
178
+
179
+ Catch specific exceptions:
180
+
181
+ ```python
182
+ from ontiver import AuthenticationError, PermissionDeniedError, RateLimitError, ValidationError
183
+
184
+ try:
185
+ client.validate_api_key()
186
+ except AuthenticationError:
187
+ ...
188
+ except PermissionDeniedError:
189
+ ...
190
+ except RateLimitError:
191
+ ...
192
+ except ValidationError:
193
+ ...
194
+ ```
195
+
196
+ The SDK redacts API keys, bearer tokens, webhook secrets, and secret prefixes from exception messages.
197
+
198
+ ## Security Notes
199
+
200
+ - Never commit API keys or webhook secrets.
201
+ - Store production secrets in a secret manager.
202
+ - Use least-privilege API key scopes.
203
+ - Use sandbox keys outside production.
204
+ - Verify webhook signatures over the raw body bytes before parsing JSON.
205
+ - Do not log raw identity documents, ID numbers, biometric payloads, or full provider payloads.
206
+
207
+ ## Build And Publish
208
+
209
+ Build locally:
210
+
211
+ ```bash
212
+ cd sdks/python
213
+ python -m pip install -e ".[test]"
214
+ pytest
215
+ python -m pip install ".[build]"
216
+ python -m build
217
+ python -m twine check dist/*
218
+ ```
219
+
220
+ Publish first to TestPyPI:
221
+
222
+ ```bash
223
+ python -m twine upload --repository testpypi dist/*
224
+ ```
225
+
226
+ Install from TestPyPI in a clean environment:
227
+
228
+ ```bash
229
+ python -m pip install --index-url https://test.pypi.org/simple/ --extra-index-url https://pypi.org/simple ontiver
230
+ ```
231
+
232
+ Publish to PyPI after TestPyPI install passes:
233
+
234
+ ```bash
235
+ python -m twine upload dist/*
236
+ ```
237
+
238
+ Target package name: `ontiver`.
@@ -0,0 +1,213 @@
1
+ # Ontiver Python SDK
2
+
3
+ Official Python SDK for integrating Ontiver verification, screening, proofs, and webhooks into enterprise backends.
4
+
5
+ The dashboard remains the control plane for API keys, scopes, billing, team roles, webhooks, and audit logs. The SDK is the integration layer for Django, Flask, FastAPI, workers, and internal services.
6
+
7
+ ## Install Locally
8
+
9
+ From this repository:
10
+
11
+ ```bash
12
+ cd sdks/python
13
+ python -m pip install -e ".[test]"
14
+ ```
15
+
16
+ After publication:
17
+
18
+ ```bash
19
+ python -m pip install ontiver
20
+ ```
21
+
22
+ ## Configuration
23
+
24
+ Create an API key from the Ontiver enterprise dashboard and store it in your secret manager.
25
+
26
+ ```bash
27
+ ONTIVER_API_KEY=prod_xxx
28
+ ONTIVER_BASE_URL=https://api.ontiver.com
29
+ ONTIVER_ENVIRONMENT=production
30
+ ONTIVER_WEBHOOK_SECRET=whsec_xxx
31
+ ```
32
+
33
+ Use sandbox keys in non-production environments:
34
+
35
+ ```bash
36
+ ONTIVER_API_KEY=sb_xxx
37
+ ONTIVER_ENVIRONMENT=sandbox
38
+ ```
39
+
40
+ ## Quickstart
41
+
42
+ ```python
43
+ from ontiver import OntiverClient
44
+
45
+ client = OntiverClient.from_env()
46
+
47
+ startup_check = client.validate_api_key()
48
+ print(startup_check["valid"])
49
+ ```
50
+
51
+ ## Verification
52
+
53
+ SDK v1 routes flexible verification payloads to:
54
+
55
+ ```text
56
+ POST /api/v1/banking/kyc/individual/verify
57
+ GET /api/v1/banking/kyc/individual/{verificationId}
58
+ ```
59
+
60
+ ```python
61
+ result = client.verifications.create(
62
+ {
63
+ "customerId": "cust_123",
64
+ "firstName": "Ada",
65
+ "lastName": "Okafor",
66
+ "country": "NG",
67
+ "idDocumentType": "passport",
68
+ "idDocumentNumber": "A12345678",
69
+ },
70
+ idempotency_key="verify-cust-123",
71
+ )
72
+
73
+ status = client.verifications.get(result["verificationId"])
74
+ ```
75
+
76
+ ## Screening
77
+
78
+ ```python
79
+ screening = client.screening.sanctions_check(
80
+ first_name="Ada",
81
+ last_name="Okafor",
82
+ nationality="NG",
83
+ idempotency_key="sanctions-cust-123",
84
+ )
85
+ ```
86
+
87
+ Supported methods:
88
+
89
+ - `client.screening.pep_check(...)`
90
+ - `client.screening.sanctions_check(...)`
91
+ - `client.screening.adverse_media_check(first_name, last_name, ...)`
92
+
93
+ ## Proofs
94
+
95
+ ```python
96
+ proof = client.proofs.get_for_verification("ver_123")
97
+
98
+ verified = client.proofs.verify(
99
+ proof_id=proof["proofId"],
100
+ proof=proof.get("proof"),
101
+ )
102
+ ```
103
+
104
+ Supported methods:
105
+
106
+ - `client.proofs.generate(...)`
107
+ - `client.proofs.get_for_verification(verification_id)`
108
+ - `client.proofs.verify(proof_id, proof=None)`
109
+ - `client.proofs.disclose(proof_id, fields=[...])`
110
+
111
+ ## Webhooks
112
+
113
+ Register a webhook:
114
+
115
+ ```python
116
+ webhook = client.webhooks.register(
117
+ "https://example.com/webhooks/ontiver",
118
+ ["verification.completed", "proof.issued"],
119
+ )
120
+
121
+ # Store this once. It will not be returned by list endpoints.
122
+ secret = webhook["secret"]
123
+ ```
124
+
125
+ Verify webhook signatures using the raw request body:
126
+
127
+ ```python
128
+ from ontiver.webhooks import verify_webhook_signature
129
+
130
+ is_valid = verify_webhook_signature(raw_body, request.headers["X-Ontiver-Signature"], secret)
131
+ ```
132
+
133
+ Header format:
134
+
135
+ ```text
136
+ X-Ontiver-Signature: sha256=<hex_digest>
137
+ ```
138
+
139
+ ## Raw Request Escape Hatch
140
+
141
+ Use `raw_request()` for routes that do not have first-class wrappers yet:
142
+
143
+ ```python
144
+ result = client.raw_request(
145
+ "POST",
146
+ "/aml/smile-checks",
147
+ json={"fullName": "Ada Okafor", "countries": ["NG"]},
148
+ idempotency_key="aml-cust-123",
149
+ )
150
+ ```
151
+
152
+ ## Errors
153
+
154
+ Catch specific exceptions:
155
+
156
+ ```python
157
+ from ontiver import AuthenticationError, PermissionDeniedError, RateLimitError, ValidationError
158
+
159
+ try:
160
+ client.validate_api_key()
161
+ except AuthenticationError:
162
+ ...
163
+ except PermissionDeniedError:
164
+ ...
165
+ except RateLimitError:
166
+ ...
167
+ except ValidationError:
168
+ ...
169
+ ```
170
+
171
+ The SDK redacts API keys, bearer tokens, webhook secrets, and secret prefixes from exception messages.
172
+
173
+ ## Security Notes
174
+
175
+ - Never commit API keys or webhook secrets.
176
+ - Store production secrets in a secret manager.
177
+ - Use least-privilege API key scopes.
178
+ - Use sandbox keys outside production.
179
+ - Verify webhook signatures over the raw body bytes before parsing JSON.
180
+ - Do not log raw identity documents, ID numbers, biometric payloads, or full provider payloads.
181
+
182
+ ## Build And Publish
183
+
184
+ Build locally:
185
+
186
+ ```bash
187
+ cd sdks/python
188
+ python -m pip install -e ".[test]"
189
+ pytest
190
+ python -m pip install ".[build]"
191
+ python -m build
192
+ python -m twine check dist/*
193
+ ```
194
+
195
+ Publish first to TestPyPI:
196
+
197
+ ```bash
198
+ python -m twine upload --repository testpypi dist/*
199
+ ```
200
+
201
+ Install from TestPyPI in a clean environment:
202
+
203
+ ```bash
204
+ python -m pip install --index-url https://test.pypi.org/simple/ --extra-index-url https://pypi.org/simple ontiver
205
+ ```
206
+
207
+ Publish to PyPI after TestPyPI install passes:
208
+
209
+ ```bash
210
+ python -m twine upload dist/*
211
+ ```
212
+
213
+ Target package name: `ontiver`.
@@ -0,0 +1,41 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "ontiver"
7
+ version = "0.1.0"
8
+ description = "Official Python SDK for the Ontiver API"
9
+ readme = "README.md"
10
+ requires-python = ">=3.11"
11
+ license = "LicenseRef-Proprietary"
12
+ authors = [{ name = "Ontiver" }]
13
+ keywords = ["ontiver", "kyc", "aml", "identity", "proofs", "webhooks"]
14
+ classifiers = [
15
+ "Development Status :: 3 - Alpha",
16
+ "Intended Audience :: Developers",
17
+ "Programming Language :: Python :: 3",
18
+ "Programming Language :: Python :: 3.11",
19
+ "Programming Language :: Python :: 3.12",
20
+ "Typing :: Typed",
21
+ ]
22
+ dependencies = ["httpx>=0.27,<1"]
23
+
24
+ [project.optional-dependencies]
25
+ test = ["pytest>=8"]
26
+ build = ["build>=1.2", "twine>=5"]
27
+
28
+ [project.urls]
29
+ Homepage = "https://ontiver.com"
30
+ Documentation = "https://docs.ontiver.com"
31
+ Repository = "https://github.com/joinverza/Ontiver"
32
+
33
+ [tool.setuptools.packages.find]
34
+ where = ["src"]
35
+
36
+ [tool.setuptools.package-data]
37
+ ontiver = ["py.typed"]
38
+
39
+ [tool.pytest.ini_options]
40
+ testpaths = ["tests"]
41
+ pythonpath = ["src"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,33 @@
1
+ from .client import OntiverClient
2
+ from .errors import (
3
+ AuthenticationError,
4
+ NetworkError,
5
+ NotFoundError,
6
+ OntiverAPIError,
7
+ OntiverConfigError,
8
+ OntiverError,
9
+ PermissionDeniedError,
10
+ RateLimitError,
11
+ ServerError,
12
+ TimeoutError,
13
+ ValidationError,
14
+ WebhookSignatureError,
15
+ )
16
+
17
+ __version__ = "0.1.0"
18
+
19
+ __all__ = [
20
+ "OntiverClient",
21
+ "OntiverError",
22
+ "OntiverConfigError",
23
+ "OntiverAPIError",
24
+ "AuthenticationError",
25
+ "PermissionDeniedError",
26
+ "ValidationError",
27
+ "RateLimitError",
28
+ "NotFoundError",
29
+ "ServerError",
30
+ "TimeoutError",
31
+ "NetworkError",
32
+ "WebhookSignatureError",
33
+ ]
@@ -0,0 +1,75 @@
1
+ from __future__ import annotations
2
+
3
+ from collections.abc import Mapping
4
+ from typing import Any
5
+
6
+ import httpx
7
+
8
+ from .config import OntiverConfig
9
+ from .http import OntiverHTTPClient
10
+ from .resources.proofs import ProofsResource
11
+ from .resources.screening import ScreeningResource
12
+ from .resources.verifications import VerificationsResource
13
+ from .resources.webhook_management import WebhookManagementResource
14
+
15
+
16
+ class OntiverClient:
17
+ def __init__(
18
+ self,
19
+ api_key: str | None = None,
20
+ base_url: str | None = None,
21
+ environment: str | None = None,
22
+ timeout: float = 30,
23
+ max_retries: int = 2,
24
+ webhook_secret: str | None = None,
25
+ http_client: httpx.Client | None = None,
26
+ ) -> None:
27
+ self.config = OntiverConfig.from_env(
28
+ api_key=api_key,
29
+ base_url=base_url,
30
+ environment=environment,
31
+ webhook_secret=webhook_secret,
32
+ timeout=timeout,
33
+ max_retries=max_retries,
34
+ )
35
+ self._http = OntiverHTTPClient(self.config, client=http_client)
36
+ self.webhooks = WebhookManagementResource(self._http)
37
+ self.verifications = VerificationsResource(self._http)
38
+ self.screening = ScreeningResource(self._http)
39
+ self.proofs = ProofsResource(self._http)
40
+
41
+ @classmethod
42
+ def from_env(cls) -> "OntiverClient":
43
+ return cls()
44
+
45
+ def close(self) -> None:
46
+ self._http.close()
47
+
48
+ def __enter__(self) -> "OntiverClient":
49
+ return self
50
+
51
+ def __exit__(self, *_: object) -> None:
52
+ self.close()
53
+
54
+ def validate_api_key(self) -> dict[str, Any]:
55
+ return self._http.request("GET", "/api-keys/validate/current")
56
+
57
+ def raw_request(
58
+ self,
59
+ method: str,
60
+ path: str,
61
+ *,
62
+ json: Any | None = None,
63
+ params: Mapping[str, Any] | None = None,
64
+ headers: Mapping[str, str] | None = None,
65
+ idempotency_key: str | None = None,
66
+ ) -> Any:
67
+ return self._http.request(
68
+ method,
69
+ path,
70
+ json=json,
71
+ params=params,
72
+ headers=headers,
73
+ idempotency_key=idempotency_key,
74
+ unwrap=False,
75
+ )
@@ -0,0 +1,67 @@
1
+ from __future__ import annotations
2
+
3
+ import os
4
+ from dataclasses import dataclass
5
+ from urllib.parse import urlparse
6
+
7
+ from .errors import OntiverConfigError
8
+
9
+
10
+ DEFAULT_BASE_URL = "https://api.ontiver.com"
11
+ VALID_ENVIRONMENTS = {"production", "sandbox"}
12
+ LOCAL_HOSTS = {"localhost", "127.0.0.1", "0.0.0.0", "::1"}
13
+
14
+
15
+ @dataclass(frozen=True)
16
+ class OntiverConfig:
17
+ api_key: str | None = None
18
+ base_url: str = DEFAULT_BASE_URL
19
+ environment: str = "production"
20
+ webhook_secret: str | None = None
21
+ timeout: float = 30
22
+ max_retries: int = 2
23
+
24
+ @classmethod
25
+ def from_env(
26
+ cls,
27
+ *,
28
+ api_key: str | None = None,
29
+ base_url: str | None = None,
30
+ environment: str | None = None,
31
+ webhook_secret: str | None = None,
32
+ timeout: float = 30,
33
+ max_retries: int = 2,
34
+ ) -> "OntiverConfig":
35
+ return cls(
36
+ api_key=api_key if api_key is not None else os.getenv("ONTIVER_API_KEY"),
37
+ base_url=base_url or os.getenv("ONTIVER_BASE_URL") or DEFAULT_BASE_URL,
38
+ environment=environment or os.getenv("ONTIVER_ENVIRONMENT") or "production",
39
+ webhook_secret=webhook_secret if webhook_secret is not None else os.getenv("ONTIVER_WEBHOOK_SECRET"),
40
+ timeout=timeout,
41
+ max_retries=max_retries,
42
+ ).validated()
43
+
44
+ def validated(self) -> "OntiverConfig":
45
+ environment = str(self.environment or "").strip().lower()
46
+ if environment not in VALID_ENVIRONMENTS:
47
+ raise OntiverConfigError("environment must be 'production' or 'sandbox'")
48
+ base_url = normalize_base_url(self.base_url)
49
+ return OntiverConfig(
50
+ api_key=str(self.api_key).strip() if self.api_key else None,
51
+ base_url=base_url,
52
+ environment=environment,
53
+ webhook_secret=str(self.webhook_secret).strip() if self.webhook_secret else None,
54
+ timeout=float(self.timeout),
55
+ max_retries=max(0, int(self.max_retries)),
56
+ )
57
+
58
+
59
+ def normalize_base_url(value: str | None) -> str:
60
+ raw = str(value or DEFAULT_BASE_URL).strip().rstrip("/")
61
+ parsed = urlparse(raw)
62
+ if parsed.scheme not in {"http", "https"} or not parsed.netloc:
63
+ raise OntiverConfigError("base_url must be an absolute HTTP(S) URL")
64
+ hostname = (parsed.hostname or "").lower()
65
+ if parsed.scheme != "https" and hostname not in LOCAL_HOSTS:
66
+ raise OntiverConfigError("HTTPS is required for non-local Ontiver API base URLs")
67
+ return raw