aho-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.
- aho_sdk-0.1.0/PKG-INFO +380 -0
- aho_sdk-0.1.0/README.md +341 -0
- aho_sdk-0.1.0/aho_sdk/__init__.py +108 -0
- aho_sdk-0.1.0/aho_sdk/account.py +180 -0
- aho_sdk-0.1.0/aho_sdk/async_account.py +194 -0
- aho_sdk-0.1.0/aho_sdk/async_cursor_page.py +99 -0
- aho_sdk-0.1.0/aho_sdk/async_holder.py +112 -0
- aho_sdk-0.1.0/aho_sdk/async_http_client.py +262 -0
- aho_sdk-0.1.0/aho_sdk/async_issuer.py +388 -0
- aho_sdk-0.1.0/aho_sdk/async_page.py +100 -0
- aho_sdk-0.1.0/aho_sdk/async_public.py +81 -0
- aho_sdk-0.1.0/aho_sdk/async_schemas.py +96 -0
- aho_sdk-0.1.0/aho_sdk/async_system.py +73 -0
- aho_sdk-0.1.0/aho_sdk/async_verifier.py +147 -0
- aho_sdk-0.1.0/aho_sdk/cursor_page.py +98 -0
- aho_sdk-0.1.0/aho_sdk/exceptions.py +140 -0
- aho_sdk-0.1.0/aho_sdk/holder.py +98 -0
- aho_sdk-0.1.0/aho_sdk/http_client.py +437 -0
- aho_sdk-0.1.0/aho_sdk/issuer.py +374 -0
- aho_sdk-0.1.0/aho_sdk/models.py +763 -0
- aho_sdk-0.1.0/aho_sdk/page.py +99 -0
- aho_sdk-0.1.0/aho_sdk/public.py +67 -0
- aho_sdk-0.1.0/aho_sdk/schemas.py +82 -0
- aho_sdk-0.1.0/aho_sdk/system.py +59 -0
- aho_sdk-0.1.0/aho_sdk/types.py +981 -0
- aho_sdk-0.1.0/aho_sdk/verifier.py +133 -0
- aho_sdk-0.1.0/aho_sdk/version.py +4 -0
- aho_sdk-0.1.0/aho_sdk.egg-info/PKG-INFO +380 -0
- aho_sdk-0.1.0/aho_sdk.egg-info/SOURCES.txt +36 -0
- aho_sdk-0.1.0/aho_sdk.egg-info/dependency_links.txt +1 -0
- aho_sdk-0.1.0/aho_sdk.egg-info/requires.txt +20 -0
- aho_sdk-0.1.0/aho_sdk.egg-info/top_level.txt +1 -0
- aho_sdk-0.1.0/pyproject.toml +75 -0
- aho_sdk-0.1.0/setup.cfg +4 -0
- aho_sdk-0.1.0/tests/test_clients.py +217 -0
- aho_sdk-0.1.0/tests/test_exceptions.py +234 -0
- aho_sdk-0.1.0/tests/test_http_client.py +276 -0
- aho_sdk-0.1.0/tests/test_page.py +164 -0
aho_sdk-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,380 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: aho-sdk
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Python SDK for the Aho Verifiable Credentials API
|
|
5
|
+
Author-email: Aho <support@aho.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://aho.com
|
|
8
|
+
Project-URL: Documentation, https://aho.com/developers/python-sdk
|
|
9
|
+
Project-URL: Repository, https://github.com/aho-hq/aho-python
|
|
10
|
+
Project-URL: Changelog, https://github.com/aho-hq/aho-python/blob/main/CHANGELOG.md
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Operating System :: OS Independent
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
21
|
+
Requires-Python: >=3.9
|
|
22
|
+
Description-Content-Type: text/markdown
|
|
23
|
+
Provides-Extra: async
|
|
24
|
+
Requires-Dist: httpx>=0.25.0; extra == "async"
|
|
25
|
+
Provides-Extra: validation
|
|
26
|
+
Requires-Dist: pydantic>=2.0; extra == "validation"
|
|
27
|
+
Provides-Extra: all
|
|
28
|
+
Requires-Dist: httpx>=0.25.0; extra == "all"
|
|
29
|
+
Requires-Dist: pydantic>=2.0; extra == "all"
|
|
30
|
+
Provides-Extra: dev
|
|
31
|
+
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
32
|
+
Requires-Dist: pytest-cov>=4.0; extra == "dev"
|
|
33
|
+
Requires-Dist: pytest-asyncio>=0.21; extra == "dev"
|
|
34
|
+
Requires-Dist: responses>=0.23; extra == "dev"
|
|
35
|
+
Requires-Dist: httpx>=0.25.0; extra == "dev"
|
|
36
|
+
Requires-Dist: mypy>=1.0; extra == "dev"
|
|
37
|
+
Requires-Dist: ruff>=0.1; extra == "dev"
|
|
38
|
+
Requires-Dist: pydantic>=2.0; extra == "dev"
|
|
39
|
+
|
|
40
|
+
# aho_sdk Python SDK
|
|
41
|
+
|
|
42
|
+
Official Python SDK for the [Aho](https://aho.com) Verifiable Credentials API.
|
|
43
|
+
|
|
44
|
+
## Installation
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
# Basic installation (sync only)
|
|
48
|
+
pip install aho-sdk
|
|
49
|
+
|
|
50
|
+
# With async support
|
|
51
|
+
pip install aho-sdk[async]
|
|
52
|
+
|
|
53
|
+
# With validation (Pydantic models)
|
|
54
|
+
pip install aho-sdk[validation]
|
|
55
|
+
|
|
56
|
+
# All features
|
|
57
|
+
pip install aho-sdk[all]
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Quick Start
|
|
61
|
+
|
|
62
|
+
```python
|
|
63
|
+
import os
|
|
64
|
+
from aho_sdk import Issuer
|
|
65
|
+
|
|
66
|
+
# Initialize the Issuer client
|
|
67
|
+
issuer = Issuer(api_key=os.environ['AHO_ISSUER_API_KEY'])
|
|
68
|
+
|
|
69
|
+
# Issue a credential
|
|
70
|
+
credential = issuer.credentials.create(
|
|
71
|
+
schema_uuid='your-schema-uuid',
|
|
72
|
+
subject_identifier='user@example.com',
|
|
73
|
+
claims={
|
|
74
|
+
'name': 'Jane Doe',
|
|
75
|
+
'role': 'Engineer'
|
|
76
|
+
}
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
print(credential['uuid'])
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## Clients
|
|
83
|
+
|
|
84
|
+
The SDK provides the following clients:
|
|
85
|
+
|
|
86
|
+
| Client | Purpose | API Key Type |
|
|
87
|
+
|--------|---------|--------------|
|
|
88
|
+
| `Account` | Manage account settings, domains, and API keys | Account API Key |
|
|
89
|
+
| `System` | System health and status endpoints | System API Key |
|
|
90
|
+
| `Holder` | Manage holder credentials and presentations | Holder API Key |
|
|
91
|
+
| `Verifier` | Create presentation requests and verify credentials | Verifier API Key |
|
|
92
|
+
| `Issuer` | Issue and manage verifiable credentials | Issuer API Key |
|
|
93
|
+
| `Schemas` | Browse and retrieve credential schemas | Schemas API Key |
|
|
94
|
+
| `Public` | Public endpoints (no authentication required) | None (public) |
|
|
95
|
+
|
|
96
|
+
## Usage Examples
|
|
97
|
+
|
|
98
|
+
### Issuing Credentials
|
|
99
|
+
|
|
100
|
+
```python
|
|
101
|
+
from aho_sdk import Issuer
|
|
102
|
+
|
|
103
|
+
issuer = Issuer(api_key=os.environ['AHO_ISSUER_API_KEY'])
|
|
104
|
+
|
|
105
|
+
# List all schemas
|
|
106
|
+
schemas = issuer.schemas.list()
|
|
107
|
+
for schema in schemas:
|
|
108
|
+
print(schema['name'])
|
|
109
|
+
|
|
110
|
+
# Create a schema
|
|
111
|
+
schema = issuer.schemas.create(
|
|
112
|
+
name='EmployeeBadge',
|
|
113
|
+
claims=[
|
|
114
|
+
{'name': 'employee_id', 'type': 'string', 'required': True},
|
|
115
|
+
{'name': 'department', 'type': 'string', 'required': True},
|
|
116
|
+
{'name': 'hire_date', 'type': 'date', 'required': False}
|
|
117
|
+
]
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
# Issue a credential
|
|
121
|
+
credential = issuer.credentials.create(
|
|
122
|
+
schema_uuid=schema['uuid'],
|
|
123
|
+
subject_identifier='jane.doe@company.com',
|
|
124
|
+
claims={
|
|
125
|
+
'employee_id': 'EMP-12345',
|
|
126
|
+
'department': 'Engineering',
|
|
127
|
+
'hire_date': '2024-01-15'
|
|
128
|
+
}
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
# Revoke a credential
|
|
132
|
+
issuer.credentials.revoke(uuid=credential['uuid'], reason='Employee departed')
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
### Verifying Credentials
|
|
136
|
+
|
|
137
|
+
```python
|
|
138
|
+
from aho_sdk import Verifier
|
|
139
|
+
|
|
140
|
+
verifier = Verifier(api_key=os.environ['AHO_VERIFIER_API_KEY'])
|
|
141
|
+
|
|
142
|
+
# Create a presentation request
|
|
143
|
+
request = verifier.requests.create(
|
|
144
|
+
name='Employment Verification',
|
|
145
|
+
query_format='dcql',
|
|
146
|
+
credentials=[
|
|
147
|
+
{
|
|
148
|
+
'id': 'employee_badge',
|
|
149
|
+
'format': 'vc+sd-jwt',
|
|
150
|
+
'claims': [
|
|
151
|
+
{'path': ['employee_id']},
|
|
152
|
+
{'path': ['department']}
|
|
153
|
+
]
|
|
154
|
+
}
|
|
155
|
+
]
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
# Get the QR code for the request (supports 'png', 'svg' formats)
|
|
159
|
+
qr = verifier.requests.qr_code(uuid=request['uuid'], output_format='svg')
|
|
160
|
+
|
|
161
|
+
# List responses to the request
|
|
162
|
+
responses = verifier.responses.list(request_uuid=request['uuid'])
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
### Managing Holder Credentials
|
|
166
|
+
|
|
167
|
+
```python
|
|
168
|
+
from aho_sdk import Holder
|
|
169
|
+
|
|
170
|
+
holder = Holder(api_key=os.environ['AHO_HOLDER_API_KEY'])
|
|
171
|
+
|
|
172
|
+
# List credentials
|
|
173
|
+
credentials = holder.credentials.list(status='active')
|
|
174
|
+
|
|
175
|
+
# Create a presentation (selective disclosure)
|
|
176
|
+
presentation = holder.presentations.create(
|
|
177
|
+
credential_uuid='credential-uuid',
|
|
178
|
+
disclosed_claims=['name', 'department']
|
|
179
|
+
)
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
### Account Management
|
|
183
|
+
|
|
184
|
+
```python
|
|
185
|
+
from aho_sdk import Account
|
|
186
|
+
|
|
187
|
+
account = Account(api_key=os.environ['AHO_API_KEY'])
|
|
188
|
+
|
|
189
|
+
# Manage domains
|
|
190
|
+
domains = account.domains.list()
|
|
191
|
+
account.domains.verify(id=domain['id'])
|
|
192
|
+
|
|
193
|
+
# Manage signing keys
|
|
194
|
+
keys = account.signing_keys.list()
|
|
195
|
+
account.signing_keys.rotate(id=key['id'])
|
|
196
|
+
|
|
197
|
+
# Configure webhooks
|
|
198
|
+
account.webhooks.create(
|
|
199
|
+
url='https://your-app.com/webhooks/aho',
|
|
200
|
+
events=['credential.issued', 'credential.revoked']
|
|
201
|
+
)
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
## Pagination
|
|
205
|
+
|
|
206
|
+
List methods return `Page` objects with built-in iteration:
|
|
207
|
+
|
|
208
|
+
```python
|
|
209
|
+
# Iterate through all pages automatically
|
|
210
|
+
for credential in issuer.credentials.list():
|
|
211
|
+
print(credential['uuid'])
|
|
212
|
+
|
|
213
|
+
# Or handle pages manually
|
|
214
|
+
page = issuer.credentials.list(per_page=50)
|
|
215
|
+
while page:
|
|
216
|
+
for credential in page.data:
|
|
217
|
+
print(credential['uuid'])
|
|
218
|
+
page = page.next_page()
|
|
219
|
+
|
|
220
|
+
# Collect all items
|
|
221
|
+
all_credentials = list(issuer.credentials.list())
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
## File Uploads
|
|
225
|
+
|
|
226
|
+
For endpoints that accept file uploads:
|
|
227
|
+
|
|
228
|
+
```python
|
|
229
|
+
# Upload a file
|
|
230
|
+
issuer.media.upload(
|
|
231
|
+
file=open('document.pdf', 'rb'),
|
|
232
|
+
metadata={'description': 'Employee contract'}
|
|
233
|
+
)
|
|
234
|
+
|
|
235
|
+
# The SDK auto-detects MIME types from file extensions
|
|
236
|
+
# You can also pass a Path:
|
|
237
|
+
from pathlib import Path
|
|
238
|
+
issuer.media.upload(file=Path('document.pdf'))
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
## Binary Responses
|
|
242
|
+
|
|
243
|
+
Some endpoints return binary data (images, PDFs):
|
|
244
|
+
|
|
245
|
+
```python
|
|
246
|
+
# Get QR code as PNG
|
|
247
|
+
png_data = verifier.requests.qr_code(uuid='...', output_format='png')
|
|
248
|
+
with open('qr.png', 'wb') as f:
|
|
249
|
+
f.write(png_data)
|
|
250
|
+
|
|
251
|
+
# Get QR code as SVG
|
|
252
|
+
svg_data = verifier.requests.qr_code(uuid='...', output_format='svg')
|
|
253
|
+
with open('qr.svg', 'w') as f:
|
|
254
|
+
f.write(svg_data.decode('utf-8'))
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
## Error Handling
|
|
258
|
+
|
|
259
|
+
```python
|
|
260
|
+
from aho_sdk import (
|
|
261
|
+
Issuer,
|
|
262
|
+
ValidationError,
|
|
263
|
+
AuthenticationError,
|
|
264
|
+
NotFoundError,
|
|
265
|
+
RateLimitError,
|
|
266
|
+
ApiError
|
|
267
|
+
)
|
|
268
|
+
|
|
269
|
+
try:
|
|
270
|
+
issuer.credentials.create(invalid_params)
|
|
271
|
+
except ValidationError as e:
|
|
272
|
+
# 422 - Validation failed
|
|
273
|
+
for error in e.field_errors:
|
|
274
|
+
print(f"{error['field']}: {error['hint']}")
|
|
275
|
+
except AuthenticationError as e:
|
|
276
|
+
# 401 - Invalid API key
|
|
277
|
+
print("Check your API key")
|
|
278
|
+
except NotFoundError as e:
|
|
279
|
+
# 404 - Resource not found
|
|
280
|
+
print(f"Resource not found: {e.message}")
|
|
281
|
+
except RateLimitError as e:
|
|
282
|
+
# 429 - Rate limited (SDK auto-retries with exponential backoff)
|
|
283
|
+
print(f"Retry after {e.retry_after} seconds")
|
|
284
|
+
except ApiError as e:
|
|
285
|
+
# Other API errors
|
|
286
|
+
print(f"Error {e.status_code}: {e.message}")
|
|
287
|
+
print(f"Request ID: {e.request_id}")
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
### Error Classes
|
|
291
|
+
|
|
292
|
+
| Error Class | HTTP Status | Description |
|
|
293
|
+
|-------------|-------------|-------------|
|
|
294
|
+
| `AuthenticationError` | 401 | Invalid or missing API key |
|
|
295
|
+
| `ForbiddenError` | 403 | Insufficient permissions |
|
|
296
|
+
| `NotFoundError` | 404 | Resource not found |
|
|
297
|
+
| `ConflictError` | 409 | Resource conflict |
|
|
298
|
+
| `ValidationError` | 422 | Request validation failed |
|
|
299
|
+
| `RateLimitError` | 429 | Rate limit exceeded |
|
|
300
|
+
| `ServerError` | 5xx | Server-side error |
|
|
301
|
+
| `NetworkError` | - | Connection/timeout error |
|
|
302
|
+
| `ApiError` | * | Base class for all API errors |
|
|
303
|
+
|
|
304
|
+
## Rate Limiting
|
|
305
|
+
|
|
306
|
+
The SDK automatically handles rate limits with exponential backoff:
|
|
307
|
+
|
|
308
|
+
- **Idempotent methods** (GET, DELETE, PUT): Auto-retry up to 3 times
|
|
309
|
+
- **Non-idempotent methods** (POST, PATCH): Only retry with idempotency key
|
|
310
|
+
|
|
311
|
+
```python
|
|
312
|
+
# Use idempotency keys for safe retries on POST/PATCH
|
|
313
|
+
issuer.credentials.create(
|
|
314
|
+
schema_uuid='...',
|
|
315
|
+
claims={...},
|
|
316
|
+
idempotency_key='unique-request-id'
|
|
317
|
+
)
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
## Configuration
|
|
321
|
+
|
|
322
|
+
```python
|
|
323
|
+
import logging
|
|
324
|
+
|
|
325
|
+
# Custom configuration
|
|
326
|
+
issuer = Issuer(
|
|
327
|
+
api_key=os.environ['AHO_ISSUER_API_KEY'],
|
|
328
|
+
base_url='https://api.aho.com', # Custom base URL
|
|
329
|
+
timeout=60, # Request timeout in seconds
|
|
330
|
+
logger=logging.getLogger('aho') # Enable debug logging
|
|
331
|
+
)
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
## Async Support
|
|
335
|
+
|
|
336
|
+
The SDK provides async versions of all clients. Install with async support:
|
|
337
|
+
|
|
338
|
+
```bash
|
|
339
|
+
pip install aho-sdk[async]
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
Then use the async clients:
|
|
343
|
+
|
|
344
|
+
```python
|
|
345
|
+
import asyncio
|
|
346
|
+
from aho_sdk import AsyncIssuer
|
|
347
|
+
|
|
348
|
+
async def main():
|
|
349
|
+
# Use async context manager for automatic cleanup
|
|
350
|
+
async with AsyncIssuer(api_key=os.environ['AHO_ISSUER_API_KEY']) as issuer:
|
|
351
|
+
# All methods are async
|
|
352
|
+
credentials = await issuer.credentials.list()
|
|
353
|
+
|
|
354
|
+
# Async iteration through pages
|
|
355
|
+
async for credential in credentials:
|
|
356
|
+
print(credential['uuid'])
|
|
357
|
+
|
|
358
|
+
# Or await individual operations
|
|
359
|
+
credential = await issuer.credentials.get(uuid='...')
|
|
360
|
+
|
|
361
|
+
asyncio.run(main())
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
Async clients available:
|
|
365
|
+
- `AsyncAccount` - Async version of `Account`
|
|
366
|
+
- `AsyncSystem` - Async version of `System`
|
|
367
|
+
- `AsyncHolder` - Async version of `Holder`
|
|
368
|
+
- `AsyncVerifier` - Async version of `Verifier`
|
|
369
|
+
- `AsyncIssuer` - Async version of `Issuer`
|
|
370
|
+
- `AsyncSchemas` - Async version of `Schemas`
|
|
371
|
+
- `AsyncPublic` - Async version of `Public`
|
|
372
|
+
|
|
373
|
+
## Requirements
|
|
374
|
+
|
|
375
|
+
- Python 3.9+
|
|
376
|
+
- httpx (for async support, optional)
|
|
377
|
+
|
|
378
|
+
## License
|
|
379
|
+
|
|
380
|
+
MIT
|
aho_sdk-0.1.0/README.md
ADDED
|
@@ -0,0 +1,341 @@
|
|
|
1
|
+
# aho_sdk Python SDK
|
|
2
|
+
|
|
3
|
+
Official Python SDK for the [Aho](https://aho.com) Verifiable Credentials API.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
# Basic installation (sync only)
|
|
9
|
+
pip install aho-sdk
|
|
10
|
+
|
|
11
|
+
# With async support
|
|
12
|
+
pip install aho-sdk[async]
|
|
13
|
+
|
|
14
|
+
# With validation (Pydantic models)
|
|
15
|
+
pip install aho-sdk[validation]
|
|
16
|
+
|
|
17
|
+
# All features
|
|
18
|
+
pip install aho-sdk[all]
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Quick Start
|
|
22
|
+
|
|
23
|
+
```python
|
|
24
|
+
import os
|
|
25
|
+
from aho_sdk import Issuer
|
|
26
|
+
|
|
27
|
+
# Initialize the Issuer client
|
|
28
|
+
issuer = Issuer(api_key=os.environ['AHO_ISSUER_API_KEY'])
|
|
29
|
+
|
|
30
|
+
# Issue a credential
|
|
31
|
+
credential = issuer.credentials.create(
|
|
32
|
+
schema_uuid='your-schema-uuid',
|
|
33
|
+
subject_identifier='user@example.com',
|
|
34
|
+
claims={
|
|
35
|
+
'name': 'Jane Doe',
|
|
36
|
+
'role': 'Engineer'
|
|
37
|
+
}
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
print(credential['uuid'])
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Clients
|
|
44
|
+
|
|
45
|
+
The SDK provides the following clients:
|
|
46
|
+
|
|
47
|
+
| Client | Purpose | API Key Type |
|
|
48
|
+
|--------|---------|--------------|
|
|
49
|
+
| `Account` | Manage account settings, domains, and API keys | Account API Key |
|
|
50
|
+
| `System` | System health and status endpoints | System API Key |
|
|
51
|
+
| `Holder` | Manage holder credentials and presentations | Holder API Key |
|
|
52
|
+
| `Verifier` | Create presentation requests and verify credentials | Verifier API Key |
|
|
53
|
+
| `Issuer` | Issue and manage verifiable credentials | Issuer API Key |
|
|
54
|
+
| `Schemas` | Browse and retrieve credential schemas | Schemas API Key |
|
|
55
|
+
| `Public` | Public endpoints (no authentication required) | None (public) |
|
|
56
|
+
|
|
57
|
+
## Usage Examples
|
|
58
|
+
|
|
59
|
+
### Issuing Credentials
|
|
60
|
+
|
|
61
|
+
```python
|
|
62
|
+
from aho_sdk import Issuer
|
|
63
|
+
|
|
64
|
+
issuer = Issuer(api_key=os.environ['AHO_ISSUER_API_KEY'])
|
|
65
|
+
|
|
66
|
+
# List all schemas
|
|
67
|
+
schemas = issuer.schemas.list()
|
|
68
|
+
for schema in schemas:
|
|
69
|
+
print(schema['name'])
|
|
70
|
+
|
|
71
|
+
# Create a schema
|
|
72
|
+
schema = issuer.schemas.create(
|
|
73
|
+
name='EmployeeBadge',
|
|
74
|
+
claims=[
|
|
75
|
+
{'name': 'employee_id', 'type': 'string', 'required': True},
|
|
76
|
+
{'name': 'department', 'type': 'string', 'required': True},
|
|
77
|
+
{'name': 'hire_date', 'type': 'date', 'required': False}
|
|
78
|
+
]
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
# Issue a credential
|
|
82
|
+
credential = issuer.credentials.create(
|
|
83
|
+
schema_uuid=schema['uuid'],
|
|
84
|
+
subject_identifier='jane.doe@company.com',
|
|
85
|
+
claims={
|
|
86
|
+
'employee_id': 'EMP-12345',
|
|
87
|
+
'department': 'Engineering',
|
|
88
|
+
'hire_date': '2024-01-15'
|
|
89
|
+
}
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
# Revoke a credential
|
|
93
|
+
issuer.credentials.revoke(uuid=credential['uuid'], reason='Employee departed')
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### Verifying Credentials
|
|
97
|
+
|
|
98
|
+
```python
|
|
99
|
+
from aho_sdk import Verifier
|
|
100
|
+
|
|
101
|
+
verifier = Verifier(api_key=os.environ['AHO_VERIFIER_API_KEY'])
|
|
102
|
+
|
|
103
|
+
# Create a presentation request
|
|
104
|
+
request = verifier.requests.create(
|
|
105
|
+
name='Employment Verification',
|
|
106
|
+
query_format='dcql',
|
|
107
|
+
credentials=[
|
|
108
|
+
{
|
|
109
|
+
'id': 'employee_badge',
|
|
110
|
+
'format': 'vc+sd-jwt',
|
|
111
|
+
'claims': [
|
|
112
|
+
{'path': ['employee_id']},
|
|
113
|
+
{'path': ['department']}
|
|
114
|
+
]
|
|
115
|
+
}
|
|
116
|
+
]
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
# Get the QR code for the request (supports 'png', 'svg' formats)
|
|
120
|
+
qr = verifier.requests.qr_code(uuid=request['uuid'], output_format='svg')
|
|
121
|
+
|
|
122
|
+
# List responses to the request
|
|
123
|
+
responses = verifier.responses.list(request_uuid=request['uuid'])
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
### Managing Holder Credentials
|
|
127
|
+
|
|
128
|
+
```python
|
|
129
|
+
from aho_sdk import Holder
|
|
130
|
+
|
|
131
|
+
holder = Holder(api_key=os.environ['AHO_HOLDER_API_KEY'])
|
|
132
|
+
|
|
133
|
+
# List credentials
|
|
134
|
+
credentials = holder.credentials.list(status='active')
|
|
135
|
+
|
|
136
|
+
# Create a presentation (selective disclosure)
|
|
137
|
+
presentation = holder.presentations.create(
|
|
138
|
+
credential_uuid='credential-uuid',
|
|
139
|
+
disclosed_claims=['name', 'department']
|
|
140
|
+
)
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
### Account Management
|
|
144
|
+
|
|
145
|
+
```python
|
|
146
|
+
from aho_sdk import Account
|
|
147
|
+
|
|
148
|
+
account = Account(api_key=os.environ['AHO_API_KEY'])
|
|
149
|
+
|
|
150
|
+
# Manage domains
|
|
151
|
+
domains = account.domains.list()
|
|
152
|
+
account.domains.verify(id=domain['id'])
|
|
153
|
+
|
|
154
|
+
# Manage signing keys
|
|
155
|
+
keys = account.signing_keys.list()
|
|
156
|
+
account.signing_keys.rotate(id=key['id'])
|
|
157
|
+
|
|
158
|
+
# Configure webhooks
|
|
159
|
+
account.webhooks.create(
|
|
160
|
+
url='https://your-app.com/webhooks/aho',
|
|
161
|
+
events=['credential.issued', 'credential.revoked']
|
|
162
|
+
)
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
## Pagination
|
|
166
|
+
|
|
167
|
+
List methods return `Page` objects with built-in iteration:
|
|
168
|
+
|
|
169
|
+
```python
|
|
170
|
+
# Iterate through all pages automatically
|
|
171
|
+
for credential in issuer.credentials.list():
|
|
172
|
+
print(credential['uuid'])
|
|
173
|
+
|
|
174
|
+
# Or handle pages manually
|
|
175
|
+
page = issuer.credentials.list(per_page=50)
|
|
176
|
+
while page:
|
|
177
|
+
for credential in page.data:
|
|
178
|
+
print(credential['uuid'])
|
|
179
|
+
page = page.next_page()
|
|
180
|
+
|
|
181
|
+
# Collect all items
|
|
182
|
+
all_credentials = list(issuer.credentials.list())
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
## File Uploads
|
|
186
|
+
|
|
187
|
+
For endpoints that accept file uploads:
|
|
188
|
+
|
|
189
|
+
```python
|
|
190
|
+
# Upload a file
|
|
191
|
+
issuer.media.upload(
|
|
192
|
+
file=open('document.pdf', 'rb'),
|
|
193
|
+
metadata={'description': 'Employee contract'}
|
|
194
|
+
)
|
|
195
|
+
|
|
196
|
+
# The SDK auto-detects MIME types from file extensions
|
|
197
|
+
# You can also pass a Path:
|
|
198
|
+
from pathlib import Path
|
|
199
|
+
issuer.media.upload(file=Path('document.pdf'))
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
## Binary Responses
|
|
203
|
+
|
|
204
|
+
Some endpoints return binary data (images, PDFs):
|
|
205
|
+
|
|
206
|
+
```python
|
|
207
|
+
# Get QR code as PNG
|
|
208
|
+
png_data = verifier.requests.qr_code(uuid='...', output_format='png')
|
|
209
|
+
with open('qr.png', 'wb') as f:
|
|
210
|
+
f.write(png_data)
|
|
211
|
+
|
|
212
|
+
# Get QR code as SVG
|
|
213
|
+
svg_data = verifier.requests.qr_code(uuid='...', output_format='svg')
|
|
214
|
+
with open('qr.svg', 'w') as f:
|
|
215
|
+
f.write(svg_data.decode('utf-8'))
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
## Error Handling
|
|
219
|
+
|
|
220
|
+
```python
|
|
221
|
+
from aho_sdk import (
|
|
222
|
+
Issuer,
|
|
223
|
+
ValidationError,
|
|
224
|
+
AuthenticationError,
|
|
225
|
+
NotFoundError,
|
|
226
|
+
RateLimitError,
|
|
227
|
+
ApiError
|
|
228
|
+
)
|
|
229
|
+
|
|
230
|
+
try:
|
|
231
|
+
issuer.credentials.create(invalid_params)
|
|
232
|
+
except ValidationError as e:
|
|
233
|
+
# 422 - Validation failed
|
|
234
|
+
for error in e.field_errors:
|
|
235
|
+
print(f"{error['field']}: {error['hint']}")
|
|
236
|
+
except AuthenticationError as e:
|
|
237
|
+
# 401 - Invalid API key
|
|
238
|
+
print("Check your API key")
|
|
239
|
+
except NotFoundError as e:
|
|
240
|
+
# 404 - Resource not found
|
|
241
|
+
print(f"Resource not found: {e.message}")
|
|
242
|
+
except RateLimitError as e:
|
|
243
|
+
# 429 - Rate limited (SDK auto-retries with exponential backoff)
|
|
244
|
+
print(f"Retry after {e.retry_after} seconds")
|
|
245
|
+
except ApiError as e:
|
|
246
|
+
# Other API errors
|
|
247
|
+
print(f"Error {e.status_code}: {e.message}")
|
|
248
|
+
print(f"Request ID: {e.request_id}")
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
### Error Classes
|
|
252
|
+
|
|
253
|
+
| Error Class | HTTP Status | Description |
|
|
254
|
+
|-------------|-------------|-------------|
|
|
255
|
+
| `AuthenticationError` | 401 | Invalid or missing API key |
|
|
256
|
+
| `ForbiddenError` | 403 | Insufficient permissions |
|
|
257
|
+
| `NotFoundError` | 404 | Resource not found |
|
|
258
|
+
| `ConflictError` | 409 | Resource conflict |
|
|
259
|
+
| `ValidationError` | 422 | Request validation failed |
|
|
260
|
+
| `RateLimitError` | 429 | Rate limit exceeded |
|
|
261
|
+
| `ServerError` | 5xx | Server-side error |
|
|
262
|
+
| `NetworkError` | - | Connection/timeout error |
|
|
263
|
+
| `ApiError` | * | Base class for all API errors |
|
|
264
|
+
|
|
265
|
+
## Rate Limiting
|
|
266
|
+
|
|
267
|
+
The SDK automatically handles rate limits with exponential backoff:
|
|
268
|
+
|
|
269
|
+
- **Idempotent methods** (GET, DELETE, PUT): Auto-retry up to 3 times
|
|
270
|
+
- **Non-idempotent methods** (POST, PATCH): Only retry with idempotency key
|
|
271
|
+
|
|
272
|
+
```python
|
|
273
|
+
# Use idempotency keys for safe retries on POST/PATCH
|
|
274
|
+
issuer.credentials.create(
|
|
275
|
+
schema_uuid='...',
|
|
276
|
+
claims={...},
|
|
277
|
+
idempotency_key='unique-request-id'
|
|
278
|
+
)
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
## Configuration
|
|
282
|
+
|
|
283
|
+
```python
|
|
284
|
+
import logging
|
|
285
|
+
|
|
286
|
+
# Custom configuration
|
|
287
|
+
issuer = Issuer(
|
|
288
|
+
api_key=os.environ['AHO_ISSUER_API_KEY'],
|
|
289
|
+
base_url='https://api.aho.com', # Custom base URL
|
|
290
|
+
timeout=60, # Request timeout in seconds
|
|
291
|
+
logger=logging.getLogger('aho') # Enable debug logging
|
|
292
|
+
)
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
## Async Support
|
|
296
|
+
|
|
297
|
+
The SDK provides async versions of all clients. Install with async support:
|
|
298
|
+
|
|
299
|
+
```bash
|
|
300
|
+
pip install aho-sdk[async]
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
Then use the async clients:
|
|
304
|
+
|
|
305
|
+
```python
|
|
306
|
+
import asyncio
|
|
307
|
+
from aho_sdk import AsyncIssuer
|
|
308
|
+
|
|
309
|
+
async def main():
|
|
310
|
+
# Use async context manager for automatic cleanup
|
|
311
|
+
async with AsyncIssuer(api_key=os.environ['AHO_ISSUER_API_KEY']) as issuer:
|
|
312
|
+
# All methods are async
|
|
313
|
+
credentials = await issuer.credentials.list()
|
|
314
|
+
|
|
315
|
+
# Async iteration through pages
|
|
316
|
+
async for credential in credentials:
|
|
317
|
+
print(credential['uuid'])
|
|
318
|
+
|
|
319
|
+
# Or await individual operations
|
|
320
|
+
credential = await issuer.credentials.get(uuid='...')
|
|
321
|
+
|
|
322
|
+
asyncio.run(main())
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
Async clients available:
|
|
326
|
+
- `AsyncAccount` - Async version of `Account`
|
|
327
|
+
- `AsyncSystem` - Async version of `System`
|
|
328
|
+
- `AsyncHolder` - Async version of `Holder`
|
|
329
|
+
- `AsyncVerifier` - Async version of `Verifier`
|
|
330
|
+
- `AsyncIssuer` - Async version of `Issuer`
|
|
331
|
+
- `AsyncSchemas` - Async version of `Schemas`
|
|
332
|
+
- `AsyncPublic` - Async version of `Public`
|
|
333
|
+
|
|
334
|
+
## Requirements
|
|
335
|
+
|
|
336
|
+
- Python 3.9+
|
|
337
|
+
- httpx (for async support, optional)
|
|
338
|
+
|
|
339
|
+
## License
|
|
340
|
+
|
|
341
|
+
MIT
|