Python client for Keyban API
Project description
Keyban API Client
A Python client library for interacting with the Keyban DPP API. Supports passport creation with on-chain certification, field-level encryption, and UNTP granularity levels (model, batch, item).
Features
- Passport management — create, update, list, delete passports at any granularity
- On-chain certification — automatic W3C Verifiable Credential (VC) issuance with Starknet anchoring
- Selective certification —
certifiedPathsto choose which fields fromdataare included in the certificate - Field-level encryption — SHA-256 hashing and AES-256-GCM encryption
- Type-safe models — Pydantic v2 request/response validation
- Filtering & pagination — query passports with flexible filters
Quick Start
Create a Certified Passport (Model Level)
import base64, os
from uuid import UUID, uuid4
from keyban_api_client import (
DppClient, ProductFields, CreatePassportRequest,
)
client = DppClient(
base_url="https://api.keyban.localtest.me",
api_key="your-api-key",
)
# Generate an encryption key for sensitive fields
encryption_key = base64.b64encode(os.urandom(32)).decode("ascii")
# Build data with encrypted fields
data = ProductFields.create_encrypted(
confidential_paths=["serial_number"],
enc_algorithm="aes-256-gcm",
enc_key=encryption_key,
name="My Certified Product",
serial_number="SN-CONFIDENTIAL-123",
brand="MyBrand",
)
# Create passport — certification triggers automatically for model granularity
passport = client.create_passport(CreatePassportRequest(
application=UUID("your-application-id"),
network="StarknetSepolia",
granularity="model",
modelNumber=f"my-model-{uuid4().hex[:8]}",
data=data.model_dump(),
))
print(f"Passport ID: {passport.id}")
print(f"Encryption Key: {encryption_key}")
client.close()
When data is provided for a model-level passport, the backend automatically:
- Builds a W3C Verifiable Credential with a Data Integrity proof (P-256
ecdsa-jcs-2019);credentialSubjectis the certified content byte-for-byte (no injected identifier) - Uploads the signed VC to IPFS
- Publishes a certification event on Starknet with the CID, a SHA-256 canonical content hash (enables trustless verification without knowing the passport id), and the certifier public key
Encryption & Hashing
Protect sensitive fields before sending to the API using ProductFields.create_encrypted().
| Algorithm | Reversible | Use Case |
|---|---|---|
sha256 |
No | Integrity verification (prove value existed) |
aes-256-gcm |
Yes | Confidential data (decryptable with key) |
from keyban_api_client import ProductFields
import base64, os
# SHA256 hashing (irreversible)
data = ProductFields.create_encrypted(
confidential_paths=["supplier_id"],
enc_algorithm="sha256",
name="My Product",
supplier_id="SECRET-123",
)
# AES-256-GCM encryption (reversible)
key = base64.b64encode(os.urandom(32)).decode("ascii")
data = ProductFields.create_encrypted(
confidential_paths=["serial_number"],
enc_algorithm="aes-256-gcm",
enc_key=key,
name="My Product",
serial_number="SN-CONFIDENTIAL-789",
)
print(f"Save this key: {data.encryption_key}")
# Nested paths supported
data = ProductFields.create_encrypted(
confidential_paths=["brand.supplier_id"],
enc_algorithm="sha256",
brand={"name": "Public", "supplier_id": "SECRET"},
)
Advanced Usage
Filtering & Pagination
from keyban_api_client import FilterOperator
# Filter passports by model number
f = FilterOperator(field="modelNumber", operator="contains", value="lead")
passports = client.list_passports(filters=[f], page_size=20)
for p in passports.data:
print(f"- {p.model_number} ({p.granularity})")
# Paginate
all_passports = []
page = 1
while True:
response = client.list_passports(current_page=page, page_size=50)
all_passports.extend(response.data)
if len(response.data) < 50:
break
page += 1
Selective Certification with certifiedPaths
By default, the entire data object is certified. Use certifiedPaths to certify only specific fields — updating non-certified fields won't trigger re-certification.
# Only certify brand and gtin — other fields in data are stored but not signed
passport = client.create_passport(CreatePassportRequest(
application=UUID("your-application-id"),
network="StarknetSepolia",
granularity="model",
modelNumber="selective-cert",
data={"brand": "Acme", "gtin": "3760001000001", "notes": "internal memo"},
certifiedPaths=["brand", "gtin"],
))
# Update a non-certified field — NO re-certification
client.update_passport(passport.id, UpdatePassportRequest(
data={"brand": "Acme", "gtin": "3760001000001", "notes": "updated memo"},
))
# Update a certified field — re-certification triggers automatically
client.update_passport(passport.id, UpdatePassportRequest(
data={"brand": "NewBrand", "gtin": "3760001000001", "notes": "updated memo"},
))
# Change the selection itself — re-certification triggers
client.update_passport(passport.id, UpdatePassportRequest(
certifiedPaths=["brand", "gtin", "notes"],
))
Update a Passport (Re-certification)
When data or certifiedPaths changes on a model-level passport, re-certification is triggered automatically (only if the resulting certificate content actually changed).
from keyban_api_client import UpdatePassportRequest
updated = client.update_passport(
passport.id,
UpdatePassportRequest(
data={"name": "Updated Product", "brand": "NewBrand"},
),
)
Error Handling
from keyban_api_client import DppClient, KeybanAPIError
client = DppClient(base_url="...", api_key="...")
try:
passport = client.create_passport(request)
except KeybanAPIError as e:
print(e.status_code) # 400
print(e.detail) # Full API response body
if e.status_code == 401:
print("Authentication failed - check your API key")
elif e.status_code == 404:
print("Resource not found")
finally:
client.close()
Context Manager
with DppClient(base_url="...", api_key="...") as client:
passports = client.list_passports()
API Reference
DppClient
Main client class. Constructor:
DppClient(
base_url: str,
api_key: str,
api_version: str = "v1",
timeout: int = 30,
)
Methods
| Method | Description |
|---|---|
list_passports(filters, current_page, page_size) |
List passports with filtering |
get_passport(passport_id) |
Get a passport by ID |
create_passport(data) |
Create passport (certifies if model + data) |
update_passport(passport_id, data) |
Update passport (re-certifies if certificate content changed) |
delete_passport(passport_id) |
Delete a passport |
create_passport_model(data) |
Shorthand for create_passport with model granularity |
Data Models
DppPassport
id: UUIDapplication: Applicationnetwork: str (StarknetSepolia,StarknetMainnet, etc.)granularity: str (model,batch, oritem)status: str (alwayspublished)model_number: str (nullable)batch_number: str (nullable)item_number: str (nullable)data: Dict[str, Any] (all passport data)certified_paths: List[str] (nullable, model only — fields to certify; empty = all)token_id: stripfs_cid: str (nullable — IPFS CID of the signed VC after certification)allowed_claim_email: str (nullable, item only)created_at,updated_at: datetime
CreatePassportRequest
application: UUID (required)network: str (default:StarknetSepolia)granularity: str (required —model,batch, oritem)modelNumber: str (identifier for model-level)batchNumber: str (identifier for batch-level)itemNumber: str (identifier for item-level)data: Dict[str, Any] (passport data)certifiedPaths: List[str] (model only — dot-notation paths to certify; omit or[]= all)allowedClaimEmail: str (item only — triggers minting)
UpdatePassportRequest
data: Dict[str, Any] (passport data)certifiedPaths: List[str] (model only — dot-notation paths to certify)allowedClaimEmail: str (item only)
API Endpoints
All operations go through a single unified endpoint:
| Endpoint | Method | Description |
|---|---|---|
/v1/dpp/passports |
GET | List passports (filterable by granularity) |
/v1/dpp/passports/:id |
GET | Get a passport |
/v1/dpp/passports |
POST | Create a passport |
/v1/dpp/passports/:id |
PATCH | Update a passport |
/v1/dpp/passports/:id |
DELETE | Delete a passport |
Migrating from 0.0.x to 1.0.0
Breaking changes
- Passport status is now always
published. Thestatusfield is no longer accepted in create or update requests.
Restored
certifiedPaths— available again onCreatePassportRequest,UpdatePassportRequest, andDppPassport. Was present in the old product API (0.0.6), lost during the passport migration (0.0.8), now restored on passport models. Selects which fields fromdataare included in the certificate. Omit or set to[]to certify everything.
Migration steps
- Update the client:
pip install --upgrade keyban-api-client - Remove any
statusfield from your create/update calls (or ignore — the API will reject it). - Existing calls without
certifiedPathscontinue to work as before (alldatais certified).
Changelog
1.0.0 (2026-04-14)
- Restore
certifiedPathson passport models (create, update, response) - Passports are always
published— status no longer configurable via API - Smart re-certification: only re-certifies when the resulting certificate content actually changes
- Remove
last_certificate_hashfrom response model (internal field)
License
This client is part of the DAP (Digital Asset Platform) by Keyban project.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file keyban_api_client-1.0.0.tar.gz.
File metadata
- Download URL: keyban_api_client-1.0.0.tar.gz
- Upload date:
- Size: 15.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
85b1a7455b7e3d03401deb84d84da7abb25b769769c5904e839368a53b0d65cc
|
|
| MD5 |
0b227362929cbe493aa357cbe256ff51
|
|
| BLAKE2b-256 |
d3dec2b317ea605a83c6f29437f81ffa26991f9b6bf9db0da2a9601ccbec76fd
|
File details
Details for the file keyban_api_client-1.0.0-py3-none-any.whl.
File metadata
- Download URL: keyban_api_client-1.0.0-py3-none-any.whl
- Upload date:
- Size: 12.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
2b84bee421e665d17a450c10470bba6e7d27549445c25611de269eb4376c2b90
|
|
| MD5 |
b8e1532f23046d4d3cc37df762ec1780
|
|
| BLAKE2b-256 |
4c440446ef37231a0ae506a1bfb81c4f8f4c72c6eb628a6ead2d325969a7a5c6
|